CI #710
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
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| typecheck: | |
| name: Typecheck | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - run: bun run typecheck | |
| format: | |
| name: Format | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - run: bun run format:check | |
| # The data-directory import runs at boot on every user's machine and moves | |
| # their credentials and history between two roots. It is also the one piece | |
| # of the CLI built almost entirely out of filesystem primitives that differ | |
| # per platform — hardlinks, exclusive create, chmod, path separators — so | |
| # the Linux-only `test` job below is not evidence it works. This leg is | |
| # deliberately narrow: one suite, three operating systems, no sandbox or web | |
| # assets to build. | |
| migration: | |
| name: Migration (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - run: bun test test/global/data-dir.test.ts | |
| shell: bash | |
| working-directory: backend/cli | |
| # Task 7 gave macOS a seatbelt profile for network:"allowlist" — an SBPL | |
| # profile plus an authenticated loopback proxy, built and unit-tested | |
| # entirely from Linux with the platform injected, because no Mac exists on | |
| # this project. `sandbox-exec` (macOS) and `bwrap --unshare-net` (Linux) | |
| # are unrelated OS-level mechanisms underneath the same `Sandbox` API, so a | |
| # green Linux run says nothing about whether seatbelt actually confines a | |
| # real process the way the profile text claims — only this leg's macOS run | |
| # does. See test/sandbox/egress-live-seatbelt.test.ts's doc comment for | |
| # exactly what a red run here would mean. | |
| sandbox: | |
| name: Sandbox (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| # Raised from 20: test/package/ now runs real pip installs through the | |
| # sandbox against real pypi, which the sandbox suite alone never did. | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| # Same step the `test` job already has. The sandbox job never needed it | |
| # until test/package/ joined it: those tests create a real project with | |
| # `tmpdir({ git: true })`, and `git commit` exits 128 on a runner with no | |
| # global identity configured. | |
| - name: Configure git for tests | |
| run: | | |
| git config --global user.email "ci@openscience.dev" | |
| git config --global user.name "OpenScience CI" | |
| git config --global init.defaultBranch main | |
| - name: Install and verify Linux sandbox | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes bubblewrap | |
| # Ubuntu 24.04's host-wide AppArmor policy blocks unprivileged user | |
| # namespaces on the hosted runner before bubblewrap can apply our | |
| # stricter per-process profile. This runner is disposable; enable | |
| # user namespaces for the job, then prove the sandbox can start. | |
| if [[ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]]; then | |
| echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns | |
| fi | |
| bwrap --ro-bind / / --dev /dev --proc /proc --unshare-pid --die-with-parent -- true | |
| # R is the one backend with no verification anywhere: no runner has | |
| # Rscript by default, and neither does any development machine on this | |
| # project, so its two live tests skip everywhere and it ships on faith. | |
| # r-base-core is the minimal package that provides Rscript. Linux only — | |
| # `brew install r` on the macOS leg costs several minutes for a backend | |
| # whose only platform-specific surface (the sandbox wrapper) is already | |
| # covered there by the Python tests. | |
| - name: Install R so the R installer tests actually run | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| sudo apt-get install --yes --no-install-recommends r-base-core | |
| Rscript -e 'cat("Rscript", as.character(getRversion()), "\n")' | |
| # test/package/ carries the merge gate: a governed install under | |
| # network "allowlist", plus the assertion that the shell route to the | |
| # same install is refused. Both legs run it, so the gate is a fact on | |
| # Linux and macOS rather than a claim about one of them. | |
| - run: bun test test/sandbox/ test/package/ | |
| shell: bash | |
| working-directory: backend/cli | |
| # Windows is deliberately NOT in the matrix above. That job's `test/package/` | |
| # leg is the merge gate — a governed install under network "allowlist" — and | |
| # allowlist egress does not exist on Windows yet: the container holds zero | |
| # capabilities, so it has no network by construction, and nothing serves the | |
| # broker pipe the spec carries. Adding windows-latest there would be red for a | |
| # feature that was never built, which teaches a reader nothing. | |
| # | |
| # What this job DOES cover is the part that was only ever verified by hand: | |
| # `test/sandbox/appcontainer-live.test.ts` runs a real CreateProcessW with real | |
| # SECURITY_CAPABILITIES and asserts the child is confined. Everything else in | |
| # test/sandbox/ exercises the Windows branch from Linux with the platform | |
| # injected, which proves what we compose and nothing about what Windows does | |
| # with it. That gap cost roughly ten manual round trips on a contributor's own | |
| # machine, one command at a time, for bugs that were not exotic: `-c` where cmd | |
| # wanted `/c`, `printf` in a shell with no printf, CommandLineToArgvW quoting | |
| # handed to the one program that does not parse it that way. Each would have | |
| # been red here within minutes. | |
| # | |
| # Widen this to test/package/ once the named-pipe broker lands. | |
| sandbox-windows: | |
| name: Sandbox (windows-latest) | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - name: Configure git for tests | |
| run: | | |
| git config --global user.email "ci@openscience.dev" | |
| git config --global user.name "OpenScience CI" | |
| git config --global init.defaultBranch main | |
| shell: bash | |
| # OPENSCIENCE_SANDBOX_DEBUG makes the launcher dump what it hands the | |
| # kernel — the SID, the attribute list, the whole STARTUPINFOEX with cb and | |
| # lpAttributeList broken out. On a machine no one can log into, a failure | |
| # that only says "expected true" is worth almost nothing. | |
| # Just the live file. The rest of test/sandbox/ asserts POSIX composition | |
| # -- seatbelt profile text, bubblewrap argv, `/tmp` paths that path.resolve | |
| # turns into `C:\tmp` here -- and several tests read source through | |
| # `new URL(...).pathname`, which yields `/D:/a/...` on Windows. Those are | |
| # Linux/macOS concerns that happen to live in the same directory; running | |
| # them here would produce 30-odd red results that say nothing about | |
| # Windows. Widen deliberately, not by directory. | |
| - run: bun test test/sandbox/appcontainer-live.test.ts test/sandbox/appcontainer-transport.test.ts | |
| shell: bash | |
| working-directory: backend/cli | |
| env: | |
| OPENSCIENCE_SANDBOX_DEBUG: "1" | |
| # A base interpreter the runner's own user owns. The whole Windows | |
| # difficulty is that an AppContainer can only be granted paths its user | |
| # owns, and every Python preinstalled on a GitHub runner is machine-wide — | |
| # so without this step the install test would exercise the one | |
| # configuration that is known not to work, and prove nothing about the one | |
| # users are told to set up. | |
| - name: Install uv and a user-owned Python | |
| run: | | |
| irm https://astral.sh/uv/install.ps1 | iex | |
| $env:Path = "$env:USERPROFILE\.local\bin;$env:Path" | |
| uv python install 3.12 | |
| uv python list --only-installed --output-format json | |
| shell: pwsh | |
| # The seven hops between "a process starts in a container" and "a package | |
| # is importable": interpreter choice, venv creation, the base pin, the ACL | |
| # grant, the launcher spawning its base, pip, and the pipe->broker->proxy | |
| # chain. None of it was covered, so all of it was found one round trip at | |
| # a time on a contributor's own machine. | |
| - name: Package install, end to end | |
| run: | | |
| $env:Path = "$env:USERPROFILE\.local\bin;$env:Path" | |
| bun test test/sandbox/appcontainer-install.test.ts | |
| shell: pwsh | |
| working-directory: backend/cli | |
| env: | |
| OPENSCIENCE_SANDBOX_DEBUG: "1" | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - name: Configure git for tests | |
| run: | | |
| git config --global user.email "ci@openscience.dev" | |
| git config --global user.name "OpenScience CI" | |
| git config --global init.defaultBranch main | |
| - name: Install and verify Linux sandbox | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes bubblewrap | |
| # Ubuntu 24.04's host-wide AppArmor policy blocks unprivileged user | |
| # namespaces on the hosted runner before bubblewrap can apply our | |
| # stricter per-process profile. This runner is disposable; enable | |
| # user namespaces for the job, then prove the sandbox can start. | |
| if [[ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]]; then | |
| echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns | |
| fi | |
| bwrap --ro-bind / / --dev /dev --proc /proc --unshare-pid --die-with-parent -- true | |
| - name: Build embedded web assets for server tests | |
| run: | | |
| bun run --cwd frontend/workspace build | |
| bun run --cwd backend/cli script/generate-web-assets.ts | |
| - run: bun run --cwd backend/cli test | |
| build: | |
| name: Build (web) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - name: Build workspace UI | |
| run: bun run --cwd frontend/workspace build | |
| - name: Build docs site | |
| run: bun run --cwd frontend/docs build | |
| # frontend/landing is standalone (own lockfile, not a workspace member), | |
| # so the root typecheck/build jobs never touch it. | |
| landing: | |
| name: Build (landing) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - name: Install | |
| working-directory: frontend/landing | |
| run: bun install --frozen-lockfile | |
| - name: Typecheck | |
| working-directory: frontend/landing | |
| run: bunx tsc -b | |
| - name: Build | |
| working-directory: frontend/landing | |
| run: bun run build | |
| # Packages with no typecheck task and scripts that otherwise only ever | |
| # execute during a release — catch syntax errors before release day. | |
| smoke: | |
| name: Smoke (launcher + scripts) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: ./.github/actions/setup-bun | |
| - name: Launcher parses | |
| run: node --check tooling/launcher/bin/synsci.mjs | |
| - name: npm bin wrapper parses | |
| run: node --check backend/cli/bin/openscience | |
| - name: Install script parses | |
| run: bash -n install && bash -n frontend/landing/public/install | |
| - name: Install script copies are in sync | |
| run: diff -q install frontend/landing/public/install | |
| - name: Release scripts parse | |
| # transpile-only: bundling would follow imports into third-party | |
| # packages with exports quirks (giget via node-fetch-native) | |
| run: | | |
| for f in tooling/repo/*.ts; do | |
| bun -e "new Bun.Transpiler({ loader: 'ts' }).transformSync(await Bun.file('$f').text())" | |
| echo "OK $f" | |
| done | |
| actionlint: | |
| name: Lint workflows | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2 |