From 40e011bea3dd2807ec7a85c9790386374cc9e905 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 22 Oct 2026 17:10:48 +0000 Subject: [PATCH 1/3] fix(ci): support exact Node.js security versions --- .changeset/exact-node-security-pins.md | 7 ++++ .github/workflows/ci-build-lint-test.yml | 21 ++++++----- .github/workflows/ci-quality.yml | 19 ++++++---- .../workflows/internal-node-version-smoke.yml | 36 +++++++++++++++++++ README.md | 6 ++-- 5 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 .changeset/exact-node-security-pins.md create mode 100644 .github/workflows/internal-node-version-smoke.yml diff --git a/.changeset/exact-node-security-pins.md b/.changeset/exact-node-security-pins.md new file mode 100644 index 0000000..4536f9c --- /dev/null +++ b/.changeset/exact-node-security-pins.md @@ -0,0 +1,7 @@ +--- +"@kethalia/workflows": patch +--- + +Allow exact Node.js security versions through a string `node-version-spec` input +in the build/lint/test and quality workflows. Preserve the existing numeric +`node-version` input for callers selecting a major version. diff --git a/.github/workflows/ci-build-lint-test.yml b/.github/workflows/ci-build-lint-test.yml index 30f56cf..2eea16e 100644 --- a/.github/workflows/ci-build-lint-test.yml +++ b/.github/workflows/ci-build-lint-test.yml @@ -8,6 +8,11 @@ on: type: number required: false default: 22 + node-version-spec: + description: Exact Node.js version or range; overrides the legacy numeric node-version + type: string + required: false + default: "" build-command: description: Build command (e.g., "pnpm build") type: string @@ -86,12 +91,12 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - id: key name: Compute node_modules cache key run: | - echo "modules=${{ runner.os }}-node${{ inputs.node-version }}-modules-${{ hashFiles('**/pnpm-lock.yaml', '**/schema.prisma') }}" >> "$GITHUB_OUTPUT" + echo "modules=${{ runner.os }}-node${{ inputs.node-version-spec || inputs.node-version }}-modules-${{ hashFiles('**/pnpm-lock.yaml', '**/schema.prisma') }}" >> "$GITHUB_OUTPUT" - name: Cache node_modules id: modules-cache uses: actions/cache@v4 @@ -117,7 +122,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - name: Restore node_modules uses: actions/cache@v4 @@ -150,7 +155,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - name: Restore node_modules uses: actions/cache@v4 @@ -185,7 +190,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - name: Restore node_modules uses: actions/cache@v4 @@ -227,7 +232,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - name: Restore node_modules uses: actions/cache@v4 @@ -273,7 +278,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: pnpm - name: Restore node_modules - if: matrix.node-version == inputs.node-version + if: matrix.node-version == (inputs.node-version-spec || inputs.node-version) uses: actions/cache@v4 with: path: | @@ -297,7 +302,7 @@ jobs: - name: Run tests run: ${{ inputs.test-command }} - name: Upload coverage reports - if: matrix.node-version == inputs.node-version + if: matrix.node-version == (inputs.node-version-spec || inputs.node-version) uses: actions/upload-artifact@v4 with: name: ${{ inputs.coverage-artifact-name }} diff --git a/.github/workflows/ci-quality.yml b/.github/workflows/ci-quality.yml index 016ac54..63e5aa3 100644 --- a/.github/workflows/ci-quality.yml +++ b/.github/workflows/ci-quality.yml @@ -8,6 +8,11 @@ on: type: number required: false default: 22 + node-version-spec: + description: Exact Node.js version or range; overrides the legacy numeric node-version + type: string + required: false + default: "" verify-command: description: Package verification command (e.g., "pnpm validate:publish") type: string @@ -72,7 +77,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - run: pnpm install --frozen-lockfile - name: Download build artifacts @@ -96,7 +101,7 @@ jobs: - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm changeset status --since=origin/${{ inputs.base-branch }} @@ -111,7 +116,7 @@ jobs: steps: - uses: kethalia/workflows/.github/actions/setup-pnpm@v4.0.0 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} - run: pnpm audit --audit-level=${{ inputs.audit-level }} # ------------------------------------------------------------------------- @@ -124,7 +129,7 @@ jobs: steps: - uses: kethalia/workflows/.github/actions/setup-pnpm@v4.0.0 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} - run: ${{ inputs.sherif-command }} # ------------------------------------------------------------------------- @@ -137,7 +142,7 @@ jobs: steps: - uses: kethalia/workflows/.github/actions/setup-pnpm@v4.0.0 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} - run: ${{ inputs.knip-command }} # ------------------------------------------------------------------------- @@ -161,7 +166,7 @@ jobs: steps: - uses: kethalia/workflows/.github/actions/setup-pnpm@v4.0.0 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} - run: ${{ inputs.cspell-command }} # ------------------------------------------------------------------------- @@ -174,5 +179,5 @@ jobs: steps: - uses: kethalia/workflows/.github/actions/setup-pnpm@v4.0.0 with: - node-version: ${{ inputs.node-version }} + node-version: ${{ inputs.node-version-spec || inputs.node-version }} - run: ${{ inputs.madge-command }} diff --git a/.github/workflows/internal-node-version-smoke.yml b/.github/workflows/internal-node-version-smoke.yml new file mode 100644 index 0000000..113596c --- /dev/null +++ b/.github/workflows/internal-node-version-smoke.yml @@ -0,0 +1,36 @@ +name: Internal — Node version compatibility + +on: + pull_request: + paths: + - .github/workflows/ci-build-lint-test.yml + - .github/workflows/ci-quality.yml + - .github/workflows/internal-node-version-smoke.yml + workflow_dispatch: + +permissions: + contents: read + pull-requests: write + +jobs: + exact: + uses: ./.github/workflows/ci-build-lint-test.yml + with: + node-version: 24 + node-version-spec: '22.23.4' + build-command: node -e "require('node:assert/strict').equal(process.version, 'v22.23.4')" + artifact-paths: .github/no-build-artifact + + legacy: + uses: ./.github/workflows/ci-build-lint-test.yml + with: + node-version: 22 + build-command: node -e "require('node:assert/strict').match(process.version, /^v22\./)" + artifact-paths: .github/no-build-artifact + + quality: + uses: ./.github/workflows/ci-quality.yml + with: + node-version-spec: '22.23.4' + changeset-check: true + audit: false diff --git a/README.md b/README.md index 3ef22e2..da6026a 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,8 @@ File: [`ci-build-lint-test.yml`](.github/workflows/ci-build-lint-test.yml). Buil | Name | Type | Required | Default | Description | |---|---|---|---|---| -| `node-version` | number | false | `22` | Node.js version | +| `node-version` | number | false | `22` | Legacy major-version selector | +| `node-version-spec` | string | false | `""` | Exact version or range, e.g. `"22.23.4"`; takes precedence over `node-version`. Set `test-node-versions` separately for build/test matrix jobs. | | `build-command` | string | true | — | Build command (e.g., "pnpm build") | | `lint-command` | string | false | `""` | Lint command (e.g., "pnpm lint") | | `format-command` | string | false | `""` | Format check command (e.g., "pnpm format:check") | @@ -183,7 +184,8 @@ File: [`ci-quality.yml`](.github/workflows/ci-quality.yml). Aggregate quality ga | Name | Type | Required | Default | Description | |---|---|---|---|---| -| `node-version` | number | false | `22` | Node.js version | +| `node-version` | number | false | `22` | Legacy major-version selector | +| `node-version-spec` | string | false | `""` | Exact version or range, e.g. `"22.23.4"`; takes precedence over `node-version`. Set `test-node-versions` separately for build/test matrix jobs. | | `verify-command` | string | false | `""` | Package verification command (e.g., "pnpm validate:publish") | | `changeset-check` | boolean | false | `true` | Whether to run changeset status check on PRs | | `base-branch` | string | false | `main` | Base branch for changeset comparison (e.g., "main") | From afc74e5acfd9ebd1720ca1fa5fb9a081dc93a487 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 22 Oct 2026 17:17:41 +0000 Subject: [PATCH 2/3] fix(ci): retain coverage for major-version test matrices --- .github/fixtures/node-version-coverage.cjs | 11 +++++++++++ .github/workflows/ci-build-lint-test.yml | 5 ++++- .github/workflows/internal-node-version-smoke.yml | 4 +++- README.md | 5 +++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 .github/fixtures/node-version-coverage.cjs diff --git a/.github/fixtures/node-version-coverage.cjs b/.github/fixtures/node-version-coverage.cjs new file mode 100644 index 0000000..67dd759 --- /dev/null +++ b/.github/fixtures/node-version-coverage.cjs @@ -0,0 +1,11 @@ +// Synthetic artifacts for the workflow's upload/download contract, not project coverage. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); + +assert.match(process.version, /^v22\./); +const metric = { total: 0, covered: 0, skipped: 0, pct: 100 }; +fs.mkdirSync('coverage', { recursive: true }); +fs.writeFileSync('coverage/coverage-summary.json', JSON.stringify({ + total: { lines: metric, statements: metric, functions: metric, branches: metric }, +})); +fs.writeFileSync('coverage/coverage-final.json', '{}'); diff --git a/.github/workflows/ci-build-lint-test.yml b/.github/workflows/ci-build-lint-test.yml index 2eea16e..1a60223 100644 --- a/.github/workflows/ci-build-lint-test.yml +++ b/.github/workflows/ci-build-lint-test.yml @@ -302,7 +302,10 @@ jobs: - name: Run tests run: ${{ inputs.test-command }} - name: Upload coverage reports - if: matrix.node-version == (inputs.node-version-spec || inputs.node-version) + if: >- + matrix.node-version == (inputs.node-version-spec || inputs.node-version) || + (!contains(fromJson(inputs.test-node-versions), inputs.node-version-spec || inputs.node-version) && + strategy.job-index == 0) uses: actions/upload-artifact@v4 with: name: ${{ inputs.coverage-artifact-name }} diff --git a/.github/workflows/internal-node-version-smoke.yml b/.github/workflows/internal-node-version-smoke.yml index 113596c..edbd430 100644 --- a/.github/workflows/internal-node-version-smoke.yml +++ b/.github/workflows/internal-node-version-smoke.yml @@ -19,7 +19,9 @@ jobs: node-version: 24 node-version-spec: '22.23.4' build-command: node -e "require('node:assert/strict').equal(process.version, 'v22.23.4')" - artifact-paths: .github/no-build-artifact + test-command: node .github/fixtures/node-version-coverage.cjs + test-node-versions: '[22]' + artifact-paths: package.json legacy: uses: ./.github/workflows/ci-build-lint-test.yml diff --git a/README.md b/README.md index da6026a..f5cf8db 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,11 @@ jobs: File: [`ci-build-lint-test.yml`](.github/workflows/ci-build-lint-test.yml). Build → lint → format → typecheck → test (matrix) pipeline with optional incremental build cache and artifact upload. +Coverage comes from the matrix entry matching the effective setup version. If +the matrix uses different selectors (for example `[22]` with an exact setup pin +`22.23.4`), its first entry publishes coverage instead. Node modules are restored +from the prepare cache only for an exact matching matrix entry. + | Name | Type | Required | Default | Description | |---|---|---|---|---| | `node-version` | number | false | `22` | Legacy major-version selector | From 308b2ca0ac2ae4b499cdf9883e4b21910e4c8822 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 22 Oct 2026 17:21:39 +0000 Subject: [PATCH 3/3] test(ci): run compatibility checks when the coverage fixture changes --- .github/workflows/internal-node-version-smoke.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/internal-node-version-smoke.yml b/.github/workflows/internal-node-version-smoke.yml index edbd430..25b66c6 100644 --- a/.github/workflows/internal-node-version-smoke.yml +++ b/.github/workflows/internal-node-version-smoke.yml @@ -3,6 +3,7 @@ name: Internal — Node version compatibility on: pull_request: paths: + - .github/fixtures/node-version-coverage.cjs - .github/workflows/ci-build-lint-test.yml - .github/workflows/ci-quality.yml - .github/workflows/internal-node-version-smoke.yml