fix(ENG-933): robustness fixes across trainer, studio, credentials, scaffold, CI #2020
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: | |
| pull_request: | |
| jobs: | |
| # Repo-wide em-dash guard. The script scans every tracked file (yaml, | |
| # md, json, html, root config, AND the linted TS / TSX / JS / JSX / | |
| # MTS / MJS source). The overlap with the ESLint rule | |
| # `local/no-em-dash` on source files is the point: this job is one | |
| # single check that succeeds iff the repo is clean end-to-end, so a | |
| # future weakening of the rule's include globs cannot silently let an | |
| # em dash slip through. The check is a single `git grep`, so the job | |
| # finishes in a few seconds and does not consume Node / pnpm cache | |
| # budget. | |
| no_em_dash: | |
| name: no em dashes | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| # The job runs `git grep` on the checkout but never pushes or | |
| # talks to the remote, so the `GITHUB_TOKEN` Actions stashes | |
| # in `.git/config` by default is unused. Disabling persistence | |
| # keeps it from being shipped in any artifact / log capture | |
| # downstream (`zizmor`'s `artipacked` lint). The matching | |
| # `excessive-permissions` lint zizmor also emits is *not* | |
| # addressed by an explicit `permissions: { contents: read }` | |
| # block on this job: this is a public repo on GitHub's | |
| # default-restrictive workflow-permissions setting, so the | |
| # token already arrives as `contents: read` only, and the | |
| # other jobs in this file follow the same pattern (only | |
| # `coverage` carries a `permissions:` block, and only | |
| # because Codecov OIDC + GitHub native coverage upload | |
| # need scopes the default does not include). Adding the | |
| # block here would break that pattern without granting any | |
| # additional security. | |
| persist-credentials: false | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| - name: Check no em dashes | |
| run: node scripts/check-no-em-dash.mts | |
| build: | |
| name: typecheck · lint · test · build · ${{ matrix.os }} · node ${{ matrix.node }} | |
| runs-on: ${{ matrix.os }} | |
| # Force bash on every OS so `run:` steps that use POSIX redirection (e.g. | |
| # `>> "$GITHUB_STEP_SUMMARY"`) work uniformly. windows-latest defaults to | |
| # pwsh otherwise. Git Bash ships on the GitHub-hosted Windows image. | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Cross every Node entry below with all three GitHub-hosted OS images. | |
| # The Node matrix exists to surface strip-types / transform-types / | |
| # amaro behaviour shifts; running it on Linux + Windows + macOS also | |
| # catches platform-specific regressions in path handling, child | |
| # process spawning, and pnpm hardlink/symlink behaviour. | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| # The 22.x line is pinned to the DoS-patched range (22.22.0+, see | |
| # https://nodejs.org/en/blog/vulnerability/january-2026-dos-mitigation-async-hooks); | |
| # earlier 22.x are out of support. The 24.x entries still cover | |
| # strip-types / transform-types / amaro behaviour shifts. | |
| node: | |
| # Node 22 LTS DoS-patched range | |
| - '22.22.0' # exact minimum supported version (DoS-patched) | |
| - '>=22.22.0 <23' # latest 22.x range | |
| - '>=24.0.0 <24.1.0' # Node 24 initial state; strip-types default-on / Stability: RC | |
| - '>=24.1.0 <24.3.0' # amaro updates | |
| # Major: experimental warning removed in Node 24 | |
| - '>=24.3.0 <24.5.0' # warning removed / amaro 1.1.0 / CJS TS loader refactor | |
| - '>=24.5.0 <24.8.0' # amaro updates | |
| - '>=24.8.0 <24.10.0' # amaro updates | |
| - '>=24.10.0 <24.12.0' # amaro updates | |
| # Major: type stripping marked Stable in Node 24 LTS | |
| - '24.12.0' # exact boundary version where type stripping became stable | |
| - '>=24.12.0 <25' # stable Node 24 range after 24.12.0 | |
| # Node 26 (Current). Follow the same floor + latest pattern as | |
| # Node 24: 26.0.0 catches initial-release behaviour, the open | |
| # range pulls in patch updates without manual bumps. | |
| - '26.0.0' # exact 26.x floor (initial release) | |
| - '>=26.0.0 <27' # latest 26.x | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| # action-setup@v6 bootstrap-installs pnpm v11 (per its committed | |
| # lockfile) and only honours `packageManager` via pnpm's own | |
| # `manage-package-manager-versions`. Force @pnpm/exe so bootstrap | |
| # pnpm runs on its bundled Node regardless of which matrix Node | |
| # we switch to next. | |
| # | |
| # Pinning `version: 11.13.1` instead doesn't work: action-setup | |
| # compares it against the full `[email protected]+sha512.xxx` | |
| # packageManager string and refuses on mismatch | |
| # (ERR_PNPM_BAD_PM_VERSION-style error). | |
| # | |
| # Do NOT pin pnpm 11.12.0 or 11.13.0: those two releases publish | |
| # their per-platform npm packages (@pnpm/linux-x64, @pnpm/macos- | |
| # arm64, @pnpm/win-x64, ...) WITHOUT the native binary (~1.9 kB | |
| # metadata-only tarballs), so `standalone: true` here resolves | |
| # @pnpm/exe, finds no binary, and leaves the placeholder that dies | |
| # with `pnpm: 1: This: not found` on every OS. Fixed upstream in | |
| # 11.13.1 (platform packages ship the binary again); 11.11.0 was | |
| # the last good release before the gap. See pnpm/pnpm#12955 | |
| # (release-flow regression from pnpm/pnpm#12949, fixed in 11.13.1). | |
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| with: | |
| standalone: true | |
| # Turbo's local cache is at .turbo/cache. Keying on the commit SHA | |
| # always writes a fresh entry; restore-keys falls back to the most | |
| # recent prior cache for the same OS/node so unaffected task hashes | |
| # hit immediately. node version is part of the key because turbo's | |
| # hash currently does not factor it in, and we want to keep the | |
| # matrix entries from clobbering each other if that ever changes. | |
| - name: Cache Turborepo | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: .turbo/cache | |
| key: turbo-${{ runner.os }}-node${{ matrix.node }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-${{ runner.os }}-node${{ matrix.node }}- | |
| - name: Setup matrix Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| cache: pnpm | |
| - name: Print resolved Node version | |
| run: | | |
| node --version | |
| echo "**Resolved Node:** \`$(node --version)\` (matrix: \`${{ matrix.node }}\`)" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Install | |
| # Belt-and-suspenders: pnpm-workspace.yaml's `allowBuilds` | |
| # already gates which packages may run postinstall, and | |
| # `playwright` is not in that list, so `pnpm install` won't | |
| # invoke Playwright's browser-download script today. Setting | |
| # `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` here keeps that | |
| # contract explicit so a future `allowBuilds` change can't | |
| # silently re-enable a 150 MB postinstall download that the | |
| # following cache + `playwright install` steps are meant to | |
| # satisfy. | |
| env: | |
| PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" | |
| run: pnpm install | |
| - name: Typecheck | |
| run: pnpm typecheck | |
| - name: Lint | |
| run: pnpm lint | |
| - name: Format check | |
| run: pnpm format:check | |
| # Playwright bundles its own browser binaries (~150 MB). Cache by | |
| # OS and the lockfile hash so a `@playwright/test` upgrade busts | |
| # the cache automatically. Cache locations are OS-specific: | |
| # Linux uses ~/.cache/ms-playwright, macOS uses | |
| # ~/Library/Caches/ms-playwright, Windows uses | |
| # %USERPROFILE%/AppData/Local/ms-playwright. `actions/cache` | |
| # expands `~` through Node's `os.homedir()` and only treats `/` | |
| # as a path separator on Windows runners; `~\AppData\…` would | |
| # not resolve, so all three patterns use POSIX separators. | |
| # `actions/cache` silently skips paths that don't exist on the | |
| # current runner, so listing all three is safe. | |
| - name: Cache Playwright browsers | |
| id: playwright-cache | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cache/ms-playwright | |
| ~/Library/Caches/ms-playwright | |
| ~/AppData/Local/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: | | |
| playwright-${{ runner.os }}- | |
| # `--with-deps` installs apt packages that Chromium needs at | |
| # runtime (libnss3, libxkbcommon0, etc.). It's Linux-only; the | |
| # action no-ops it on macOS/Windows but warns; gating on runner.os | |
| # keeps the log clean. | |
| - name: Install Playwright (Linux) | |
| if: runner.os == 'Linux' | |
| run: pnpm --filter @arkor/e2e-studio exec playwright install --with-deps chromium | |
| - name: Install Playwright (macOS/Windows) | |
| if: runner.os != 'Linux' | |
| run: pnpm --filter @arkor/e2e-studio exec playwright install chromium | |
| - name: Test | |
| env: | |
| ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC: file:${{ github.workspace }}/packages/arkor | |
| run: pnpm test | |
| # Upload Playwright's HTML report + traces / videos / screenshots | |
| # for every non-cancelled outcome so failures retain their | |
| # artefacts regardless of which step failed. `if: failure()` | |
| # would only fire when a *prior* step had already failed at the | |
| # time this step runs; because Upload sits before Build (line | |
| # below), a Test green + Build red sequence used to lose the | |
| # report entirely. `always() && !cancelled()` also uploads on | |
| # fully-green runs; the report dir is small then, so the extra | |
| # artefact cost is negligible relative to the debugging value of | |
| # having traces for every run. The `!cancelled()` half ensures | |
| # a manual cancel doesn't get stalled waiting for an artefact | |
| # upload that the maintainer is no longer interested in. | |
| # `if-no-files-found: ignore` covers the case where Turbo | |
| # cancelled `@arkor/e2e-studio#test` before Playwright could | |
| # write anything (see comment on the option below). | |
| # | |
| # `matrix.node` would disambiguate across the matrix but it | |
| # contains `<`, `>`, `=`, which `actions/upload-artifact` rejects | |
| # (NTFS-incompatible); `strategy.job-index` is the documented | |
| # alphanumeric escape hatch. | |
| - name: Upload Playwright report | |
| if: ${{ always() && !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: playwright-report-${{ runner.os }}-${{ strategy.job-index }} | |
| path: | | |
| e2e/studio/playwright-report | |
| e2e/studio/test-results | |
| # Turbo may cancel `@arkor/e2e-studio#test` when an earlier | |
| # task fails (e.g. a unit test failure in `arkor:test`), | |
| # leaving Playwright with no chance to write its report. | |
| # `actions/upload-artifact@v7` defaults to failing the step | |
| # when no files match, which would convert "test step | |
| # failed for an unrelated reason" into "upload step also | |
| # failed" and noise up the CI summary. `ignore` makes the | |
| # upload silently no-op when there's nothing to publish. | |
| if-no-files-found: ignore | |
| - name: Build | |
| run: pnpm build | |
| # Cross-pm install matrix: exercises `arkor init --use-<pm>` and | |
| # `create-arkor --use-<pm>` against every package manager the SDK accepts, | |
| # crossed with OS and a curated set of Node versions known to shift | |
| # spawn / pipe / strip-types behaviour (e.g. libuv 1.51 in Node 22.17 | |
| # changed macOS pipe-flush timing, the kind of thing that ate hours of | |
| # debugging in eng-606). The build job above already runs `pnpm test` | |
| # end-to-end, but its install assertions only cover the pms that a | |
| # typical contributor has on PATH (npm + pnpm). The yarn / yarn-berry / | |
| # bun lanes pull their runtimes in CI-only because they aren't a | |
| # development prerequisite and we don't want `pnpm test` on a fresh | |
| # checkout to require them. | |
| # | |
| # KNOWN MATRIX GAP (Windows × bun × `arkor init`): this combination is | |
| # NOT covered by the install matrix. `arkor init --use-bun` on Windows | |
| # currently fails to produce `bun.lock` due to a CLI-binary-axis | |
| # divergence between `arkor init` and `create-arkor` (the two share | |
| # `cli-internal/install.ts` but the spawn shape differs in a way that | |
| # bun on Windows is sensitive to). The gate is implemented at the | |
| # test-runner level in `e2e/cli/src/arkor-init.test.ts` via `it.skip` | |
| # so vitest reports the case as skipped per CI run; the | |
| # `create-arkor.test.ts` lane DOES run bun on Windows and covers bun | |
| # itself + the create-arkor spawn shape. The `arkor init --use-bun` | |
| # CLI flag stays exposed because the divergence is treated as a bug | |
| # to fix, not a permanent constraint; until then, the | |
| # docs/cli/init.mdx page and `docs/ja/cli/init.mdx` mirror carry an | |
| # explicit Windows caveat on the `--use-bun` flag so users hitting it | |
| # have signal that this is known. Re-enable the case (drop the | |
| # `isBunOnWindows` arm in arkor-init.test.ts, drop the caveat in | |
| # both docs/cli/init.mdx files, AND this comment block) when the | |
| # spawn-shape divergence is fixed. | |
| # | |
| # Each runner sets `ARKOR_E2E_PM=<label>`; the e2e suites | |
| # (e2e/cli/src/{arkor-init,create-arkor}.test.ts) gate their install | |
| # cases on that env var so exactly one pm's install assertions run per | |
| # matrix entry. The rest of the e2e suite runs unfiltered: setup | |
| # (workspace install + CLI build + corepack/bun bootstrap) dominates | |
| # each job's wall time, so the marginal cost of running every e2e test | |
| # over the matrix pm/Node/OS triple is small and occasionally surfaces | |
| # cross-axis interactions outside the install path (e.g. yarn-berry's | |
| # PnP fs layout influencing scaffold or spawn assertions). | |
| install-matrix: | |
| name: install · ${{ matrix.id }} · ${{ matrix.os }} · node ${{ matrix.node }} | |
| runs-on: ${{ matrix.os }} | |
| # Gate the 147-job Cartesian fanout on the fast em-dash guard so a | |
| # style regression in any tracked file (yaml, md, ts, ...) fails | |
| # before the install matrix burns runner minutes provisioning | |
| # corepack / yarn-berry / bun across every OS, Node, and pm cell. | |
| needs: no_em_dash | |
| defaults: | |
| run: | |
| shell: bash | |
| strategy: | |
| fail-fast: false | |
| # Full Cartesian product: 3 OS × 7 Node × 7 pm = 147 jobs. | |
| # This is intentional. `arkor init` and `create-arkor` are | |
| # bootstrap-time entry points; every (OS, Node, pm) cell | |
| # exercises a different combination of `child_process.spawn` | |
| # shell semantics (`.cmd` shims on Windows, corepack | |
| # provisioning, yarn-berry's PnP-vs-`node-modules` linker, | |
| # pnpm 9 / 10 / 11 lockfile and `allowBuilds` schema | |
| # divergence, bun's lockfile + spawn-shape quirks). The | |
| # observed divergences this matrix has caught (bun on | |
| # Windows missing `bun.lock` under `arkor init`'s spawn | |
| # shape; pnpm 9's `packages:` requirement; yarn 4 PnP | |
| # default on `CI=1`) were not reproducible from a smaller | |
| # axis-folded matrix; a single missing combination would | |
| # have masked the bug at review time. The runtime / queue- | |
| # time cost is accepted for that reason. | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| # Curated Node versions: the supported Node lines, i.e. | |
| # the active LTS lines (22, 24) plus Current (26, not | |
| # yet LTS; promotes in Oct 2026). main's engines bump | |
| # (`packages/arkor/package.json` → `>=22.22.0`) made the | |
| # prior pre-22.22 entries (22.13/17/21) point at | |
| # unsupported configurations, so they've been dropped | |
| # along with the yarn × pre-22.22 excludes that masked | |
| # the engines mismatch. | |
| # | |
| # Each Node line gets two entries: exact engines floor | |
| # (so the lowest officially supported version is actually | |
| # exercised, not just whatever 22.22+ patch setup-node | |
| # happens to resolve) plus the live `<minor>` range. The | |
| # build matrix above uses the same pattern. | |
| node: | |
| - '22.22.0' # exact engines floor (DoS-patched 22 LTS) | |
| - '>=22.22.0 <23' # latest 22.x | |
| - '24.0.0' # exact 24.x floor (initial release; type-stripping default-on / Stability: RC) | |
| - '24.12.0' # exact boundary where type stripping became Stable | |
| - '>=24.12.0 <25' # latest 24.x | |
| - '26.0.0' # exact 26.x floor (initial release) | |
| - '>=26.0.0 <27' # latest 26.x | |
| id: [pnpm-9, pnpm-10, pnpm-11, npm, yarn, yarn-berry, bun] | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| # The prior `rolldownIncompat` bootstrap path (separate Node 24 | |
| # checkout to install + build CLIs before exercising them under | |
| # a matrix Node where rolldown's native binding wouldn't load) | |
| # was for the pre-22.22 Node entries that the engines bump | |
| # removed from this matrix. Every supported Node now satisfies | |
| # rolldown's `^20.19 || >=22.12` requirement, so the bootstrap | |
| # path is no longer needed and the matrix uses a single Node | |
| # setup throughout. | |
| - name: Setup matrix Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| # Intentionally no `cache: pnpm`; at this point pnpm isn't on | |
| # PATH yet (the install-matrix job uses Node's bundled corepack | |
| # to provision pnpm in the *next* step instead of running | |
| # `pnpm/action-setup` up-front), so setup-node's pnpm-aware | |
| # cache lookup fails with `Unable to locate executable file: | |
| # pnpm`. Each job re-fetches deps fresh; ~30 s/job extra is | |
| # noise next to the 2–3 min setup cost, and skipping the cache | |
| # avoids a class of cross-OS pitfalls (e.g. corepack writing | |
| # shims into a system-owned bin dir on Windows). | |
| # We deliberately use Node's bundled corepack instead of | |
| # pnpm/action-setup. action-setup adds its pnpm to PATH in a way | |
| # that shadows corepack's shim, which would defeat the matrix | |
| # --activate below: every entry would end up running action-setup's | |
| # pinned pnpm regardless of which version we asked corepack to | |
| # activate. Workspace `pnpm install` still resolves to pnpm 11.13.1 | |
| # because corepack reads `packageManager` from the root | |
| # package.json; only the matrix pm test runs in tmpdirs (no | |
| # packageManager field there), so corepack falls back to the | |
| # activated default. | |
| - name: Enable corepack | |
| run: | | |
| # Some Node 22.x minors ship a corepack predating the | |
| # npm-registry signing-key rotation, so a plain `corepack | |
| # enable` followed by `pnpm install` blows up in | |
| # `verifySignature`. Upgrade first to be safe across the | |
| # matrix Nodes: `[email protected]` carries the | |
| # post-rotation keys and its engines `^20.10.0 || | |
| # ^22.11.0 || >=24.0.0` cover every supported entry. | |
| # `--force` is required on Windows to clobber the runner | |
| # image's pre-seeded `C:\npm\prefix\yarn.cmd`. Pinned | |
| # (not `@latest`) so an upstream corepack release can't | |
| # change resolution behaviour or raise its supported-Node | |
| # floor and start failing this matrix without any | |
| # repository change, mirroring the same pinning policy | |
| # applied to pnpm/yarn/bun below. | |
| npm install -g --force [email protected] | |
| corepack enable | |
| echo "COREPACK_ENABLE_DOWNLOAD_PROMPT=0" >> "$GITHUB_ENV" | |
| - name: Install workspace + build CLIs | |
| run: | | |
| pnpm install | |
| pnpm --filter create-arkor build | |
| pnpm --filter arkor build | |
| # NOTE: an earlier "Pack arkor into a scaffold tarball" step | |
| # used to run `pnpm pack` and expose the result as a workflow | |
| # output, fed into the now-removed `ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC` | |
| # job env. With the per-case `file:../<tgz>` override (the | |
| # only viable shape on Windows + pnpm-10 / bun) the real- | |
| # install tests now pack their own tarball in `beforeAll`, | |
| # so the workflow-level pack was paying its `pnpm pack` cost | |
| # on every leg of the (very large) matrix without anything | |
| # consuming the output. PR #99 round-39 Copilot review. | |
| - name: Setup pm under test (${{ matrix.id }}) | |
| if: ${{ matrix.id != 'bun' }} | |
| run: | | |
| case "${{ matrix.id }}" in | |
| pnpm-9) corepack prepare "[email protected]" --activate ;; | |
| pnpm-10) corepack prepare "[email protected]" --activate ;; | |
| pnpm-11) corepack prepare "[email protected]" --activate ;; | |
| npm) echo "Using bundled npm $(npm --version)" ;; | |
| yarn) corepack prepare "[email protected]" --activate ;; | |
| # yarn classic and berry both use the `yarn` npm package; | |
| # only the version line differs (1.x vs 4.x). | |
| yarn-berry) corepack prepare "[email protected]" --activate ;; | |
| esac | |
| # bun isn't on corepack's package list and its official `curl | | |
| # bash` installer is POSIX-only, so on Windows it'd silently no-op. | |
| # `oven-sh/setup-bun` handles all three OSes uniformly and adds | |
| # bun to PATH via $GITHUB_PATH for us. | |
| # | |
| # Pinning the version (rather than `latest`) keeps the matrix | |
| # reproducible in lockstep with the corepack-prepared | |
| # pnpm-*/yarn versions above. Copilot review on PR #99 flagged | |
| # `bun-version: latest` as a way for an upstream bun release to | |
| # silently flip CI green/red without a repo change. Bump in a | |
| # dedicated commit when intentionally rolling forward. | |
| - name: Setup bun | |
| if: ${{ matrix.id == 'bun' }} | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 | |
| with: | |
| bun-version: '1.3.13' | |
| - name: Confirm pm version | |
| run: | | |
| # Run from $RUNNER_TEMP, not the workspace cwd. corepack | |
| # enforces the workspace's `packageManager: [email protected]` and | |
| # rejects `yarn --version` / `bun --version` with "This project | |
| # is configured to use pnpm". Stepping out into a directory | |
| # without a packageManager field lets corepack fall back to the | |
| # version we just `--activate`d for the matrix pm, which is | |
| # also more useful diagnostically (it prints the matrix | |
| # version, not the workspace's pinned 11.13.1 for pnpm-N | |
| # entries). | |
| cd "$RUNNER_TEMP" | |
| case "${{ matrix.id }}" in | |
| pnpm-*) pnpm --version ;; | |
| npm) npm --version ;; | |
| yarn|yarn-berry) yarn --version ;; | |
| bun) bun --version ;; | |
| esac | |
| # `ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC` is deliberately NOT | |
| # set at the job level. The real-install matrix tests | |
| # (`runs real <pm> install + git commit` in | |
| # `e2e/cli/src/{arkor-init,create-arkor}.test.ts`) pack | |
| # their own `arkor-*.tgz` in `beforeAll` and pass a per-case | |
| # `file:../<tgz>` override into `runCli` / `runCreateArkor`, | |
| # which is required because pnpm 10 rejects absolute | |
| # Windows-drive `file:` URIs (`file:D:\…`) and bun on Windows | |
| # skips `bun.lock` generation for multi-segment relative | |
| # `file:` paths. Setting a job-wide absolute fallback here | |
| # would mask that requirement. A future test that performs | |
| # a real install without overriding would silently pass on | |
| # Linux/macOS and break only on Windows × pnpm-10. (PR #99 | |
| # round-39 Copilot review: that earlier fallback predated | |
| # the per-case override and is now vestigial.) | |
| # | |
| # `YARN_NETWORK_CONCURRENCY=1` (yarn 1 optionalDeps | |
| # extraction race) lives in e2e/cli/src/spawn-cli.ts, set | |
| # per spawn so local `ARKOR_E2E_PM=yarn-berry pnpm --filter | |
| # @arkor/e2e-cli test` runs work without extra env-var | |
| # threading. The round-13 yarn-4 immutable-install fix lives | |
| # in `packages/cli-internal/src/install.ts` itself (the SDK | |
| # forwards `YARN_ENABLE_IMMUTABLE_INSTALLS=false` to the | |
| # spawned `yarn install`), so real users running `arkor init | |
| # --use-yarn` in their CI no longer fail on the missing | |
| # initial lockfile. | |
| - name: Run e2e suite under matrix pm | |
| run: | | |
| # Derive the e2e install-test gate label from the matrix id. | |
| # yarn-berry and the pnpm-N entries share a common label | |
| # across versions because the test only cares about which | |
| # `--use-<flag>` to pass; the binary version is selected by | |
| # corepack / setup-bun above. | |
| # | |
| # The label vocabulary lives in | |
| # `e2e/cli/src/install-matrix.ts` (`InstallCaseLabel` / | |
| # `INSTALL_CASES`). When adding or removing a matrix `id` | |
| # here, update that file in lockstep; the e2e suite's gate | |
| # function reads `ARKOR_E2E_PM` against the same label set. | |
| case "${{ matrix.id }}" in | |
| pnpm-*) PM_LABEL=pnpm ;; | |
| yarn-berry) PM_LABEL=yarn-berry ;; | |
| *) PM_LABEL="${{ matrix.id }}" ;; | |
| esac | |
| # We deliberately don't `-t`-filter to "real <pm> install": | |
| # setup (workspace install + CLI build + corepack/bun) is the | |
| # bulk of each job's wall time, so dropping the filter to run | |
| # the full e2e suite is ~30 s/job extra and surfaces any | |
| # cross-axis interactions we didn't anticipate (e.g. yarn-berry | |
| # PnP affecting unrelated spawn paths). The install cases | |
| # themselves are still pm-gated via `ARKOR_E2E_PM` so only one | |
| # pm's install assertions run per job. | |
| ARKOR_E2E_PM="$PM_LABEL" pnpm --filter @arkor/e2e-cli exec vitest run | |
| coverage: | |
| name: coverage · upload to Codecov | |
| runs-on: ubuntu-latest | |
| # The matrix above already exercises tests across Node versions; this | |
| # job runs once on the newest LTS to keep coverage + Test Analytics | |
| # uploads stable. Codecov uploads use OIDC (`use_oidc: true`); no | |
| # CODECOV_TOKEN secret is stored in this repo. `id-token: write` is | |
| # required for the runner to mint the OIDC JWT that codecov-action | |
| # exchanges for an upload token. See | |
| # https://docs.codecov.com/docs/github-tokenless-uploads. | |
| # | |
| # `code-quality: write` is required by GitHub's native code-coverage | |
| # public preview (announced 2026-05-26): `actions/upload-code-coverage` | |
| # POSTs the Cobertura XML through the GitHub API using the workflow's | |
| # default GITHUB_TOKEN, and the token needs that scope to write to | |
| # the code-quality API. Coverage uploads to GitHub run in parallel | |
| # with the Codecov uploads below; Codecov stays authoritative while | |
| # the GitHub feature is in preview, so both signals are available | |
| # side-by-side. See | |
| # https://github.blog/changelog/2026-05-26-code-coverage-in-pull-requests-is-now-in-public-preview/. | |
| permissions: | |
| id-token: write | |
| code-quality: write | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| # Same reason as the build job; see comment there. | |
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 | |
| with: | |
| standalone: true | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| cache: pnpm | |
| - name: Cache Turborepo | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: .turbo/cache | |
| key: turbo-${{ runner.os }}-coverage-${{ github.sha }} | |
| restore-keys: | | |
| turbo-${{ runner.os }}-coverage- | |
| - name: Install | |
| # Belt-and-suspenders: pnpm-workspace.yaml's `allowBuilds` | |
| # already gates which packages may run postinstall, and | |
| # `playwright` is not in that list, so `pnpm install` won't | |
| # invoke Playwright's browser-download script today. Setting | |
| # `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` here keeps that | |
| # contract explicit so a future `allowBuilds` change can't | |
| # silently re-enable a 150 MB postinstall download that the | |
| # following cache + `playwright install` steps are meant to | |
| # satisfy. | |
| env: | |
| PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" | |
| run: pnpm install | |
| # Mirror the matrix job's Playwright cache so the coverage run | |
| # also reuses the previously-downloaded browsers. | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: | | |
| ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} | |
| restore-keys: | | |
| playwright-${{ runner.os }}- | |
| - name: Install Playwright | |
| run: pnpm --filter @arkor/e2e-studio exec playwright install --with-deps chromium | |
| # Tests must keep going on failure so every package still produces | |
| # its lcov.info / junit.xml; turbo's default `--continue=never` would | |
| # cancel sibling tasks after the first failure and starve the upload | |
| # steps below. Subsequent uploads gate on `!cancelled()` so they run | |
| # even when tests fail; the job's final outcome still reflects the | |
| # test result. | |
| - name: Test with coverage | |
| env: | |
| ARKOR_INTERNAL_SCAFFOLD_ARKOR_SPEC: file:${{ github.workspace }}/packages/arkor | |
| # `create-arkor`'s tsdown only emits sourcemaps when this is | |
| # set; e2e/cli's c8 needs them to remap V8 coverage from the | |
| # spawned scaffolder back to packages/create-arkor/src/**. | |
| # Replaces the inline `CREATE_ARKOR_BUILD_SOURCEMAP=1 ...` | |
| # that lived in e2e/cli's pretest:coverage hook before | |
| # workspace deps + turbo's `^build` made that pretest | |
| # redundant. | |
| CREATE_ARKOR_BUILD_SOURCEMAP: "1" | |
| run: pnpm exec turbo run test:coverage --continue | |
| - name: Upload arkor coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| files: ./packages/arkor/coverage/lcov.info | |
| flags: arkor | |
| fail_ci_if_error: false | |
| - name: Upload create-arkor coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| files: ./packages/create-arkor/coverage/lcov.info | |
| flags: create-arkor | |
| fail_ci_if_error: false | |
| - name: Upload cli-internal coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| files: ./packages/cli-internal/coverage/lcov.info | |
| flags: cli-internal | |
| fail_ci_if_error: false | |
| - name: Upload studio-app coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| files: ./packages/studio-app/coverage/lcov.info | |
| flags: studio-app | |
| fail_ci_if_error: false | |
| # E2E coverage is collected via c8 wrapping vitest in e2e/cli; the | |
| # spawned CLI children inherit NODE_V8_COVERAGE and their hits are | |
| # remapped through tsdown's sourcemaps back into | |
| # packages/{arkor,create-arkor}/src/**. | |
| - name: Upload e2e-cli coverage | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| files: ./e2e/cli/coverage/lcov.info | |
| flags: e2e-cli | |
| fail_ci_if_error: false | |
| # GitHub native code coverage (public preview, announced 2026-05-26). | |
| # Uploads the Cobertura XML produced alongside lcov.info by each | |
| # package's vitest v8 coverage reporter so GitHub can display | |
| # per-PR coverage % directly on pull requests. e2e/cli is excluded: | |
| # see the skip note below for the c8 / cobertura empty-output | |
| # rationale. | |
| # Codecov stays authoritative while the feature is in preview; once | |
| # GitHub Coverage reaches GA we can decide whether to drop one path. | |
| # `fail-on-error: false` mirrors the Codecov uploads' philosophy: | |
| # an upload glitch must not turn a green test run red. The action | |
| # itself already no-ops on `merge_group` events and fork PRs, so no | |
| # extra `if:` gating is needed for those cases. | |
| - name: Upload arkor coverage to GitHub | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0 | |
| with: | |
| file: ./packages/arkor/coverage/cobertura-coverage.xml | |
| language: TypeScript | |
| label: code-coverage/arkor | |
| fail-on-error: false | |
| - name: Upload create-arkor coverage to GitHub | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0 | |
| with: | |
| file: ./packages/create-arkor/coverage/cobertura-coverage.xml | |
| language: TypeScript | |
| label: code-coverage/create-arkor | |
| fail-on-error: false | |
| - name: Upload cli-internal coverage to GitHub | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0 | |
| with: | |
| file: ./packages/cli-internal/coverage/cobertura-coverage.xml | |
| language: TypeScript | |
| label: code-coverage/cli-internal | |
| fail-on-error: false | |
| - name: Upload studio-app coverage to GitHub | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-code-coverage@abb5995db9e0199b0e2bb9dbd136fce4cb1ec4d3 # v1.3.0 | |
| with: | |
| file: ./packages/studio-app/coverage/cobertura-coverage.xml | |
| language: TypeScript | |
| label: code-coverage/studio-app | |
| fail-on-error: false | |
| # e2e/cli intentionally does NOT upload a Cobertura report to GitHub. | |
| # c8's cobertura reporter currently emits an empty 402-byte shell | |
| # here: the istanbul cobertura writer doesn't pick up c8's | |
| # sourcemap-remapped coverage of the spawned CLI children the way | |
| # the lcov reporter does. The Codecov upload above stays on the | |
| # lcov path, which has produced usable signal historically; once | |
| # the c8 ↔ cobertura gap is closed (or we switch e2e/cli off c8), | |
| # re-add this upload alongside the others. | |
| # Codecov Test Analytics: uploads JUnit XML produced by the vitest | |
| # `junit` reporter. Tracks pass/fail history per test, surfaces flaky | |
| # tests, and shows failure trends on the Codecov dashboard. See | |
| # https://docs.codecov.com/docs/test-analytics. | |
| - name: Upload arkor test results | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| report_type: test_results | |
| files: ./packages/arkor/coverage/junit.xml | |
| flags: arkor | |
| fail_ci_if_error: false | |
| - name: Upload create-arkor test results | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| report_type: test_results | |
| files: ./packages/create-arkor/coverage/junit.xml | |
| flags: create-arkor | |
| fail_ci_if_error: false | |
| - name: Upload cli-internal test results | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| report_type: test_results | |
| files: ./packages/cli-internal/coverage/junit.xml | |
| flags: cli-internal | |
| fail_ci_if_error: false | |
| - name: Upload studio-app test results | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| report_type: test_results | |
| files: ./packages/studio-app/coverage/junit.xml | |
| flags: studio-app | |
| fail_ci_if_error: false | |
| - name: Upload e2e-cli test results | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| report_type: test_results | |
| files: ./e2e/cli/coverage/junit.xml | |
| flags: e2e-cli | |
| fail_ci_if_error: false | |
| # Studio E2E reports JUnit XML produced by the Playwright junit | |
| # reporter (see e2e/studio/playwright.config.ts). No lcov: browser | |
| # tests don't collect server-side V8 coverage, so we only feed | |
| # Test Analytics for pass/fail trend tracking. | |
| - name: Upload e2e-studio test results | |
| if: ${{ !cancelled() }} | |
| uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 | |
| with: | |
| use_oidc: true | |
| disable_search: true | |
| report_type: test_results | |
| files: ./e2e/studio/coverage/junit.xml | |
| flags: e2e-studio | |
| fail_ci_if_error: false |