ci(js-tests): split check into parallel matrix shards per workspace (#70252)

Every npm workspace package now defines check:* scripts (check:unit,
check:lint, check:bundle, check:typecheck, etc.) that fan out to
separate matrix runners in CI. The check umbrella script chains all
shards for local dev.

The matrix discovery in the workspaces job queries npm workspaces,
finds check:* scripts (in package.json insertion order), falls back to
check when none exist, and emits an include matrix. No hardcoded
package names — the workflow is fully auto-derived from workspace
metadata.

Previously every package ran a single check script on one worker, and
the fix step (lint:fix + prettier) ran as a separate CI step with
special-cased run_fix gating to avoid running on every shard. Now that
lint is just another check:lint shard, the run_fix field and the fix
step are gone entirely — lint runs in its own runner like everything
else.
This commit is contained in:
ethernet 2026-07-23 18:45:16 -04:00 committed by GitHub
parent 4c9628eab5
commit 5be99b6fce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 39 additions and 17 deletions

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
packages: ${{ steps.set-matrix.outputs.packages }}
checks: ${{ steps.set-matrix.outputs.checks }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
@ -22,21 +22,40 @@ jobs:
command: npm ci --ignore-scripts
- id: set-matrix
run: |
PACKAGES=$(npm query .workspace | jq -c '[.[].location]')
if [ "$PACKAGES" = "[]" ] || [ -z "$PACKAGES" ]; then
echo "::error::Workspace discovery produced an empty package list — refusing to emit a zero-length matrix (would skip all JS/TS checks silently)."
exit 1
fi
echo "packages=$PACKAGES" >> "$GITHUB_OUTPUT"
node -e '
const { execSync } = require("child_process");
const pkgs = JSON.parse(execSync("npm query .workspace", { encoding: "utf-8" }));
if (pkgs.length === 0) {
console.error("::error::Workspace discovery produced an empty package list — refusing to emit a zero-length matrix (would skip all JS/TS checks silently).");
process.exit(1);
}
const checks = [];
for (const pkg of pkgs) {
const scripts = pkg.scripts || {};
const subs = Object.keys(scripts).filter(s => /^check:.+$/.test(s));
if (subs.length > 0) {
for (const script of subs) {
checks.push({ package: pkg.location, script });
}
} else if (scripts.check) {
checks.push({ package: pkg.location, script: "check" });
}
}
if (checks.length === 0) {
console.error("::error::No check scripts found in any workspace package.");
process.exit(1);
}
process.stdout.write("checks=" + JSON.stringify(checks) + "\n");
' >> "$GITHUB_OUTPUT"
check:
name: Typecheck & Test
name: ${{ matrix.package }} / ${{ matrix.script }}
needs: workspaces
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
matrix:
package: ${{ fromJson(needs.workspaces.outputs.packages) }}
include: ${{ fromJson(needs.workspaces.outputs.checks) }}
fail-fast: false # report all failures, not just the first one
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@ -47,5 +66,4 @@ jobs:
- uses: ./.github/actions/retry
with:
command: npm ci
- run: npm run --prefix ${{ matrix.package }} check
- run: npm run --prefix ${{ matrix.package }} fix
- run: npm run --prefix ${{ matrix.package }} ${{ matrix.script }}

View file

@ -13,7 +13,7 @@
"tauri:build": "tauri build",
"tauri:build:debug": "tauri build --debug",
"typecheck": "tsc -p . --noEmit",
"check": "npm run typecheck",
"check": "npm run typecheck && npm run lint",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"fix": "npm run lint:fix"

View file

@ -54,7 +54,11 @@
"test:desktop:platforms": "vitest run --project electron",
"test": "vitest run",
"preview": "node scripts/assert-root-install.mjs && vite preview --host 127.0.0.1 --port 4174",
"check": "npm run typecheck && npm run test && npm run test:desktop:all",
"check:test:desktop:platforms": "npm run test:desktop:platforms",
"check:test:ui": "npm run test:ui",
"check:test:desktop:all": "npm run test:desktop:all",
"check:lint": "npm run typecheck && npm run lint",
"check": "npm run check:lint && npm run check:test:ui && npm run test:desktop:platforms && npm run test:desktop:all",
"test:e2e": "npm run build && playwright test e2e/",
"test:e2e:visual": "npm run build && WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list",
"test:e2e:update-snapshots": "npm run build && WLR_BACKENDS=headless WLR_NO_HARDWARE_CURSORS=1 cage -- npx playwright test e2e/ --reporter=list --update-snapshots"

View file

@ -16,7 +16,7 @@
"lint:fix": "eslint src/ --fix",
"fix": "npm run lint:fix",
"typecheck": "tsc -p . --noEmit",
"check": "npm run typecheck"
"check": "npm run typecheck && npm run lint"
},
"devDependencies": {
"@eslint/js": "^9.39.4",

View file

@ -5,7 +5,7 @@
"scripts": {
"typecheck": "tsc --noEmit -p tsconfig.json",
"test": "vitest run",
"check": "npm run typecheck && npm run test",
"check": "npm run typecheck && npm run test && npm run lint",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"fix": "npm run lint:fix"

View file

@ -14,7 +14,7 @@
"lint:fix": "eslint src/ packages/ --fix",
"fmt": "prettier --write 'src/**/*.{ts,tsx}' 'packages/**/*.{ts,tsx}'",
"fix": "npm run lint:fix && npm run fmt",
"check": "npm run build:ink && npm run typecheck && npm run test",
"check": "npm run build:ink && npm run typecheck && npm run test && npm run lint",
"test": "vitest run",
"test:watch": "vitest"
},

View file

@ -12,7 +12,7 @@
"preview": "vite preview",
"typecheck": "tsc -p . --noEmit",
"test": "vitest run",
"check": "npm run typecheck && npm run test"
"check": "npm run typecheck && npm run test && npm run lint"
},
"dependencies": {
"@hermes/shared": "file:../apps/shared",