Repository navigation
feat(analysis): safe, explicit, versioned AnalysisConfig layer v1 #7
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # SPDX-License-Identifier: AGPL-3.0-only | |
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Estate hygiene gates (standards/RSR alignment): cheap, fail-fast, run | |
| # before the heavyweight Julia/frontend matrix. Each gate has a documented | |
| # local equivalent — scripts/check-*.sh (see CONTRIBUTING.md). | |
| repo-hygiene: | |
| name: Repo hygiene (licence · format · lint · commit) | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version-file: .bun-version | |
| - name: Install frontend dependencies | |
| working-directory: frontend | |
| run: bun install --frozen-lockfile | |
| - name: Licence header check | |
| run: scripts/check-spdx.sh | |
| - name: Formatting check | |
| run: scripts/check-format.sh | |
| - name: Lint check | |
| run: scripts/check-lint.sh | |
| # Commit convention mirror of .githooks/commit-msg (enforced locally by | |
| # the hooksPath setting documented in CONTRIBUTING.md). | |
| - name: Commit convention check | |
| run: | | |
| subject="$(git log -1 --pretty=%s)" | |
| if ! printf '%s' "$subject" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_/-]+\))?!?: .{1,72}$'; then | |
| echo "::error::commit subject fails the conventional pattern: $subject" | |
| exit 1 | |
| fi | |
| echo "commit subject ok: $subject" | |
| test: | |
| name: Julia ${{ matrix.julia-version }} / ${{ matrix.os }} | |
| runs-on: ${{ matrix.os }} | |
| # The repository's root .Rprofile sources renv/activate.R, so R started from | |
| # the checkout auto-activates renv and rewrites the library paths. renv is a | |
| # local-development convenience; CI installs into the system library and | |
| # reaches it through R_LIBS_SITE, so the autoloader is switched off here. | |
| # This variable covers the unprivileged R that Julia embeds through RCall. | |
| # It cannot cover the installs themselves, which run under sudo: sudo resets | |
| # the environment, so those commands pass --no-init-file instead. | |
| env: | |
| RENV_CONFIG_AUTOLOADER_ENABLED: "FALSE" | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Pinned rather than tracking latest stable, because a pinned Manifest.toml | |
| # resolved by an unpinned Julia is not a reproducible build. This must equal | |
| # julia_version in Manifest.toml; it cannot be read from the pin file, since | |
| # nothing can be read before Julia exists. test/unit/test_install_pins.jl | |
| # fails if the three ever disagree. | |
| julia-version: ["1.12.5"] | |
| # Explicit, not ubuntu-latest: the R pin below names an apt package revision | |
| # built for 24.04, and a runner that silently rolled to the next LTS would | |
| # take that pin with it. | |
| os: [ubuntu-24.04] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Julia | |
| uses: julia-actions/setup-julia@v2 | |
| with: | |
| version: ${{ matrix.julia-version }} | |
| - name: Cache Julia packages | |
| uses: julia-actions/cache@v2 | |
| # Every external version CI installs is read from the committed pin file, so CI | |
| # and a developer's machine cannot drift apart. A temporary environment is used | |
| # because the project environment cannot be instantiated until R is present: | |
| # RCall's build step requires it. | |
| - name: Read pinned versions | |
| run: | | |
| julia -e ' | |
| using Pkg | |
| Pkg.activate(temp = true) | |
| Pkg.add("YAML") | |
| using YAML | |
| pins = YAML.load_file("config/defaults/tool_versions.yml") | |
| open(ENV["GITHUB_ENV"], "a") do io | |
| println(io, "R_APT_VERSION=", pins["runtimes"]["r"]["apt_version"]) | |
| println(io, "BUN_VERSION=", pins["toolchain"]["bun"]["version"]) | |
| println(io, "CUTADAPT_SPEC=cutadapt==", pins["tools"]["cutadapt"]["version"]) | |
| for tool in ("vsearch", "swarm") | |
| archive = pins["tools"][tool]["archives"]["linux-x86_64"] | |
| println(io, uppercase(tool), "_VERSION=", pins["tools"][tool]["version"]) | |
| println(io, uppercase(tool), "_URL=", archive["url"]) | |
| println(io, uppercase(tool), "_SHA256=", archive["sha256"]) | |
| end | |
| end' | |
| # R + required packages (needed by RCall) | |
| # Install R directly via apt to avoid r-lib/actions/setup-r mangling | |
| # R_HOME and stripping default packages (utils, methods, stats, etc.) | |
| # The version is pinned to the apt revision named in the pin file, which tracks | |
| # R.Version in renv.lock; CRAN's Ubuntu repository retains older revisions, so | |
| # this resolves rather than merely requesting the newest. | |
| - name: Set up R | |
| run: | | |
| wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \ | |
| | sudo gpg --dearmor -o /usr/share/keyrings/r-project.gpg | |
| echo "deb [signed-by=/usr/share/keyrings/r-project.gpg] https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/" \ | |
| | sudo tee /etc/apt/sources.list.d/r-project.list | |
| sudo apt-get update -qq | |
| sudo apt-get install -y \ | |
| r-base-core="$R_APT_VERSION" \ | |
| r-base-dev="$R_APT_VERSION" \ | |
| r-base-html="$R_APT_VERSION" \ | |
| r-recommended="$R_APT_VERSION" \ | |
| r-base="$R_APT_VERSION" | |
| Rscript -e 'cat(R.version.string, "\n")' | |
| - name: Install R system dependencies | |
| run: sudo apt-get install -y libcurl4-openssl-dev libssl-dev libxml2-dev libfontconfig1-dev | |
| # The R packages are restored from renv.lock, the same pin a developer restores | |
| # from, rather than resolved against whatever the repositories serve that day. | |
| # Asking for a bare package name pins nothing: a Bioconductor release is frozen, | |
| # so dada2 and its kin held still, but CRAN publishes only its newest revision, | |
| # so vegan silently moved off the pinned 2.7-3 and test_provenance caught the | |
| # drift. Restoring into .Library keeps the system library that the RCall steps | |
| # and R_LIBS_SITE below expect, and renv is invoked explicitly here rather than | |
| # left to activate itself. --no-init-file stops R sourcing the repository's | |
| # .Rprofile, and with it the renv autoloader, which would otherwise rebind | |
| # .Library to its own sandbox under root's cache; the flag is used rather than | |
| # the environment variable above because sudo resets the environment first. | |
| - name: Install R packages | |
| run: | | |
| sudo Rscript --no-init-file -e 'install.packages(c("renv", "BiocManager"), repos="https://cloud.r-project.org", lib=.Library)' | |
| sudo Rscript --no-init-file -e 'renv::restore(project=".", library=.Library, prompt=FALSE)' | |
| Rscript --no-init-file -e 'for (pkg in c("dada2","Biostrings","ShortRead","vegan","dplyr")) if (!require(pkg,character.only=TRUE,quietly=TRUE)) stop(pkg, " failed to install")' | |
| - name: Install cutadapt | |
| run: pip install "$CUTADAPT_SPEC" | |
| # The one tool CI does not pin. Ubuntu ships no versioned cd-hit revision worth | |
| # naming, and 24.04 is frozen, so its cd-hit does not move under us; the version | |
| # that arrives is recorded at preflight, where a discrepancy is visible. | |
| - name: Install cd-hit | |
| run: sudo apt-get install -y cd-hit | |
| # Downloaded against the pinned URL and refused unless it hashes to the pinned | |
| # SHA256, so CI runs the same bytes install.jl puts on a developer's machine. | |
| - name: Install vsearch | |
| run: | | |
| curl -sSL -o vsearch.tar.gz "$VSEARCH_URL" | |
| echo "$VSEARCH_SHA256 vsearch.tar.gz" | sha256sum -c - | |
| tar xzf vsearch.tar.gz --warning=no-unknown-keyword | |
| sudo mv "vsearch-$VSEARCH_VERSION-linux-x86_64/bin/vsearch" /usr/local/bin/vsearch | |
| rm -rf vsearch.tar.gz "vsearch-$VSEARCH_VERSION-linux-x86_64" | |
| vsearch --version | |
| - name: Install swarm | |
| run: | | |
| curl -sSL -o swarm.tar.gz "$SWARM_URL" | |
| echo "$SWARM_SHA256 swarm.tar.gz" | sha256sum -c - | |
| tar xzf swarm.tar.gz --warning=no-unknown-keyword | |
| sudo mv "swarm-$SWARM_VERSION-linux-x86_64/bin/swarm" /usr/local/bin/swarm | |
| rm -rf swarm.tar.gz "swarm-$SWARM_VERSION-linux-x86_64" | |
| swarm --version | |
| - name: Instantiate Julia project | |
| run: julia --project=. -e 'import Pkg; Pkg.instantiate()' | |
| - name: Set up Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: ${{ env.BUN_VERSION }} | |
| - name: Install frontend dependencies | |
| working-directory: frontend | |
| run: bun install --frozen-lockfile | |
| # Explicit strict-typecheck gate (strict foundation; see | |
| # docs/types/strict-mode-status.md). Runs before the (heavier) build so | |
| # type regressions fail fast instead of surfacing inside vite's bundling. | |
| - name: Typecheck frontend | |
| working-directory: frontend | |
| run: bun run typecheck | |
| # Scaffold smoke battery (bun:test) — machine-readable results + coverage | |
| # profile, both shipped as artifacts. No coverage gate yet by design | |
| # (docs/testing/infrastructure.md). | |
| - name: Test frontend | |
| working-directory: frontend | |
| run: bun test --coverage --coverage-reporter=lcov --coverage-dir tests/coverage --reporter=junit --reporter-outfile tests/results/junit.xml | |
| # Benchmark harness (proven-tests-and-benches discipline): informational | |
| # medians vs committed baseline; JSON result ships as an artifact. | |
| - name: Benchmark frontend | |
| working-directory: frontend | |
| run: bun run bench -- --json bench/results/results.json | |
| - name: Upload frontend test & benchmark artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: frontend-tests-benchmarks | |
| path: | | |
| frontend/tests/results/junit.xml | |
| frontend/tests/coverage/lcov.info | |
| frontend/bench/results/results.json | |
| if-no-files-found: warn | |
| - name: Build frontend | |
| working-directory: frontend | |
| run: bun run build | |
| - name: Download PR2 databases | |
| run: julia --project=. config/ci/download_databases.jl | |
| - name: Rebuild RCall against installed R | |
| run: julia --project=. -e 'import Pkg; Pkg.build("RCall")' | |
| - name: Verify R packages visible from RCall | |
| run: | | |
| julia --project=. -e ' | |
| using RCall | |
| R"cat(.libPaths(), sep=\"\n\")" | |
| for pkg in ["dada2", "dplyr", "vegan", "tibble"] | |
| R"if (!require($pkg, character.only=TRUE, quietly=TRUE)) stop($pkg, \" not found\")" | |
| end | |
| ' | |
| - name: Run tests | |
| env: | |
| CI_SKIP_TAXONOMY: "1" | |
| R_LIBS_SITE: /usr/local/lib/R/site-library:/usr/lib/R/site-library:/usr/lib/R/library | |
| run: julia --project=. -t 2 --code-coverage=user --compiled-modules=no test/runtests.jl --integration --server | |
| - name: Process coverage | |
| uses: julia-actions/julia-processcoverage@v1 | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v6 | |
| with: | |
| file: lcov.info | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| slug: JoshuaJewell/MetaManifold-WebUI | |
| fail_ci_if_error: false |