From cddc47e295b4f3ec5747717cce068b0dab5b581c Mon Sep 17 00:00:00 2001 From: Alperen Date: Wed, 20 May 2026 10:10:13 +0300 Subject: [PATCH] =?UTF-8?q?chore(ci):=20modernize=20Node=20matrix=20?= =?UTF-8?q?=E2=80=94=20drop=20EOL=20versions,=20target=20Active=20LTS=20+?= =?UTF-8?q?=20Current?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sprint 175 PR #16 CI dogfood surfaced 230+ test failures across the entire matrix. Root cause was not Sprint 175 work — it was systemic CI rot. ## Why now | Node | Status (May 2026) | We were testing? | |------|---------------------|---------------------| | v18 (Hydrogen) | EOL Apr 2025 — 1+ year dead | ❌ Yes (wasted) | | v20 (Iron) | EOL Mar 2026 — 2 months dead | ❌ Yes (wasted) | | v22 (Jod) | Maintenance EOL May 2026 — this month | ❌ Yes (wasted) | | **v24 (Krypton)** | **Active LTS (until May 2027) — production target** | ❌ NO (untested) | | v26 | Current (latest) | ❌ NO (untested) | The matrix burned three slots on dead-or-dying releases while never touching Active LTS or Current. better-sqlite3 v12.10.0 (released May 12, 2026) explicitly removed Node 20/23 prebuilds and added Node 26 — so the old matrix was guaranteed to fail post-12.10.0 anyway. ## Changes - All 7 workflows (`ci.yml`, `dashboard-build.yml`, `cross-platform-e2e.yml`, `docs.yml`, `publish.yml`, `release.yml`, `secret-scan.yml`): - Matrix `[18.x, 20.x, 22.x]` → `[24.x, 26.x]` (test-core, test-orchestra, test-cli, test-remaining, dashboard build) - Fixed `'22.x'` / `'20'` → `'24.x'` / `'24'` (typecheck, security, test-docs-scripts, test-dashboard, test-windows, coverage, build, docs, publish, release, secret-scan, cross-platform-e2e) - Dashboard artifact upload condition: `matrix.node-version == '22.x'` → `'24.x'` so the LTS run ships the artifact. - `package.json`: - `engines.node`: `>=18.0.0` → `>=24.0.0`. Breaking-change-by-design for OSS GA — Node 18/20/22 users get a clear error instead of a runtime crash later (npm shows `engineStrict` warning at install time). - `better-sqlite3`: `^12.9.0` → `^12.10.0` (Node 26 prebuilds, Node 20/23 cleanup). Verified locally: 12.10.0 ships prebuilds for the new matrix; no native compile required at install time. - New script `ci:rebuild-native: npm rebuild better-sqlite3 --ignore- scripts=false`. Required because `.npmrc` sets `ignore-scripts=true` (Sprint 167 H3 supply-chain hardening); `npm ci` honours that and skips better-sqlite3's `prebuild-install || node-gyp rebuild`, so the native binding never lands without an explicit rebuild step. Targeted to the one package that needs it — the .npmrc guard for every other dependency stays intact. - All 7 workflows: new step `npm run ci:rebuild-native` after `npm ci`. Five-second cost per job; without it the binding is missing and 200+ tests collapse on `Database` instantiation. - `src/cli/entry.ts`: runtime Node guard `< 18` → `< 24`. Error message updated to mention Active LTS. - 7 test files realigned: - `tests/github/ci-workflow.test.ts`, `tests/github/workflows/ci.test.ts`, `tests/github/workflows/release.test.ts` — workflow assertions against the new matrix and fixed versions. - `tests/cli/bin-entry-validation.test.ts`, `tests/cli/npx-compat.test.ts` — engines.node and entry.ts guard assertions. - `tests/core/nervous-enabled-integration.test.ts` — `.deckent/config. json` dogfood-only assertion now skipped when the file is absent (PR #16 made the config gitignored). ## Local verification (Node 24.15.0, the current Active LTS) | Gate | Result | |------|--------| | Non-orchestra suites | 475 files, **10,625 pass**, 23 skip, **0 fail** | | Orchestra suite | 205 files, **4,416 pass**, 11 skip, **0 fail** | | Dashboard suite | 23 files, **493 pass**, **0 fail** | | `tsc --noEmit` | clean | | `npm run build` | dist/cli/{index,entry}.js, dist/mcp/server.js, dist/index.js all present with shebang | | VitePress build | complete | | `secret-baseline.mjs` | 0 unallowlisted secrets | Total: **15,534 tests pass, 0 fail**. ## Baseline note This PR is based on `docs/embedded-web-terminal-spec` (PR #16). PR #16's test/vitepress/secret-baseline fixes are inherited; once PR #16 merges to main, this PR's base becomes main automatically with no conflict. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 39 +++++++++++++------ .github/workflows/cross-platform-e2e.yml | 3 +- .github/workflows/dashboard-build.yml | 19 +++++---- .github/workflows/docs.yml | 5 ++- .github/workflows/publish.yml | 5 ++- .github/workflows/release.yml | 5 ++- .github/workflows/secret-scan.yml | 2 +- package-lock.json | 12 +++--- package.json | 5 ++- src/cli/entry.ts | 4 +- tests/cli/bin-entry-validation.test.ts | 8 ++-- tests/cli/npx-compat.test.ts | 4 +- .../core/nervous-enabled-integration.test.ts | 14 ++++--- tests/github/ci-workflow.test.ts | 2 +- tests/github/workflows/ci.test.ts | 6 +-- tests/github/workflows/release.test.ts | 4 +- 16 files changed, 86 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 994a90c89..e0ce18126 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - run: npm run lint security: @@ -26,9 +27,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: npm audit (high severity) run: npm audit --audit-level=high continue-on-error: true @@ -39,7 +41,13 @@ jobs: needs: typecheck strategy: matrix: - node-version: [18.x, 20.x, 22.x] + # Node 18/20/22 dropped — all EOL by May 2026: + # - v18 EOL Apr 2025 (1+ year dead) + # - v20 (Iron) Maintenance EOL Mar 2026 + # - v22 (Jod) Maintenance EOL May 2026 (this month) + # Active LTS = v24 (Krypton, until May 2027). v26 = Current. + # better-sqlite3 v12.10.0 prebuilds: v24 + v26 (v20/v23 removed). + node-version: [24.x, 26.x] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -47,6 +55,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run core tests run: npx vitest run tests/core/ tests/agents/ timeout-minutes: 5 @@ -57,7 +66,7 @@ jobs: needs: typecheck strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [24.x, 26.x] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -65,6 +74,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run orchestra tests # Orchestra suite is large (~118 test files) — historical OOM on default # 2GB heap. Bumped to 8GB + forks pool (isolated workers, no shared state). @@ -80,7 +90,7 @@ jobs: needs: typecheck strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [24.x, 26.x] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -88,6 +98,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run CLI tests run: npx vitest run tests/cli/ timeout-minutes: 10 @@ -98,7 +109,7 @@ jobs: needs: typecheck strategy: matrix: - node-version: [18.x, 20.x, 22.x] + node-version: [24.x, 26.x] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -106,6 +117,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run remaining tests run: npx vitest run tests/mcp/ tests/api/ tests/integration/ tests/security/ tests/providers/ tests/monitor/ tests/skills/ tests/analytics/ tests/github/ timeout-minutes: 10 @@ -122,9 +134,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run docs and scripts tests run: npx vitest run tests/docs/ tests/scripts/ --pool=forks timeout-minutes: 5 @@ -137,9 +150,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Install dashboard dependencies run: npm install --prefix src/dashboard --ignore-scripts 2>/dev/null || true - name: Run dashboard tests @@ -156,9 +170,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run core tests (Windows informational) run: npx vitest run tests/core/ tests/agents/ timeout-minutes: 10 @@ -172,9 +187,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Run tests with coverage run: npm run test:coverage timeout-minutes: 20 @@ -193,9 +209,10 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - name: Install dashboard dependencies run: npm install --prefix src/dashboard --ignore-scripts 2>/dev/null || true - run: npm run build diff --git a/.github/workflows/cross-platform-e2e.yml b/.github/workflows/cross-platform-e2e.yml index feecbff40..db4221be3 100644 --- a/.github/workflows/cross-platform-e2e.yml +++ b/.github/workflows/cross-platform-e2e.yml @@ -28,10 +28,11 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' cache: npm - run: npm ci + - run: npm run ci:rebuild-native - run: npm run build diff --git a/.github/workflows/dashboard-build.yml b/.github/workflows/dashboard-build.yml index 376f3f5e0..32ed05f6f 100644 --- a/.github/workflows/dashboard-build.yml +++ b/.github/workflows/dashboard-build.yml @@ -33,12 +33,9 @@ jobs: strategy: fail-fast: false matrix: - # Node 18 dropped: Tailwind 4 @tailwindcss/oxide native binding is - # delivered as a per-platform optionalDependency, npm bug #4828 fails - # to resolve it on Node 18 in CI. Tailwind 4 officially supports - # Node 20+. engines.node remains >=18 for the library; only the - # dashboard build needs 20+. Sprint 175 PR #16 CI confirmed. - node-version: [20.x, 22.x] + # Node 18/20/22 dropped: all EOL by May 2026. Tailwind 4 + better- + # sqlite3 12.10.0 prebuilds target Node 24 (Active LTS) + 26 (Current). + node-version: [24.x, 26.x] env: ARTIFACT_SIZE_LIMIT_BYTES: 5242880 steps: @@ -54,6 +51,9 @@ jobs: - name: Install root dependencies run: npm ci + - name: Rebuild native binding (better-sqlite3) + run: npm run ci:rebuild-native + - name: Install dashboard dependencies run: npm install --prefix src/dashboard --ignore-scripts @@ -82,7 +82,7 @@ jobs: run: npx vitest run tests/dashboard/dashboard-build-smoke.test.ts --config vitest.dashboard.config.ts - name: Upload dashboard artifact - if: matrix.node-version == '22.x' + if: matrix.node-version == '24.x' uses: actions/upload-artifact@v4 with: name: dashboard-dist @@ -102,12 +102,15 @@ jobs: - name: Setup Node.js 22.x uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - name: Install root dependencies run: npm ci + - name: Rebuild native binding (better-sqlite3) + run: npm run ci:rebuild-native + - name: Install dashboard dependencies run: npm install --prefix src/dashboard --ignore-scripts diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a80418ce2..5206c9210 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,12 +33,15 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm - name: Install dependencies run: npm ci + - name: Rebuild native binding (better-sqlite3) + run: npm run ci:rebuild-native + - name: Install docs dependencies run: npm install --prefix docs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 671d2e8f4..c06d0c80f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,13 +22,16 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: npm ci + - name: Rebuild native binding (better-sqlite3) + run: npm run ci:rebuild-native + - name: Type check run: npm run lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1c5b2792..dbbb4a11c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,13 +22,16 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '22.x' + node-version: '24.x' cache: npm registry-url: 'https://registry.npmjs.org' - name: Install dependencies run: npm ci + - name: Rebuild native binding (better-sqlite3) + run: npm run ci:rebuild-native + - name: Type check (lint) run: npm run lint diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml index d0839d1b4..60517fd3e 100644 --- a/.github/workflows/secret-scan.yml +++ b/.github/workflows/secret-scan.yml @@ -16,6 +16,6 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '24' - name: Run secret scan run: node scripts/security/secret-baseline.mjs diff --git a/package-lock.json b/package-lock.json index fa14188c8..acbc682d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@modelcontextprotocol/sdk": "^1.27.1", "@noble/ed25519": "^2.3.0", "@noble/hashes": "^1.8.0", - "better-sqlite3": "^12.9.0", + "better-sqlite3": "^12.10.0", "commander": "^13.0.0", "telegraf": "^4.16.0", "ws": "^8.18.0", @@ -35,7 +35,7 @@ "vitest": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=24.0.0" }, "optionalDependencies": { "discord.js": "^14.26.3" @@ -1835,9 +1835,9 @@ "license": "MIT" }, "node_modules/better-sqlite3": { - "version": "12.9.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz", - "integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==", + "version": "12.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.10.0.tgz", + "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1845,7 +1845,7 @@ "prebuild-install": "^7.1.1" }, "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x" + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" } }, "node_modules/bindings": { diff --git a/package.json b/package.json index 351c2ec00..676ac6c99 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "lint:errors": "node scripts/check-error-handling.mjs", "lint:link": "node scripts/lint-links.mjs", "clean": "rm -rf dist", + "ci:rebuild-native": "npm rebuild better-sqlite3 --ignore-scripts=false", "validate:publish": "npx tsx scripts/validate-publish.ts", "docs:generate-cli": "npx tsx scripts/generate-cli-docs.ts", "docs:ref": "node scripts/gen-reference-docs.mjs --write", @@ -64,14 +65,14 @@ "@modelcontextprotocol/sdk": "^1.27.1", "@noble/ed25519": "^2.3.0", "@noble/hashes": "^1.8.0", - "better-sqlite3": "^12.9.0", + "better-sqlite3": "^12.10.0", "commander": "^13.0.0", "telegraf": "^4.16.0", "ws": "^8.18.0", "zod": "^3.25.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=24.0.0" }, "files": [ "dist", diff --git a/src/cli/entry.ts b/src/cli/entry.ts index af61d8e66..bcf43c332 100644 --- a/src/cli/entry.ts +++ b/src/cli/entry.ts @@ -7,9 +7,9 @@ import { killAllSessions } from '../orchestra/tmux.js'; // ─── Node Version Guard ───────────────────────────────────────────────────── const [major] = process.versions.node.split('.').map(Number); -if ((major ?? 0) < 18) { +if ((major ?? 0) < 24) { process.stderr.write( - `deckent requires Node.js >= 18. Current version: ${process.versions.node}\n`, + `deckent requires Node.js >= 24 (Active LTS). Current version: ${process.versions.node}\n`, ); process.exit(1); } diff --git a/tests/cli/bin-entry-validation.test.ts b/tests/cli/bin-entry-validation.test.ts index a8c9c1295..402db061d 100644 --- a/tests/cli/bin-entry-validation.test.ts +++ b/tests/cli/bin-entry-validation.test.ts @@ -32,9 +32,9 @@ describe('bin entry — package.json validation', () => { expect(pkg['type']).toBe('module'); }); - it('engines.node is >= 18 (matches entry.ts runtime guard)', () => { + it('engines.node is >= 24 (Active LTS — Node 18/20/22 EOL by May 2026)', () => { const engines = pkg['engines'] as Record; - expect(engines['node']).toMatch(/^>=\s*18/); + expect(engines['node']).toMatch(/^>=\s*24/); }); it('files array contains dist so bin target is published', () => { @@ -77,8 +77,8 @@ describe('bin entry — src/cli/entry.ts source validation', () => { expect(source).toContain("'SIGTERM'"); }); - it('has Node version guard requiring >= 18', () => { - expect(source).toContain('< 18'); + it('has Node version guard requiring >= 24 (Active LTS)', () => { + expect(source).toContain('< 24'); }); it('imports handleCliError from helpers/process', () => { diff --git a/tests/cli/npx-compat.test.ts b/tests/cli/npx-compat.test.ts index 65dc7cb95..43174d769 100644 --- a/tests/cli/npx-compat.test.ts +++ b/tests/cli/npx-compat.test.ts @@ -44,9 +44,9 @@ describe('npx deckent compatibility', () => { expect(pkg.type).toBe('module'); }); - it('package.json engines requires node >= 18', () => { + it('package.json engines requires node >= 24 (Active LTS)', () => { const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, 'package.json'), 'utf-8')); - expect(pkg.engines.node).toMatch(/>=\s*18/); + expect(pkg.engines.node).toMatch(/>=\s*24/); }); it('package.json files includes dist', () => { diff --git a/tests/core/nervous-enabled-integration.test.ts b/tests/core/nervous-enabled-integration.test.ts index 5ea9e84ea..94097c813 100644 --- a/tests/core/nervous-enabled-integration.test.ts +++ b/tests/core/nervous-enabled-integration.test.ts @@ -4,17 +4,21 @@ // and that new-project defaults remain safely disabled. import { describe, it, expect } from 'vitest'; -import { readFileSync } from 'node:fs'; +import { readFileSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { createDefaultConfig } from '../../src/core/config.js'; // ─── Test 1: .deckent/config.json loads with enabled=true ────────────────── describe('nervous_system enabled=true pivot (Sprint 148 T-006)', () => { - it('project config .deckent/config.json has nervous_system.enabled === true', () => { - // Load the project config directly — simulates what loadConfig() does - const projectRoot = join(process.cwd()); - const configPath = join(projectRoot, '.deckent', 'config.json'); + // PR #16 made .deckent/config.json gitignored (it's a runtime file, not + // a tracked fixture). Skip the dogfood assertion when the file is absent + // (clean checkout / CI without a deckent init). + const projectRoot = join(process.cwd()); + const configPath = join(projectRoot, '.deckent', 'config.json'); + const hasProjectConfig = existsSync(configPath); + + it.skipIf(!hasProjectConfig)('project config .deckent/config.json has nervous_system.enabled === true', () => { const raw = readFileSync(configPath, 'utf-8'); const projectConfig = JSON.parse(raw) as { nervous_system?: { enabled?: boolean; mode?: string } }; diff --git a/tests/github/ci-workflow.test.ts b/tests/github/ci-workflow.test.ts index 22ab23dcf..c5fbc3898 100644 --- a/tests/github/ci-workflow.test.ts +++ b/tests/github/ci-workflow.test.ts @@ -176,7 +176,7 @@ describe('CI Workflow (.github/workflows/ci.yml)', () => { }); it('should still use matrix strategy for node versions', () => { - expect(content).toContain('node-version: [18.x, 20.x, 22.x]'); + expect(content).toContain('node-version: [24.x, 26.x]'); }); }); diff --git a/tests/github/workflows/ci.test.ts b/tests/github/workflows/ci.test.ts index d2432b2bf..860072d2f 100644 --- a/tests/github/workflows/ci.test.ts +++ b/tests/github/workflows/ci.test.ts @@ -39,14 +39,14 @@ describe('CI Workflow (.github/workflows/ci.yml)', () => { expect(workflowContent).toContain('npm run lint') }) - it('should use Node.js 22.x', () => { - expect(workflowContent).toContain("node-version: '22.x'") + it('should use Node.js 24.x (Active LTS)', () => { + expect(workflowContent).toContain("node-version: '24.x'") }) }) describe('Test Jobs', () => { it('should test across multiple Node.js versions', () => { - expect(workflowContent).toContain('[18.x, 20.x, 22.x]') + expect(workflowContent).toContain('[24.x, 26.x]') }) it('should depend on typecheck', () => { diff --git a/tests/github/workflows/release.test.ts b/tests/github/workflows/release.test.ts index ca44c1c30..bc5a8ae7f 100644 --- a/tests/github/workflows/release.test.ts +++ b/tests/github/workflows/release.test.ts @@ -66,8 +66,8 @@ describe('Release Workflow (.github/workflows/release.yml)', () => { expect(workflowContent).toContain("uses: actions/setup-node@v4") }) - it('should specify node-version 22.x', () => { - expect(workflowContent).toMatch(/Setup Node\.js[\s\S]*?node-version: '22\.x'/) + it('should specify node-version 24.x (Active LTS)', () => { + expect(workflowContent).toMatch(/Setup Node\.js[\s\S]*?node-version: '24\.x'/) }) it('should enable npm cache', () => {