ci: matrix over Node 22 and 24, add the bench and packaging tiers (task 0040)
The workflow pinned Node 20, which reached end-of-life in April 2026, while the manifest promised support down to it — so the entire claimed range below the single tested version went unverified and its floor was unsupported. Matrix over the two supported long-term-support majors and narrow the engine range to `^22 || ^24`, naming exactly what is tested. Narrowing is free now because nothing has been published and no tags exist. The benchmark harness tier joins every leg: it is deterministic and needs no network, and its non-default runner configuration made it easy to believe it was running when it was not. The end-to-end and packaging tiers run on the highest leg only, conditioned on a flag attached to that leg through a matrix `include` entry rather than on a version number restated at each site. The benchmark smoke tier stays out, since it would pass by skipping. `@types/node` follows the new floor; it was the last Node 20 reference in the manifest, and the typecheck runs on every leg.
This commit was merged in pull request #49.
This commit is contained in:
@@ -39,7 +39,7 @@ It ships as both an installable npm CLI and a bundled Agent Skill, so any agent
|
||||
|
||||
### Language and Runtime
|
||||
|
||||
TypeScript on Node 20+, matching the `gh-axi` reference implementation.
|
||||
TypeScript on the supported Node long-term-support majors — currently 22 and 24, as declared in the manifest's engine range and matrixed over by continuous integration.
|
||||
ESM module format.
|
||||
|
||||
### Implementation Strategy
|
||||
|
||||
@@ -24,11 +24,38 @@ The workflow keeps its container-and-npm shape and its GitHub Actions compatibil
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] The workflow runs a matrix over the two supported Node versions, and no leg runs an end-of-life Node.
|
||||
- [ ] The manifest's declared engine range names exactly the versions the matrix tests.
|
||||
- [ ] The packaging tier's engine assertion matches the narrowed range and passes.
|
||||
- [ ] The live end-to-end tier runs on the highest leg only.
|
||||
- [ ] The benchmark harness tier runs on every leg, under its own runner configuration.
|
||||
- [ ] The packaging tier runs on the highest leg only.
|
||||
- [ ] The benchmark smoke tier does not run.
|
||||
- [ ] The workflow syntax stays GitHub-Actions-compatible.
|
||||
- [x] The workflow runs a matrix over the two supported Node versions, and no leg runs an end-of-life Node.
|
||||
- [x] The manifest's declared engine range names exactly the versions the matrix tests.
|
||||
- [x] The packaging tier's engine assertion matches the narrowed range and passes.
|
||||
- [x] The live end-to-end tier runs on the highest leg only.
|
||||
- [x] The benchmark harness tier runs on every leg, under its own runner configuration.
|
||||
- [x] The packaging tier runs on the highest leg only.
|
||||
- [x] The benchmark smoke tier does not run.
|
||||
- [x] The workflow syntax stays GitHub-Actions-compatible.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The supported majors are 22 and 24 — the two current long-term-support lines, and the pair that brackets the nixpkgs default the flake already builds against (24.18.0).
|
||||
The engine range became `^22 || ^24` rather than `>=22`, so it names those two majors and nothing else; a bare floor would have re-promised the odd-numbered 23, which is itself end-of-life.
|
||||
|
||||
The single matrix job was kept rather than split into a matrixed job plus a separate single-version job for the once-only tiers.
|
||||
The acceptance criteria are phrased in terms of matrix legs, one job keeps the version list in exactly one place, and the task asked that the workflow keep its container-and-npm shape.
|
||||
The cost is that the Gitea service container starts on the Node 22 leg without the end-to-end tier consuming it — cheap next to running that tier twice, which is what the spec's rationale was actually guarding against.
|
||||
|
||||
The once-only steps condition on `matrix.highest`, a flag attached to the `24` leg through a `strategy.matrix.include` entry, rather than on `matrix.node == '24'` at each site.
|
||||
An `include` entry whose keys match an existing combination augments that leg rather than adding a new one, on both GitHub Actions and Gitea Actions, and an undefined context property is falsy on the other leg.
|
||||
|
||||
Two consequential changes beyond the criteria, both surfaced by review:
|
||||
|
||||
`@types/node` moved from `^20.19.0` to `^22.20.1`.
|
||||
It was the last Node 20 reference left in the manifest, and the typecheck runs on every leg.
|
||||
It tracks the *floor* rather than the highest leg deliberately: typings for 24 would let source compile against APIs the Node 22 leg does not have.
|
||||
|
||||
`.claude/spec/gitea-axi.md`'s "Language and Runtime" section still read "TypeScript on Node 20+", which this change falsified.
|
||||
|
||||
Verified by running the whole matrix's worth of tiers locally through `nix develop`: typecheck, the fast tier with coverage thresholds (410 tests), the benchmark harness tier under its own runner configuration (117), and the full packaging tier (7), plus `nix build .#gitea-axi` after the lockfile moved.
|
||||
The workflow was parsed with `yq` to confirm the matrix, the container expression, and the two `if:` conditions land where intended.
|
||||
The live end-to-end tier was not run locally — it needs a disposable Gitea instance, and the diff only gates it.
|
||||
|
||||
One follow-up worth flagging: `vitest.bench.config.ts` sets `passWithNoTests: true`, so if its include glob ever broke, the new benchmark step would go green while running nothing — precisely the failure mode this task cites as the reason to add the step.
|
||||
Pre-existing, and left alone here rather than widened into a test-configuration change.
|
||||
|
||||
@@ -16,7 +16,22 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20-bookworm
|
||||
container: node:${{ matrix.node }}-bookworm
|
||||
|
||||
strategy:
|
||||
# Every leg's result is wanted: a failure on one Node major says nothing
|
||||
# about the other, and cancelling the sibling would hide half the answer.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The supported Node majors, matching the manifest's declared engine
|
||||
# range. Node 20 is end-of-life and deliberately absent.
|
||||
node: ["22", "24"]
|
||||
# Augments the highest leg with a flag, so the steps that run once
|
||||
# share one named condition rather than each restating a version
|
||||
# number. Keep this entry's `node` matching the last element above.
|
||||
include:
|
||||
- node: "24"
|
||||
highest: true
|
||||
|
||||
services:
|
||||
gitea:
|
||||
@@ -50,5 +65,20 @@ jobs:
|
||||
- name: Unit and integration tiers (with coverage thresholds)
|
||||
run: npm run test:coverage
|
||||
|
||||
# Deterministic, and needs neither network nor the agent SDK, so it runs
|
||||
# everywhere. Its non-default runner configuration makes it an easy tier
|
||||
# to believe is running when it is not.
|
||||
- name: Benchmark harness tier
|
||||
run: npm run test:bench
|
||||
|
||||
# Exercises the Gitea API contract rather than Node-version behaviour, so
|
||||
# one leg is enough.
|
||||
- name: End-to-end tier
|
||||
if: matrix.highest
|
||||
run: npm run test:e2e
|
||||
|
||||
# Slow, and near enough version-independent — but the only automated guard
|
||||
# on the distribution artifact, since publishing is a manual command.
|
||||
- name: Packaging tier
|
||||
if: matrix.highest
|
||||
run: npm run test:pack
|
||||
|
||||
10
package-lock.json
generated
10
package-lock.json
generated
@@ -17,14 +17,14 @@
|
||||
"gitea-axi": "dist/main.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.0",
|
||||
"@types/node": "^22.20.1",
|
||||
"@vitest/coverage-v8": "^3.2.7",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^5.8.0",
|
||||
"vitest": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": "^22 || ^24"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": ">=0.3.0"
|
||||
@@ -1050,9 +1050,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
|
||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||
"version": "22.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
|
||||
"integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": "^22 || ^24"
|
||||
},
|
||||
"bin": {
|
||||
"gitea-axi": "dist/main.js"
|
||||
@@ -47,7 +47,7 @@
|
||||
"gitea-js": "^1.23.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.0",
|
||||
"@types/node": "^22.20.1",
|
||||
"@vitest/coverage-v8": "^3.2.7",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^5.8.0",
|
||||
|
||||
@@ -63,8 +63,13 @@ describe("npm distribution artifact", () => {
|
||||
|
||||
expect(packedManifest.license).toBe("MIT");
|
||||
|
||||
// The declared range names exactly the majors continuous integration
|
||||
// matrixes over, so nothing is promised that is never tested. Node 20 is
|
||||
// end-of-life and deliberately no longer named.
|
||||
const engines = packedManifest.engines as Record<string, string> | undefined;
|
||||
expect(engines?.node).toMatch(/20/);
|
||||
expect(engines?.node).toMatch(/22/);
|
||||
expect(engines?.node).toMatch(/24/);
|
||||
expect(engines?.node).not.toMatch(/20/);
|
||||
|
||||
expect(packedManifest.type).toBe("module");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user