Skip to content

feat(analysis): safe, explicit, versioned AnalysisConfig layer v1 #7

feat(analysis): safe, explicit, versioned AnalysisConfig layer v1

feat(analysis): safe, explicit, versioned AnalysisConfig layer v1 #7

Workflow file for this run

# 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