Files
orca/config/scripts/check-node-runtime-pin.test.mjs
T
OrcaWinandm4air ddd4927a0b build(orcad): server node-pty slots at glibc 2.28, plus a glibc 2.17 compat slot (#24134)
* build(orcad): build server glibc slots on glibc 2.28 and add the glibc 2.17 compat slot

Design D6: the default linux-{x64,arm64}-glibc node-pty slots now build in
manylinux_2_28 (digest-pinned) and are gated at glibc 2.28 / GLIBCXX_3.4.25
through a floor profile on verify-linux-glibc-floor.cjs; the desktop keeps
its Ubuntu 20.04 (2.31) default.

Adds the opt-in linux-x64-glibc217 compat target: NODE_RUNTIME_COMPAT_ASSETS
pins the unofficial glibc-217 Node (update/check pin scripts cover it, outside
SERVER_TARGETS), and a new CI lane builds the compat slot in manylinux2014
with static libstdc++, gates it at glibc 2.17 with no shared C++ runtime in
DT_NEEDED, and smokes it under the glibc-217 Node.

* refactor(node-runtime-pin): route compat lookups through isCompatServerTarget; keep the glibc doc's slot-name paragraph intact

---------

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

196 lines
6.8 KiB
JavaScript

import { readFileSync } from 'node:fs'
import path from 'node:path'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
import {
NODE_RUNTIME_ASSETS,
NODE_RUNTIME_PIN,
SERVER_TARGETS
} from '../../src/shared/node-runtime-pin.ts'
import {
findNodeRuntimePinProblems,
lockfileRootImporter,
main
} from './check-node-runtime-pin.mjs'
const projectDir = path.resolve(import.meta.dirname, '../..')
const HASH = 'a'.repeat(64)
function validInput() {
const pin = {
version: '24.21.0',
electron: '43.7.5',
napi: 10,
headers: { file: 'node-v24.21.0-headers.tar.gz', sha256: HASH },
windowsImportLibs: { 'win32-x64': { file: 'win-x64/node.lib', sha256: HASH } }
}
const targets = ['linux-x64-glibc', 'win32-x64']
const assets = {
'linux-x64-glibc': {
source: 'official',
archive: 'node-v24.21.0-linux-x64.tar.gz',
archiveSha256: HASH,
executableSha256: HASH,
executableSize: 1
},
'win32-x64': {
source: 'official',
archive: 'node-v24.21.0-win-x64.zip',
archiveSha256: HASH,
executableSha256: HASH,
executableSize: 1
}
}
return {
pin,
assets,
targets,
packageJson: { devDependencies: { electron: '43.7.5' }, engines: { node: '24' } },
rootImporter: {
devDependencies: {
electron: { specifier: '43.7.5', version: '43.7.5(supports-color@7.2.0)' }
}
}
}
}
describe('findNodeRuntimePinProblems', () => {
it('accepts a consistent pin', () => {
expect(findNodeRuntimePinProblems(validInput())).toEqual([])
})
it('rejects an Electron bump that the pin did not follow', () => {
const input = validInput()
input.packageJson.devDependencies.electron = '43.8.0'
input.rootImporter.devDependencies.electron.version = '43.8.0'
expect(findNodeRuntimePinProblems(input)).toEqual([
'package.json electron is 43.8.0, but NODE_RUNTIME_PIN.electron is 43.7.5',
'pnpm-lock.yaml resolves electron 43.8.0, but NODE_RUNTIME_PIN.electron is 43.7.5'
])
})
it('rejects a lockfile that resolves a different Electron than package.json', () => {
const input = validInput()
input.rootImporter.devDependencies.electron.version = '43.7.6'
expect(findNodeRuntimePinProblems(input)).toEqual([
'pnpm-lock.yaml resolves electron 43.7.6, but NODE_RUNTIME_PIN.electron is 43.7.5'
])
})
it('rejects a pin major that differs from engines.node', () => {
const input = validInput()
input.packageJson.engines.node = '>=26'
expect(findNodeRuntimePinProblems(input)).toEqual([
'NODE_RUNTIME_PIN.version 24.21.0 is not package.json engines.node major 26'
])
})
it('requires exactly one asset per server target', () => {
const input = validInput()
delete input.assets['win32-x64']
input.assets['freebsd-x64'] = input.assets['linux-x64-glibc']
expect(findNodeRuntimePinProblems(input)).toEqual([
'NODE_RUNTIME_ASSETS has no entry for win32-x64',
'NODE_RUNTIME_ASSETS has freebsd-x64, which is not in SERVER_TARGETS'
])
})
it('rejects malformed hashes, sizes, sources and stale archive names', () => {
const input = validInput()
input.pin.headers.sha256 = 'ABC'
input.assets['linux-x64-glibc'] = {
source: 'mirror',
archive: 'node-v24.20.0-linux-x64.tar.gz',
archiveSha256: HASH.toUpperCase(),
executableSha256: `${HASH}0`,
executableSize: 0
}
expect(findNodeRuntimePinProblems(input)).toEqual([
'NODE_RUNTIME_PIN.headers.sha256 is not a 64-character hex SHA-256',
'linux-x64-glibc: source must be official or unofficial, got mirror',
'linux-x64-glibc: archive node-v24.20.0-linux-x64.tar.gz is not node-v24.21.0-linux-x64.tar.gz',
'linux-x64-glibc: archiveSha256 is not a 64-character hex SHA-256',
'linux-x64-glibc: executableSha256 is not a 64-character hex SHA-256',
'linux-x64-glibc: executableSize must be a positive integer'
])
})
it('rejects a missing or mistargeted Windows node.lib', () => {
const input = validInput()
input.pin.windowsImportLibs['win32-x64'] = { file: 'win-arm64/node.lib', sha256: 'x' }
expect(findNodeRuntimePinProblems(input)).toEqual([
'NODE_RUNTIME_PIN.windowsImportLibs.win32-x64.file is not win-x64/node.lib',
'NODE_RUNTIME_PIN.windowsImportLibs.win32-x64.sha256 is not a 64-character hex SHA-256'
])
})
it('checks the compat table against its own target list', () => {
const input = validInput()
input.compatTargets = ['linux-x64-glibc217']
input.compatAssets = {
'linux-x64-glibc217': {
source: 'unofficial',
archive: 'node-v24.21.0-linux-x64.tar.gz',
archiveSha256: HASH,
executableSha256: HASH,
executableSize: 1
},
'linux-x64-glibc': input.assets['linux-x64-glibc']
}
expect(findNodeRuntimePinProblems(input)).toEqual([
'linux-x64-glibc217: archive node-v24.21.0-linux-x64.tar.gz is not node-v24.21.0-linux-x64-glibc-217.tar.gz',
'NODE_RUNTIME_COMPAT_ASSETS has linux-x64-glibc, which is not in COMPAT_SERVER_TARGETS'
])
delete input.compatAssets['linux-x64-glibc217']
expect(findNodeRuntimePinProblems(input)).toContain(
'NODE_RUNTIME_COMPAT_ASSETS has no entry for linux-x64-glibc217'
)
})
it("rejects another target's archive", () => {
const input = validInput()
input.assets['win32-x64'].archive = 'node-v24.21.0-win-arm64.zip'
expect(findNodeRuntimePinProblems(input)).toEqual([
'win32-x64: archive node-v24.21.0-win-arm64.zip is not node-v24.21.0-win-x64.zip'
])
})
})
describe('lockfileRootImporter', () => {
it('merges the root importer across pnpm 12 lockfile documents', () => {
const contents = [
'---',
"lockfileVersion: '9.0'",
'importers:',
' .:',
' packageManagerDependencies: {}',
'---',
"lockfileVersion: '9.0'",
'importers:',
' .:',
' devDependencies:',
' electron:',
' specifier: 43.7.5',
' version: 43.7.5(supports-color@7.2.0)',
''
].join('\n')
expect(lockfileRootImporter(contents).devDependencies.electron.version).toBe(
'43.7.5(supports-color@7.2.0)'
)
})
})
describe('committed pin', () => {
it('passes the repository check', () => {
expect(main(projectDir)).toBe(0)
})
it('covers every server target', () => {
expect(Object.keys(NODE_RUNTIME_ASSETS).sort()).toEqual([...SERVER_TARGETS].sort())
expect(NODE_RUNTIME_PIN.headers.file).toBe(`node-v${NODE_RUNTIME_PIN.version}-headers.tar.gz`)
})
it('runs in the static analysis job', () => {
const workflow = parse(readFileSync(path.join(projectDir, '.github/workflows/pr.yml'), 'utf8'))
const commands = workflow.jobs.static_analysis.steps.map((step) => step.run ?? '')
expect(commands).toContain('pnpm run check:node-runtime-pin')
})
})