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/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 30f56cf..1a60223 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,10 @@ 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) || + (!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/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..25b66c6 --- /dev/null +++ b/.github/workflows/internal-node-version-smoke.yml @@ -0,0 +1,39 @@ +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 + 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')" + 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 + 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..f5cf8db 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,15 @@ 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` | 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 +189,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") |