mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
build(ssh): enforce exact relay runtime closure
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -107,6 +107,8 @@ jobs:
|
||||
node --check config/scripts/ssh-relay-runtime-reproducibility.test.mjs
|
||||
node --check config/scripts/build-ssh-relay-runtime.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-build.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-closure.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-closure.test.mjs
|
||||
pnpm exec vitest run --config config/vitest.config.ts \
|
||||
config/scripts/ssh-relay-node-release-verification.test.mjs \
|
||||
config/scripts/ssh-relay-node-tar-inspection.test.mjs \
|
||||
@@ -116,6 +118,7 @@ jobs:
|
||||
config/scripts/ssh-relay-node-zip-inspection.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-artifact.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-build.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-closure.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs \
|
||||
@@ -279,6 +282,10 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime build script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-build.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime build test syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-closure.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime closure script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-closure.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime closure test syntax check failed' }
|
||||
pnpm exec vitest run --config config/vitest.config.ts `
|
||||
config/scripts/ssh-relay-node-release-verification.test.mjs `
|
||||
config/scripts/ssh-relay-node-tar-inspection.test.mjs `
|
||||
@@ -288,6 +295,7 @@ jobs:
|
||||
config/scripts/ssh-relay-node-zip-inspection.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-artifact.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-build.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-closure.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs `
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const NODE_VERSION = '24.18.0'
|
||||
const NODE_PTY_VERSION = '1.1.0'
|
||||
const PARCEL_WATCHER_VERSION = '2.5.6'
|
||||
|
||||
const WATCHER_PACKAGES = Object.freeze({
|
||||
'linux-x64-glibc': '@parcel/watcher-linux-x64-glibc',
|
||||
'linux-arm64-glibc': '@parcel/watcher-linux-arm64-glibc',
|
||||
'darwin-x64': '@parcel/watcher-darwin-x64',
|
||||
'darwin-arm64': '@parcel/watcher-darwin-arm64',
|
||||
'win32-x64': '@parcel/watcher-win32-x64',
|
||||
'win32-arm64': '@parcel/watcher-win32-arm64'
|
||||
})
|
||||
|
||||
const COMMON_FILES = Object.freeze([
|
||||
'.version',
|
||||
'THIRD_PARTY_LICENSES.txt',
|
||||
'node_modules/@parcel/watcher/index.js',
|
||||
'node_modules/@parcel/watcher/package.json',
|
||||
'node_modules/@parcel/watcher/wrapper.js',
|
||||
'node_modules/detect-libc/lib/detect-libc.js',
|
||||
'node_modules/detect-libc/lib/elf.js',
|
||||
'node_modules/detect-libc/lib/filesystem.js',
|
||||
'node_modules/detect-libc/lib/process.js',
|
||||
'node_modules/detect-libc/package.json',
|
||||
'node_modules/is-extglob/index.js',
|
||||
'node_modules/is-extglob/package.json',
|
||||
'node_modules/is-glob/index.js',
|
||||
'node_modules/is-glob/package.json',
|
||||
'node_modules/node-pty/lib/eventEmitter2.js',
|
||||
'node_modules/node-pty/lib/index.js',
|
||||
'node_modules/node-pty/lib/terminal.js',
|
||||
'node_modules/node-pty/lib/utils.js',
|
||||
'node_modules/node-pty/package.json',
|
||||
'node_modules/picomatch/index.js',
|
||||
'node_modules/picomatch/lib/constants.js',
|
||||
'node_modules/picomatch/lib/parse.js',
|
||||
'node_modules/picomatch/lib/picomatch.js',
|
||||
'node_modules/picomatch/lib/scan.js',
|
||||
'node_modules/picomatch/lib/utils.js',
|
||||
'node_modules/picomatch/package.json',
|
||||
'relay-watcher.js',
|
||||
'relay.js',
|
||||
'runtime-metadata.json'
|
||||
])
|
||||
|
||||
const POSIX_NODE_PTY_FILES = Object.freeze([
|
||||
'node_modules/node-pty/build/Release/pty.node',
|
||||
'node_modules/node-pty/lib/unixTerminal.js'
|
||||
])
|
||||
|
||||
const WINDOWS_NODE_PTY_FILES = Object.freeze([
|
||||
'node_modules/node-pty/build/Release/conpty.node',
|
||||
'node_modules/node-pty/build/Release/conpty/OpenConsole.exe',
|
||||
'node_modules/node-pty/build/Release/conpty/conpty.dll',
|
||||
'node_modules/node-pty/build/Release/conpty_console_list.node',
|
||||
'node_modules/node-pty/lib/conpty_console_list_agent.js',
|
||||
'node_modules/node-pty/lib/shared/conout.js',
|
||||
'node_modules/node-pty/lib/windowsConoutConnection.js',
|
||||
'node_modules/node-pty/lib/windowsPtyAgent.js',
|
||||
'node_modules/node-pty/lib/windowsTerminal.js',
|
||||
'node_modules/node-pty/lib/worker/conoutSocketWorker.js'
|
||||
])
|
||||
|
||||
export function sshRelayRuntimeWatcherPackage(tuple) {
|
||||
const packageName = WATCHER_PACKAGES[tuple]
|
||||
if (!packageName) {
|
||||
throw new Error(`No bundled Parcel watcher package is defined for ${tuple}`)
|
||||
}
|
||||
return packageName
|
||||
}
|
||||
|
||||
export function sshRelayRuntimeFileRole(path) {
|
||||
if (path === 'bin/node' || path === 'bin/node.exe') {
|
||||
return 'node'
|
||||
}
|
||||
if (path === 'relay.js') {
|
||||
return 'relay'
|
||||
}
|
||||
if (path === 'relay-watcher.js') {
|
||||
return 'relay-watcher'
|
||||
}
|
||||
if (/\/(?:pty|conpty|conpty_console_list)\.node$/.test(path)) {
|
||||
return 'node-pty-native'
|
||||
}
|
||||
if (path.endsWith('/watcher.node')) {
|
||||
return 'parcel-watcher-native'
|
||||
}
|
||||
if (
|
||||
path.endsWith('/spawn-helper') ||
|
||||
path.endsWith('/conpty/conpty.dll') ||
|
||||
path.endsWith('/conpty/OpenConsole.exe')
|
||||
) {
|
||||
return 'native-runtime'
|
||||
}
|
||||
if (path === 'THIRD_PARTY_LICENSES.txt') {
|
||||
return 'license'
|
||||
}
|
||||
return 'runtime-javascript'
|
||||
}
|
||||
|
||||
function expectedFiles(tuple) {
|
||||
const watcherPackage = sshRelayRuntimeWatcherPackage(tuple)
|
||||
const paths = [
|
||||
...COMMON_FILES,
|
||||
tuple.startsWith('win32-') ? 'bin/node.exe' : 'bin/node',
|
||||
`node_modules/${watcherPackage}/package.json`,
|
||||
`node_modules/${watcherPackage}/watcher.node`,
|
||||
...(tuple.startsWith('win32-') ? WINDOWS_NODE_PTY_FILES : POSIX_NODE_PTY_FILES)
|
||||
]
|
||||
if (tuple.startsWith('darwin-')) {
|
||||
paths.push('node_modules/node-pty/build/Release/spawn-helper')
|
||||
}
|
||||
return paths.sort().map((path) => {
|
||||
const role = sshRelayRuntimeFileRole(path)
|
||||
const mode = ['node', 'node-pty-native', 'parcel-watcher-native', 'native-runtime'].includes(
|
||||
role
|
||||
)
|
||||
? 0o755
|
||||
: 0o644
|
||||
return { path, type: 'file', role, mode }
|
||||
})
|
||||
}
|
||||
|
||||
function expectedDirectories(files) {
|
||||
const paths = new Set()
|
||||
for (const file of files) {
|
||||
const segments = file.path.split('/')
|
||||
for (let depth = 1; depth < segments.length; depth += 1) {
|
||||
paths.add(segments.slice(0, depth).join('/'))
|
||||
}
|
||||
}
|
||||
return [...paths].sort().map((path) => ({ path, type: 'directory', mode: 0o755 }))
|
||||
}
|
||||
|
||||
export function expectedSshRelayRuntimeClosureEntries(tuple) {
|
||||
const files = expectedFiles(tuple)
|
||||
return [...expectedDirectories(files), ...files]
|
||||
}
|
||||
|
||||
function indexEntries(entries, type) {
|
||||
const indexed = new Map()
|
||||
for (const entry of entries.filter((candidate) => candidate.type === type)) {
|
||||
if (indexed.has(entry.path)) {
|
||||
throw new Error(`Runtime closure contains a duplicate ${type}: ${entry.path}`)
|
||||
}
|
||||
indexed.set(entry.path, entry)
|
||||
}
|
||||
return indexed
|
||||
}
|
||||
|
||||
function assertExactEntries(actualEntries, expectedEntries, type) {
|
||||
const actual = indexEntries(actualEntries, type)
|
||||
const expected = indexEntries(expectedEntries, type)
|
||||
for (const [path, contract] of expected) {
|
||||
const entry = actual.get(path)
|
||||
if (!entry) {
|
||||
throw new Error(`Runtime closure is missing required ${type}: ${path}`)
|
||||
}
|
||||
for (const field of type === 'file' ? ['role', 'mode'] : ['mode']) {
|
||||
if (entry[field] !== contract[field]) {
|
||||
throw new Error(`Runtime closure ${type} has unexpected ${field}: ${path}`)
|
||||
}
|
||||
}
|
||||
actual.delete(path)
|
||||
}
|
||||
if (actual.size > 0) {
|
||||
throw new Error(`Runtime closure contains undeclared ${type}: ${actual.keys().next().value}`)
|
||||
}
|
||||
}
|
||||
|
||||
export function assertSshRelayRuntimeClosureEntries(identity) {
|
||||
if (
|
||||
identity.nodeVersion !== NODE_VERSION ||
|
||||
identity.dependencies?.nodePtyVersion !== NODE_PTY_VERSION ||
|
||||
identity.dependencies?.parcelWatcherVersion !== PARCEL_WATCHER_VERSION
|
||||
) {
|
||||
// Why: dependency refreshes require a reviewed desktop release and a new immutable manifest.
|
||||
throw new Error('Runtime closure dependency versions do not match the reviewed contract')
|
||||
}
|
||||
const expected = expectedSshRelayRuntimeClosureEntries(identity.tupleId)
|
||||
assertExactEntries(identity.entries, expected, 'directory')
|
||||
assertExactEntries(identity.entries, expected, 'file')
|
||||
}
|
||||
|
||||
function expectedPackages(tuple) {
|
||||
const watcherPackage = sshRelayRuntimeWatcherPackage(tuple)
|
||||
return [
|
||||
{ name: 'node-pty', version: NODE_PTY_VERSION, main: './lib/index.js', license: 'MIT' },
|
||||
{ name: '@parcel/watcher', version: PARCEL_WATCHER_VERSION, main: 'index.js', license: 'MIT' },
|
||||
{ name: watcherPackage, version: PARCEL_WATCHER_VERSION, main: 'watcher.node', license: 'MIT' },
|
||||
{ name: 'detect-libc', version: '2.1.2', main: 'lib/detect-libc.js', license: 'Apache-2.0' },
|
||||
{ name: 'is-glob', version: '4.0.3', main: 'index.js', license: 'MIT' },
|
||||
{ name: 'is-extglob', version: '2.1.1', main: 'index.js', license: 'MIT' },
|
||||
{ name: 'picomatch', version: '4.0.4', main: 'index.js', license: 'MIT' }
|
||||
]
|
||||
}
|
||||
|
||||
function assertLicenseSections(text, packageNames) {
|
||||
const matches = [...text.matchAll(/^===== ([^\r\n]+) =====$/gm)]
|
||||
const names = matches.map((match) => match[1])
|
||||
if (!isDeepStrictEqual(names, ['Node.js', ...packageNames])) {
|
||||
throw new Error('Runtime license bundle does not match the exact package closure')
|
||||
}
|
||||
for (let index = 0; index < matches.length; index += 1) {
|
||||
const bodyStart = matches[index].index + matches[index][0].length
|
||||
const bodyEnd = matches[index + 1]?.index ?? text.length
|
||||
if (text.slice(bodyStart, bodyEnd).trim().length === 0) {
|
||||
throw new Error(`Runtime license bundle has an empty section: ${matches[index][1]}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function verifySshRelayRuntimeClosure(runtimeRoot, identity) {
|
||||
assertSshRelayRuntimeClosureEntries(identity)
|
||||
const packages = expectedPackages(identity.tupleId)
|
||||
for (const expected of packages) {
|
||||
const path = join(runtimeRoot, 'node_modules', ...expected.name.split('/'), 'package.json')
|
||||
const actual = JSON.parse(await readFile(path, 'utf8'))
|
||||
if (!isDeepStrictEqual(actual, expected)) {
|
||||
throw new Error(
|
||||
`Runtime package metadata does not match the closure contract: ${expected.name}`
|
||||
)
|
||||
}
|
||||
}
|
||||
const [licenseText, versionText, metadataText] = await Promise.all([
|
||||
readFile(join(runtimeRoot, 'THIRD_PARTY_LICENSES.txt'), 'utf8'),
|
||||
readFile(join(runtimeRoot, '.version'), 'utf8'),
|
||||
readFile(join(runtimeRoot, 'runtime-metadata.json'), 'utf8')
|
||||
])
|
||||
assertLicenseSections(
|
||||
licenseText,
|
||||
packages.map((entry) => entry.name)
|
||||
)
|
||||
const metadata = JSON.parse(metadataText)
|
||||
if (
|
||||
metadata.schemaVersion !== 1 ||
|
||||
metadata.tuple !== identity.tupleId ||
|
||||
metadata.nodeVersion !== identity.nodeVersion ||
|
||||
metadata.relayBuildVersion !== versionText.trim() ||
|
||||
!isDeepStrictEqual(metadata.dependencies, {
|
||||
nodePtyVersion: NODE_PTY_VERSION,
|
||||
parcelWatcherVersion: PARCEL_WATCHER_VERSION
|
||||
})
|
||||
) {
|
||||
throw new Error('Runtime metadata does not match the exact package closure')
|
||||
}
|
||||
return { files: identity.entries.filter((entry) => entry.type === 'file').length, packages: 7 }
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
assertSshRelayRuntimeClosureEntries,
|
||||
expectedSshRelayRuntimeClosureEntries,
|
||||
verifySshRelayRuntimeClosure
|
||||
} from './ssh-relay-runtime-closure.mjs'
|
||||
|
||||
const temporaryDirectories = []
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true })))
|
||||
})
|
||||
|
||||
const watcherPackages = Object.freeze({
|
||||
'linux-x64-glibc': '@parcel/watcher-linux-x64-glibc',
|
||||
'linux-arm64-glibc': '@parcel/watcher-linux-arm64-glibc',
|
||||
'darwin-x64': '@parcel/watcher-darwin-x64',
|
||||
'darwin-arm64': '@parcel/watcher-darwin-arm64',
|
||||
'win32-x64': '@parcel/watcher-win32-x64',
|
||||
'win32-arm64': '@parcel/watcher-win32-arm64'
|
||||
})
|
||||
|
||||
function identity(tuple) {
|
||||
return {
|
||||
tupleId: tuple,
|
||||
nodeVersion: '24.18.0',
|
||||
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
|
||||
entries: expectedSshRelayRuntimeClosureEntries(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
function packageRecords(tuple) {
|
||||
return [
|
||||
{ name: 'node-pty', version: '1.1.0', main: './lib/index.js', license: 'MIT' },
|
||||
{ name: '@parcel/watcher', version: '2.5.6', main: 'index.js', license: 'MIT' },
|
||||
{ name: watcherPackages[tuple], version: '2.5.6', main: 'watcher.node', license: 'MIT' },
|
||||
{ name: 'detect-libc', version: '2.1.2', main: 'lib/detect-libc.js', license: 'Apache-2.0' },
|
||||
{ name: 'is-glob', version: '4.0.3', main: 'index.js', license: 'MIT' },
|
||||
{ name: 'is-extglob', version: '2.1.1', main: 'index.js', license: 'MIT' },
|
||||
{ name: 'picomatch', version: '4.0.4', main: 'index.js', license: 'MIT' }
|
||||
]
|
||||
}
|
||||
|
||||
async function writeClosureMetadata(tuple = 'linux-x64-glibc') {
|
||||
const runtimeRoot = await mkdtemp(join(tmpdir(), 'orca-runtime-closure-'))
|
||||
temporaryDirectories.push(runtimeRoot)
|
||||
const packages = packageRecords(tuple)
|
||||
for (const record of packages) {
|
||||
const path = join(runtimeRoot, 'node_modules', ...record.name.split('/'), 'package.json')
|
||||
await mkdir(dirname(path), { recursive: true })
|
||||
await writeFile(path, `${JSON.stringify(record, null, 2)}\n`)
|
||||
}
|
||||
await writeFile(join(runtimeRoot, '.version'), '1.4.140+123456789abc\n')
|
||||
await writeFile(
|
||||
join(runtimeRoot, 'runtime-metadata.json'),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
tuple,
|
||||
nodeVersion: '24.18.0',
|
||||
relayBuildVersion: '1.4.140+123456789abc',
|
||||
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' }
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`
|
||||
)
|
||||
const licenseNames = ['Node.js', ...packages.map((entry) => entry.name)]
|
||||
await writeFile(
|
||||
join(runtimeRoot, 'THIRD_PARTY_LICENSES.txt'),
|
||||
`${licenseNames.map((name) => `===== ${name} =====\n${name} license text\n`).join('\n')}\n`
|
||||
)
|
||||
return { runtimeRoot, packages }
|
||||
}
|
||||
|
||||
describe('SSH relay runtime exact closure', () => {
|
||||
it.each([
|
||||
['linux-x64-glibc', 34],
|
||||
['linux-arm64-glibc', 34],
|
||||
['darwin-x64', 35],
|
||||
['darwin-arm64', 35],
|
||||
['win32-x64', 42],
|
||||
['win32-arm64', 42]
|
||||
])('pins the reviewed %s runtime to %i files and one native watcher', (tuple, fileCount) => {
|
||||
const entries = expectedSshRelayRuntimeClosureEntries(tuple)
|
||||
const files = entries.filter((entry) => entry.type === 'file')
|
||||
expect(files).toHaveLength(fileCount)
|
||||
expect(files.filter((entry) => entry.role === 'parcel-watcher-native')).toEqual([
|
||||
expect.objectContaining({ path: `node_modules/${watcherPackages[tuple]}/watcher.node` })
|
||||
])
|
||||
expect(files.map((entry) => entry.path)).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringMatching(/(?:^|\/)(?:npm|npx|corepack|pnpm|yarn)(?:\.|\/|$)/i),
|
||||
expect.stringMatching(/\.(?:map|pdb|ilk|cc|cpp|c|h|hpp|o|obj)$/i)
|
||||
])
|
||||
)
|
||||
expect(() => assertSshRelayRuntimeClosureEntries(identity(tuple))).not.toThrow()
|
||||
})
|
||||
|
||||
it('rejects undeclared package managers, source maps, and native build outputs', () => {
|
||||
const fixture = identity('linux-x64-glibc')
|
||||
for (const path of [
|
||||
'node_modules/npm/bin/npm-cli.js',
|
||||
'relay.js.map',
|
||||
'node_modules/node-pty/build/Release/pty.pdb'
|
||||
]) {
|
||||
expect(() =>
|
||||
assertSshRelayRuntimeClosureEntries({
|
||||
...fixture,
|
||||
entries: [
|
||||
...fixture.entries,
|
||||
{ path, type: 'file', role: 'runtime-javascript', mode: 0o644 }
|
||||
]
|
||||
})
|
||||
).toThrow(/undeclared file/i)
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects a missing native dependency, wrong role, or unreviewed dependency refresh', () => {
|
||||
const fixture = identity('darwin-arm64')
|
||||
expect(() =>
|
||||
assertSshRelayRuntimeClosureEntries({
|
||||
...fixture,
|
||||
entries: fixture.entries.filter((entry) => !entry.path.endsWith('/watcher.node'))
|
||||
})
|
||||
).toThrow(/missing required file/i)
|
||||
expect(() =>
|
||||
assertSshRelayRuntimeClosureEntries({
|
||||
...fixture,
|
||||
entries: fixture.entries.map((entry) =>
|
||||
entry.path === 'bin/node' ? { ...entry, role: 'runtime-javascript' } : entry
|
||||
)
|
||||
})
|
||||
).toThrow(/unexpected role/i)
|
||||
expect(() =>
|
||||
assertSshRelayRuntimeClosureEntries({ ...fixture, nodeVersion: '24.19.0' })
|
||||
).toThrow(/dependency versions/i)
|
||||
})
|
||||
|
||||
it('verifies exact package metadata, runtime metadata, and non-empty license sections', async () => {
|
||||
const fixture = await writeClosureMetadata()
|
||||
await expect(
|
||||
verifySshRelayRuntimeClosure(fixture.runtimeRoot, identity('linux-x64-glibc'))
|
||||
).resolves.toEqual({ files: 34, packages: 7 })
|
||||
|
||||
const packagePath = join(fixture.runtimeRoot, 'node_modules', 'node-pty', 'package.json')
|
||||
const packageJson = JSON.parse(await readFile(packagePath, 'utf8'))
|
||||
await writeFile(packagePath, `${JSON.stringify({ ...packageJson, version: '1.1.1' })}\n`)
|
||||
await expect(
|
||||
verifySshRelayRuntimeClosure(fixture.runtimeRoot, identity('linux-x64-glibc'))
|
||||
).rejects.toThrow(/node-pty/i)
|
||||
})
|
||||
|
||||
it('rejects omitted and empty dependency license sections', async () => {
|
||||
const fixture = await writeClosureMetadata()
|
||||
const licensePath = join(fixture.runtimeRoot, 'THIRD_PARTY_LICENSES.txt')
|
||||
const licenseText = await readFile(licensePath, 'utf8')
|
||||
await writeFile(licensePath, licenseText.replace(/===== picomatch =====[\s\S]*$/, ''))
|
||||
await expect(
|
||||
verifySshRelayRuntimeClosure(fixture.runtimeRoot, identity('linux-x64-glibc'))
|
||||
).rejects.toThrow(/license bundle/i)
|
||||
|
||||
const second = await writeClosureMetadata()
|
||||
const secondLicensePath = join(second.runtimeRoot, 'THIRD_PARTY_LICENSES.txt')
|
||||
const secondText = await readFile(secondLicensePath, 'utf8')
|
||||
await writeFile(
|
||||
secondLicensePath,
|
||||
secondText.replace('===== picomatch =====\npicomatch license text', '===== picomatch =====')
|
||||
)
|
||||
await expect(
|
||||
verifySshRelayRuntimeClosure(second.runtimeRoot, identity('linux-x64-glibc'))
|
||||
).rejects.toThrow(/empty section/i)
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,11 @@ import { createRequire } from 'node:module'
|
||||
import { dirname, join, relative, sep } from 'node:path'
|
||||
|
||||
import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity.mjs'
|
||||
import {
|
||||
sshRelayRuntimeFileRole,
|
||||
sshRelayRuntimeWatcherPackage,
|
||||
verifySshRelayRuntimeClosure
|
||||
} from './ssh-relay-runtime-closure.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const MAX_FILES = 5_000
|
||||
@@ -53,15 +58,6 @@ const COMPATIBILITY = Object.freeze({
|
||||
}
|
||||
})
|
||||
|
||||
const WATCHER_PACKAGES = Object.freeze({
|
||||
'linux-x64-glibc': '@parcel/watcher-linux-x64-glibc',
|
||||
'linux-arm64-glibc': '@parcel/watcher-linux-arm64-glibc',
|
||||
'darwin-x64': '@parcel/watcher-darwin-x64',
|
||||
'darwin-arm64': '@parcel/watcher-darwin-arm64',
|
||||
'win32-x64': '@parcel/watcher-win32-x64',
|
||||
'win32-arm64': '@parcel/watcher-win32-arm64'
|
||||
})
|
||||
|
||||
function packageDirectory(name) {
|
||||
return dirname(require.resolve(`${name}/package.json`))
|
||||
}
|
||||
@@ -176,10 +172,7 @@ async function copyPackageRuntime(name, runtimeRoot, relativePaths) {
|
||||
}
|
||||
|
||||
async function copyParcelWatcher(runtimeRoot, tuple) {
|
||||
const watcherPackage = WATCHER_PACKAGES[tuple]
|
||||
if (!watcherPackage) {
|
||||
throw new Error(`No bundled Parcel watcher package is defined for ${tuple}`)
|
||||
}
|
||||
const watcherPackage = sshRelayRuntimeWatcherPackage(tuple)
|
||||
await copyPackageRuntime('@parcel/watcher', runtimeRoot, ['index.js', 'wrapper.js'])
|
||||
await copyPackageRuntime(watcherPackage, runtimeRoot, ['watcher.node'])
|
||||
await chmod(
|
||||
@@ -221,39 +214,6 @@ async function writeLicenses(runtimeRoot, nodeRoot, packages) {
|
||||
await chmod(join(runtimeRoot, 'THIRD_PARTY_LICENSES.txt'), 0o644)
|
||||
}
|
||||
|
||||
function runtimeRole(path) {
|
||||
if (path === 'bin/node' || path === 'bin/node.exe') {
|
||||
return 'node'
|
||||
}
|
||||
if (path === 'relay.js') {
|
||||
return 'relay'
|
||||
}
|
||||
if (path === 'relay-watcher.js') {
|
||||
return 'relay-watcher'
|
||||
}
|
||||
if (
|
||||
path.endsWith('/pty.node') ||
|
||||
path.endsWith('/conpty.node') ||
|
||||
path.endsWith('/conpty_console_list.node')
|
||||
) {
|
||||
return 'node-pty-native'
|
||||
}
|
||||
if (path.endsWith('/watcher.node')) {
|
||||
return 'parcel-watcher-native'
|
||||
}
|
||||
if (
|
||||
path.endsWith('/spawn-helper') ||
|
||||
path.endsWith('/conpty/conpty.dll') ||
|
||||
path.endsWith('/conpty/OpenConsole.exe')
|
||||
) {
|
||||
return 'native-runtime'
|
||||
}
|
||||
if (path === 'THIRD_PARTY_LICENSES.txt') {
|
||||
return 'license'
|
||||
}
|
||||
return 'runtime-javascript'
|
||||
}
|
||||
|
||||
function assertPortableRuntimePath(path) {
|
||||
const segments = path.split('/')
|
||||
if (
|
||||
@@ -301,7 +261,7 @@ async function collectEntries(runtimeRoot) {
|
||||
if (metadata.size > MAX_FILE_BYTES) {
|
||||
throw new Error(`Runtime file exceeds size limit: ${path}`)
|
||||
}
|
||||
const role = runtimeRole(path)
|
||||
const role = sshRelayRuntimeFileRole(path)
|
||||
// Why: NTFS has no POSIX execute bits; ZIP metadata carries the canonical runtime mode.
|
||||
const mode =
|
||||
process.platform === 'win32' &&
|
||||
@@ -392,5 +352,6 @@ export async function assembleSshRelayRuntimeTree({
|
||||
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
|
||||
entries: tree.entries
|
||||
}
|
||||
await verifySshRelayRuntimeClosure(runtimeRoot, identity)
|
||||
return { ...identity, contentId: computeSshRelayRuntimeContentId(identity), ...tree }
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(source).toContain('foreach ($output in @($firstOutput, $secondOutput))')
|
||||
expect(source).toContain('ssh-relay-runtime-reproducibility.mjs')
|
||||
expect(source).toContain('ssh-relay-runtime-reproducibility.test.mjs')
|
||||
expect(source.match(/ssh-relay-runtime-closure\.test\.mjs/g)).toHaveLength(4)
|
||||
expect(
|
||||
source.match(/node --check config\/scripts\/ssh-relay-runtime-closure\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
expect(source).toContain('ssh-relay-runtime-windows-pe-diagnostic.mjs')
|
||||
expect(source).toContain('ssh-relay-runtime-windows-pe-diagnostic.test.mjs')
|
||||
expect(source).toContain('llvm-objdump.exe')
|
||||
|
||||
Reference in New Issue
Block a user