fix: validate the Node 24.9 runtime floor

This commit is contained in:
Tianyi Cui committed 2026-10-15 23:33:49 +08:00
1 parent c6a59a1ae9
commit cfb65d3bb2
60 files changed
+256 -127

No files matched your search

+12 -3
View File
@@ -5,6 +5,7 @@ import { spawnSync } from 'node:child_process'
import { runInNewContext } from 'node:vm'
import * as yaml from 'js-yaml'
import { describe, expect, it } from 'vitest'
import { BUILD_BACKED_NODE_COMPAT_VERSION } from './run-gates.ts'
interface Step {
name?: string
@@ -54,6 +55,14 @@ function route(options: { mode?: string; author?: string; repository?: string; f
}
describe('Node compatibility self-hosted routing', () => {
it('runs the build-backed compatibility minor and declares it for installed CLI users', () => {
expect(job.strategy.matrix.include.filter(entry => String(entry.node) === BUILD_BACKED_NODE_COMPAT_VERSION)).toHaveLength(1)
for (const manifest of ['package.json', 'apps/cli/package.json']) {
const pkg = JSON.parse(readFileSync(resolve(import.meta.dirname, '..', manifest), 'utf8')) as { engines: { node: string } }
expect(pkg.engines.node).toBe(`>=${BUILD_BACKED_NODE_COMPAT_VERSION}.0`)
}
})
it('uses the Linux pool only for opted-in repository-owned PRs', () => {
expect(route()).toEqual(labels)
for (const mode of ['', 'hosted', 'unexpected']) expect(route({ mode })).toBe('ubuntu-latest')
@@ -71,7 +80,7 @@ describe('Node compatibility self-hosted routing', () => {
expect(job.if).toBe("github.event_name == 'pull_request'")
expect(job.strategy['fail-fast']).toBe(false)
expect(job.strategy.matrix.include).toEqual([
{ node: '24.0', name: 'node 24.0', runner: 'ubuntu-latest', gate_concurrency: '1' },
{ node: '24.9', name: 'node 24.9', runner: 'ubuntu-latest', gate_concurrency: '1' },
{ node: 26, name: 'node 26', runner: 'ubuntu-latest', gate_concurrency: '1' },
])
expect(job.env.DSH_GATE_CONCURRENCY).toBe('${{ matrix.gate_concurrency }}')
@@ -132,8 +141,8 @@ describe('Node compatibility self-hosted routing', () => {
const step = job.steps.find(candidate => candidate.name === 'Verify isolated Node installation')!
expect(step.if).toBe("runner.environment == 'self-hosted'")
for (const [executable, status] of [
['/runner temp/node-compat-toolcache/node/24.0.0/x64/bin/node', 0],
['/shared/toolcache/node/24.0.0/x64/bin/node', 1],
['/runner temp/node-compat-toolcache/node/24.9.0/x64/bin/node', 0],
['/shared/toolcache/node/24.9.0/x64/bin/node', 1],
['/runner temp/node-compat-toolcache-other/node', 1],
] as const) {
const child = spawnSync('bash', ['-e', '-u', '-o', 'pipefail', '-c', 'node() { printf "%s" "$TEST_EXECUTABLE"; }; ' + step.run!], {
+1 -1
View File
@@ -30,7 +30,7 @@ describe('CI workflow', () => {
expect(compatibilityLimit).toBeGreaterThan(0)
expect(compatibility.strategy).toMatchObject({
'fail-fast': false,
matrix: { include: [{ node: '24.0' }, { node: 26 }] },
matrix: { include: [{ node: '24.9' }, { node: 26 }] },
})
expect(workflowJob(ci, 'all-checks-passed').needs).toContain('node-compat')
const ordinaryJobs = Object.entries(ci.jobs)
@@ -0,0 +1,24 @@
/** Compatibility checks execute command entrypoints; importing helpers cannot detect a silent no-op. */
import { spawnSync } from 'node:child_process'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
const root = resolve(import.meta.dirname, '..')
describe('repository command entrypoints', () => {
it.each([
['run-gates.ts', ['invalid-compat-mode'], 'run-gates: expected mode'],
['build.ts', ['--scope', 'invalid-compat-scope'], 'build: --scope must be'],
] as const)('executes %s argument validation on the running Node version', (script, args, diagnostic) => {
const child = spawnSync(process.execPath, ['--import', 'tsx/esm', resolve(root, 'scripts', script), ...args], {
cwd: root,
encoding: 'utf8',
timeout: 30_000,
})
expect(child.error).toBeUndefined()
expect(child.signal).toBeNull()
expect(child.status).toBe(1)
expect(child.stderr).toContain(diagnostic)
})
})
+1 -1
View File
@@ -37,7 +37,7 @@ function fixture(options: {
version: '0.0.1',
type: 'module',
license: 'MIT',
engines: { node: '>=24.0.0' },
engines: { node: '>=24.9.0' },
sideEffects: false,
files: ['lib'],
exports: { '.': { default: options.exportPath ?? './lib/index.js' } },
+6 -5
View File
@@ -846,7 +846,7 @@ describe('Typert contract preparation', () => {
})
describe('Node compatibility graph', () => {
it.each(['24.0.0', '24.19.0', '26.0.0'])('runs all source compatibility smokes serially on Node %s', (version) => {
it.each(['24.9.0', '24.19.0', '26.0.0'])('runs all source compatibility smokes serially on Node %s', (version) => {
const subject = withNodeVersion(version, () =>
withPnpmEntrypoint(() => gatesForMode('node-compat')))
@@ -861,6 +861,7 @@ describe('Node compatibility graph', () => {
'packages/session/session-persistence-jsonl/tests/zstd.compat.spec.ts',
'apps/cli/tests/source-launch.compat.spec.ts',
'scripts/vitest-environment.compat.spec.ts',
'scripts/command-entrypoints.compat.spec.ts',
'packages/boot/app-boot/tests/profile-resolution.spec.ts',
'packages/boot/app-boot/tests/profile-resolution-service.spec.ts',
'packages/boot/app-boot/tests/profile-resolution-worker-bootstrap.spec.ts',
@@ -886,8 +887,8 @@ describe('Node compatibility graph', () => {
expect(subject.map(item => item.id)).not.toContain('cli-lazy-search-startup-smoke')
})
it.each([undefined, '1'])('builds the complete Node 24.0 artifacts once with skip-typecheck=%s', (skipTypecheck) => {
const subject = withNodeVersion('24.0.0', () =>
it.each([undefined, '1'])('builds the complete Node 24.9 artifacts once with skip-typecheck=%s', (skipTypecheck) => {
const subject = withNodeVersion('24.9.0', () =>
withEnv('DSH_NODE_COMPAT_SKIP_TYPECHECK', skipTypecheck, () =>
withPnpmEntrypoint(() => gatesForMode('node-compat'))))
@@ -902,8 +903,8 @@ describe('Node compatibility graph', () => {
})
})
it.each(['passed', 'failed'] as const)('runs the Node 24.0 lazy-search smoke only after a passed build (%s)', async (buildStatus) => {
const subject = withNodeVersion('24.0.0', () =>
it.each(['passed', 'failed'] as const)('runs the Node 24.9 lazy-search smoke only after a passed build (%s)', async (buildStatus) => {
const subject = withNodeVersion('24.9.0', () =>
withEnv('DSH_NODE_COMPAT_SKIP_TYPECHECK', '1', () =>
withPnpmEntrypoint(() => gatesForMode('node-compat'))))
let buildCompleted = false
+5 -1
View File
@@ -95,6 +95,9 @@ type ResultObserver = (result: GateResult) => void
/** Worker cap of the documentation and hygiene aggregates, locally and in CI, whose leaves each build a full ts.Program. */
const PROGRAM_MODE_CAP = 8
/** CI pins this minor in .github/workflows/ci.yml; its compatibility leg owns the built CLI smoke. */
export const BUILD_BACKED_NODE_COMPAT_VERSION = '24.9'
const root = resolve(import.meta.dirname, '..')
if (import.meta.main) {
process.exitCode = await main(process.argv.slice(2))
@@ -429,7 +432,7 @@ function nodeCompatGates(): Gate[] {
const typecheck = flagEnabled('DSH_NODE_COMPAT_SKIP_TYPECHECK')
? []
: [pnpmScript('typecheck', 'typecheck')]
if (!process.versions.node.startsWith('24.0.')) {
if (!process.versions.node.startsWith(`${BUILD_BACKED_NODE_COMPAT_VERSION}.`)) {
return [...typecheck, ...nodeCompatSmokeGates()]
}
return [
@@ -452,6 +455,7 @@ function nodeCompatSmokeGates(options: { cliSmoke?: boolean } = {}): Gate[] {
'packages/session/session-persistence-jsonl/tests/zstd.compat.spec.ts',
'apps/cli/tests/source-launch.compat.spec.ts',
'scripts/vitest-environment.compat.spec.ts',
'scripts/command-entrypoints.compat.spec.ts',
'packages/boot/app-boot/tests/profile-resolution.spec.ts',
'packages/boot/app-boot/tests/profile-resolution-service.spec.ts',
'packages/boot/app-boot/tests/profile-resolution-worker-bootstrap.spec.ts',