Files
orca/config/scripts/update-node-runtime-pin.test.mjs
T
OrcaWinandm4air d2dfc79764 ci(daemon): runtime-launcher protocol ratchet and Node slot marker (#24108)
* ci(daemon): gate PRs on daemon protocol crossing from the newest release

Lands daemon-protocol-facts.mjs from the Windows update diagnostic branch with a
stricter parser, and adds check-daemon-protocol-crossing.mjs (rule R1): the working
tree must attach the newest release tag's daemon. Rollback crossing is reported only.
Runs in the cross-version-wire job, which already has full tags; tag selection moves
to config/scripts/stable-release-tags.mjs so both use one rule.

* feat(persistence): run profile backups in the worker whenever its entry is bundled

* refactor(orcad): make profile and native preflight runtime-neutral

The profile preflight parser now takes the expected runtime identity from the
caller (shipped callers pass the pinned Bun identity), and the native
preflight is renamed to orcad-runtime-native-preflight with neutral wording.

* feat(runtime): pin the Node 24.21.0 server runtime with an offline CI check

Add src/shared/node-runtime-pin.ts (NODE_RUNTIME_PIN, SERVER_TARGETS,
NODE_RUNTIME_ASSETS for all 8 server targets plus the headers tarball),
generated by config/scripts/update-node-runtime-pin.mjs from the nodejs.org
and unofficial-builds SHASUMS. check-node-runtime-pin.mjs verifies, with no
network, that the pin tracks the locked Electron, matches engines.node's
major, and covers exactly SERVER_TARGETS; it runs in the static analysis job.

ORCAD_BUN_TARGETS consumers now read SERVER_TARGETS so there is one target
list; orcad's Bun runtime and build output are unchanged.

* test(persistence): skip plain-Node backup selection tests in the Bun profile suite

* fix(runtime): reject a pinned archive that belongs to another target

* ci(daemon): fail PRs that swap a runtime launcher and bump the daemon protocol

D7.1 R3: hosting orcad or the daemon on another runtime is not a protocol change,
so one PR must not do both. The launcher file list lives in the check script; the
allow-runtime-launcher-protocol-bump label overrides it.

* feat(orcad): select pinned-Node slots by a .runtime-node marker

D7.1 R5: a Node slot names its shared runtimes/node-<sha256>/node through
.runtime-node instead of .build-target, so Bun-era clients read it as a legacy
slot rather than exiting 78 on a missing bun-runtime. Nothing builds the marker yet.

* fix(runtime): load the Node pin without the typeless-module warning

check-node-runtime-pin.mjs now requires the pin and takes nodeDistArchiveName from
its own module, so it no longer loads the update script's build graph.

* fix(orcad): resolve Node slots to the design's runtimes/node-<sha>/bin/node layout

---------

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
2026-10-01 00:10:57 -07:00

98 lines
3.3 KiB
JavaScript

import { readFileSync } from 'node:fs'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import {
NODE_RUNTIME_ASSETS,
NODE_RUNTIME_PIN,
SERVER_TARGETS,
nodeRuntimeExecutablePath
} from '../../src/shared/node-runtime-pin.ts'
import { nodeDistArchiveName } from './node-dist-archive-name.mjs'
import {
parseNodeApiVersion,
parseShasums,
renderGeneratedBlock,
replaceGeneratedBlock,
selectAssetSource
} from './update-node-runtime-pin.mjs'
const pinFile = path.resolve(import.meta.dirname, '../../src/shared/node-runtime-pin.ts')
const HASH_A = 'a'.repeat(64)
const HASH_B = 'b'.repeat(64)
describe('nodeDistArchiveName', () => {
it('uses nodejs.org platform names and zip only on Windows', () => {
expect(nodeDistArchiveName('24.21.0', 'linux-x64-glibc')).toBe('node-v24.21.0-linux-x64.tar.gz')
expect(nodeDistArchiveName('24.21.0', 'linux-arm64-musl')).toBe(
'node-v24.21.0-linux-arm64-musl.tar.gz'
)
expect(nodeDistArchiveName('24.21.0', 'win32-arm64')).toBe('node-v24.21.0-win-arm64.zip')
})
it('covers every server target', () => {
for (const target of SERVER_TARGETS) {
expect(nodeDistArchiveName('24.21.0', target)).not.toContain('undefined')
}
})
})
describe('nodeRuntimeExecutablePath', () => {
it('points at bin/node on POSIX and node.exe on Windows', () => {
expect(nodeRuntimeExecutablePath('darwin-arm64', 'node-v24.21.0-darwin-arm64.tar.gz')).toBe(
'node-v24.21.0-darwin-arm64/bin/node'
)
expect(nodeRuntimeExecutablePath('win32-x64', 'node-v24.21.0-win-x64.zip')).toBe(
'node-v24.21.0-win-x64/node.exe'
)
})
})
describe('parseShasums and selectAssetSource', () => {
const official = parseShasums(
`${HASH_A} node-v24.21.0-linux-x64-musl.tar.gz\nnot a hash line\n${HASH_A} node-v24.21.0-linux-x64.tar.gz\n`
)
const unofficial = parseShasums(
`${HASH_B} node-v24.21.0-linux-x64-musl.tar.gz\n${HASH_B} node-v24.21.0-linux-arm64-musl.tar.gz\n`
)
it('prefers the official build when both publish an archive', () => {
expect(selectAssetSource('node-v24.21.0-linux-x64-musl.tar.gz', official, unofficial)).toEqual({
source: 'official',
archiveSha256: HASH_A
})
})
it('falls back to unofficial builds and reports a missing archive as null', () => {
expect(
selectAssetSource('node-v24.21.0-linux-arm64-musl.tar.gz', official, unofficial)
).toEqual({ source: 'unofficial', archiveSha256: HASH_B })
expect(selectAssetSource('node-v24.21.0-aix-ppc64.tar.gz', official, unofficial)).toBeNull()
})
})
describe('parseNodeApiVersion', () => {
it('reads the highest supported N-API version from node_version.h', () => {
expect(
parseNodeApiVersion(
'#define NODE_API_SUPPORTED_VERSION_MAX 10\n#define NODE_API_SUPPORTED_VERSION_MIN 1\n'
)
).toBe(10)
expect(() => parseNodeApiVersion('#define NODE_MAJOR_VERSION 24')).toThrow()
})
})
describe('generated block', () => {
it('reproduces the committed table byte for byte', () => {
const source = readFileSync(pinFile, 'utf8')
const regenerated = replaceGeneratedBlock(
source,
renderGeneratedBlock(NODE_RUNTIME_PIN, NODE_RUNTIME_ASSETS)
)
expect(regenerated).toBe(source)
})
it('refuses a file without markers', () => {
expect(() => replaceGeneratedBlock('export {}\n', 'x')).toThrow(/markers/)
})
})