mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
feat(native): add prebuilt Node-API flock support
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* verify the result. The Release workflow's build legs upload one
|
||||
* `prebuild-<package>` artifact per platform package (its `bin/` payload);
|
||||
* this script copies each into `packages/<package>/bin/` and then checks
|
||||
* every declared binary for presence and ELF architecture.
|
||||
* every declared binary for presence and native architecture.
|
||||
*
|
||||
* Usage: `node scripts/assemble-prebuilds.mjs <artifact-root>`.
|
||||
*/
|
||||
@@ -39,13 +39,16 @@ for (const artifactName of fs.readdirSync(artifactRoot)) {
|
||||
for (const file of fs.readdirSync(artifactDir)) {
|
||||
const source = path.join(artifactDir, file);
|
||||
const destination = path.join(root, 'packages', name, 'bin', file);
|
||||
fs.copyFileSync(source, destination);
|
||||
fs.chmodSync(destination, 0o755);
|
||||
fs.cpSync(source, destination, { recursive: true, preserveTimestamps: true });
|
||||
console.log(`Copied ${path.relative(root, source)} -> ${path.relative(root, destination)}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const dir of platformDirs()) {
|
||||
const metadata = JSON.parse(fs.readFileSync(path.join(root, dir, 'prebuilds.json'), 'utf8'));
|
||||
for (const binary of metadata.binaries) {
|
||||
if (binary.kind === 'static-musl') fs.chmodSync(path.join(root, dir, binary.path), 0o755);
|
||||
}
|
||||
const { name, count } = verifyPlatformBinaries(path.join(root, dir));
|
||||
console.log(`Verified ${name}: ${count} binaries`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/** Build the independent POSIX flock oracle used by native behavior tests. */
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
const root = fileURLToPath(new URL('..', import.meta.url));
|
||||
if (process.platform !== 'linux' && process.platform !== 'darwin') {
|
||||
throw new Error('The flock oracle is a POSIX test fixture');
|
||||
}
|
||||
const variants = process.platform === 'linux' ? ['glibc', 'musl'] : [''];
|
||||
for (const variant of variants) {
|
||||
const compiler = variant === 'musl' ? 'musl-gcc' : 'cc';
|
||||
const output = path.join(root, 'test/bin', variant, 'flock-oracle');
|
||||
fs.mkdirSync(path.dirname(output), { recursive: true });
|
||||
const args = ['-std=c11', '-O2', '-Wall', '-Wextra', '-Werror'];
|
||||
if (process.platform === 'darwin') args.push('-mmacosx-version-min=11.0');
|
||||
if (variant === 'musl') args.push('-static');
|
||||
const result = spawnSync(compiler, [...args, path.join(root, 'test/fixtures/flock-oracle.c'), '-o', output], { stdio: 'inherit' });
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) throw new Error(`${compiler} failed to build the flock oracle`);
|
||||
console.log(`Built test oracle ${path.relative(root, output)}`);
|
||||
}
|
||||
@@ -1,86 +1,92 @@
|
||||
/**
|
||||
* Build every native tool this host can build, into its per-platform
|
||||
* package.
|
||||
*
|
||||
* Targets are derived from the checked-in matrix: each
|
||||
* `packages/<name>/prebuilds.json` whose `platform` matches this host names
|
||||
* the binaries to produce; the TOOLS table below maps each `tool` to its C
|
||||
* source. Builds are NATIVE-ONLY — each Linux architecture compiles its own
|
||||
* binary with the distro's `musl-gcc` (static musl: runs on glibc and musl
|
||||
* distros alike, no loader or libc expectations on the consumer host), and
|
||||
* CI's per-arch runners are the builders of record. No cross toolchain
|
||||
* exists here on purpose: native runners replace it, and the audit surface
|
||||
* is the reviewed C source plus the CI job that built the binary.
|
||||
*
|
||||
* Binaries land in `packages/<name>/bin/` — git-ignored (root
|
||||
* `.gitignore`), packed into the platform package's npm tarball behind its
|
||||
* `prepack` gate (`scripts/verify-launcher-binary.mjs`).
|
||||
*
|
||||
* Run: `pnpm run build:native` (Linux with musl-gcc on PATH:
|
||||
* `apt-get install musl-tools`). Non-Linux hosts fail fast — no platform
|
||||
* package exists for them to build.
|
||||
* Build this host's declared system binaries. Landlock is a static musl
|
||||
* executable; flock uses stable Node-API with separate Linux libc builds.
|
||||
* Node headers come from the Node installation running this script.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync } from 'node:fs'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { parseArgs } from 'node:util'
|
||||
|
||||
/** Each native tool's C source, keyed by the `tool` field in prebuilds.json. */
|
||||
const TOOLS: Record<string, { source: string }> = {
|
||||
'landlock-run': { source: 'packages/entry/src/main.c' },
|
||||
const root = resolve(import.meta.dirname, '..')
|
||||
const { values } = parseArgs({ options: { 'host-addon-only': { type: 'boolean' } }, allowPositionals: false })
|
||||
const hostAddonOnly = values['host-addon-only'] === true
|
||||
const sources: Record<string, string> = {
|
||||
'landlock-run': 'packages/entry/src/main.c',
|
||||
flock: 'packages/entry/src/flock.c',
|
||||
}
|
||||
|
||||
const repoRoot = resolve(import.meta.dirname, '..')
|
||||
|
||||
if (process.platform !== 'linux') {
|
||||
console.error(`build: native tools are built natively per Linux architecture (no cross toolchain) — nothing to build on ${process.platform}. CI's per-arch runners build and rehearse every platform package.`)
|
||||
process.exit(1)
|
||||
}
|
||||
const hostPlatform = `linux-${process.arch}`
|
||||
|
||||
/** This host's platform packages, from the checked-in matrix. */
|
||||
const targets: { packageDir: string; tool: string; binaryPath: string; kind: string }[] = []
|
||||
const packagesRoot = join(repoRoot, 'packages')
|
||||
for (const name of readdirSync(packagesRoot).sort()) {
|
||||
const prebuildsFile = join(packagesRoot, name, 'prebuilds.json')
|
||||
if (!existsSync(prebuildsFile)) continue
|
||||
const prebuilds = JSON.parse(readFileSync(prebuildsFile, 'utf8')) as {
|
||||
platform: string
|
||||
binaries: { tool: string; kind: string; path: string }[]
|
||||
}
|
||||
if (prebuilds.platform !== hostPlatform) continue
|
||||
for (const binary of prebuilds.binaries) {
|
||||
targets.push({ packageDir: join(packagesRoot, name), tool: binary.tool, binaryPath: binary.path, kind: binary.kind })
|
||||
}
|
||||
}
|
||||
if (targets.length === 0) {
|
||||
console.error(`build: no platform package declares binaries for ${hostPlatform} — supported platforms are the packages/*/prebuilds.json "platform" values.`)
|
||||
process.exit(1)
|
||||
interface Binary {
|
||||
tool: string
|
||||
kind: string
|
||||
path: string
|
||||
napi?: number
|
||||
libc?: string
|
||||
}
|
||||
|
||||
for (const target of targets) {
|
||||
const tool = TOOLS[target.tool]
|
||||
if (tool === undefined) {
|
||||
console.error(`build: prebuilds.json names unknown tool "${target.tool}" — add it to the TOOLS table in scripts/build.ts.`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (target.kind !== 'static-musl') {
|
||||
console.error(`build: unknown binary kind "${target.kind}" — the only toolchain here is static musl.`)
|
||||
process.exit(1)
|
||||
}
|
||||
const binary = join(target.packageDir, target.binaryPath)
|
||||
mkdirSync(dirname(binary), { recursive: true })
|
||||
|
||||
// -static against musl: self-contained, no loader/libc expectations on the
|
||||
// consumer host. -Werror is safe to keep hard: CI pins the builder images,
|
||||
// and a new warning on a toolchain bump deserves a look, not a pass.
|
||||
const result = spawnSync('musl-gcc', [
|
||||
'-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s',
|
||||
'-o', binary, join(repoRoot, tool.source),
|
||||
], { stdio: ['ignore', 'inherit', 'inherit'] })
|
||||
if (result.error !== undefined || result.status !== 0) {
|
||||
console.error('build: musl-gcc failed' +
|
||||
(result.error ? ` (${result.error.message} — is musl-tools installed?)` : ''))
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`build: built ${basename(target.packageDir)}/${target.binaryPath}`)
|
||||
if (process.platform !== 'linux' && process.platform !== 'darwin') {
|
||||
if (hostAddonOnly) process.exit(0)
|
||||
throw new Error('build: system binaries are built on Linux or macOS; no native target for this host')
|
||||
}
|
||||
const host = `${process.platform}-${process.arch}`
|
||||
const libc = process.platform === 'linux'
|
||||
? ((process.report.getReport() as { header: { glibcVersionRuntime?: string } }).header.glibcVersionRuntime ? 'glibc' : 'musl')
|
||||
: undefined
|
||||
const headers = resolve(dirname(process.execPath), '../include/node')
|
||||
let built = 0
|
||||
|
||||
for (const name of readdirSync(join(root, 'packages')).sort()) {
|
||||
const dir = join(root, 'packages', name)
|
||||
const metadata = join(dir, 'prebuilds.json')
|
||||
if (!existsSync(metadata)) continue
|
||||
const spec = JSON.parse(readFileSync(metadata, 'utf8')) as { platform: string; binaries: Binary[] }
|
||||
if (spec.platform !== host) continue
|
||||
|
||||
for (const binary of spec.binaries) {
|
||||
if (hostAddonOnly && (binary.kind !== 'node-api' || (binary.libc !== undefined && binary.libc !== libc))) continue
|
||||
const source = sources[binary.tool]
|
||||
if (source === undefined) throw new Error(`build: unknown tool ${binary.tool}`)
|
||||
const output = join(dir, binary.path)
|
||||
mkdirSync(dirname(output), { recursive: true })
|
||||
let compiler: string
|
||||
let flags: string[]
|
||||
|
||||
if (binary.kind === 'static-musl' && process.platform === 'linux' && binary.tool === 'landlock-run') {
|
||||
compiler = 'musl-gcc'
|
||||
flags = ['-std=c11', '-Os', '-Wall', '-Wextra', '-Werror', '-static', '-s']
|
||||
} else if (binary.kind === 'node-api' && binary.tool === 'flock' && binary.napi === 8) {
|
||||
if (!existsSync(join(headers, 'node_api.h'))) {
|
||||
throw new Error(`build: Node-API headers missing at ${headers}; use a Node installation with development headers`)
|
||||
}
|
||||
compiler = process.platform === 'linux' && binary.libc === 'musl' ? 'musl-gcc' : 'cc'
|
||||
flags = ['-std=c11', '-O2', '-Wall', '-Wextra', '-Werror', '-fPIC', '-fvisibility=hidden', '-DNAPI_VERSION=8', '-I', headers]
|
||||
if (process.platform === 'darwin') {
|
||||
if (binary.libc !== undefined) throw new Error('build: macOS flock does not select a Linux libc')
|
||||
flags.push('-bundle', '-undefined', 'dynamic_lookup', '-mmacosx-version-min=11.0')
|
||||
} else {
|
||||
if (binary.libc !== 'glibc' && binary.libc !== 'musl') {
|
||||
throw new Error('build: Linux flock must select glibc or musl')
|
||||
}
|
||||
flags.push('-shared')
|
||||
}
|
||||
} else {
|
||||
throw new Error(`build: unsupported ${binary.tool}/${binary.kind} target on ${host}`)
|
||||
}
|
||||
|
||||
mkdirSync(join(root, '.release'), { recursive: true })
|
||||
const temporary = mkdtempSync(join(root, '.release', 'native-build-'))
|
||||
try {
|
||||
const pending = join(temporary, basename(output))
|
||||
const result = spawnSync(compiler, [...flags, '-o', pending, join(root, source)], { stdio: 'inherit' })
|
||||
if (result.error) throw result.error
|
||||
if (result.status !== 0) throw new Error(`build: ${compiler} failed for ${binary.path}`)
|
||||
// Readers never see a truncated addon when source checks build concurrently.
|
||||
renameSync(pending, output)
|
||||
} finally {
|
||||
rmSync(temporary, { recursive: true, force: true })
|
||||
}
|
||||
console.log(`build: built ${basename(dir)}/${binary.path}`)
|
||||
built++
|
||||
}
|
||||
}
|
||||
if (built === 0) throw new Error(`build: no declared binaries for ${host}`)
|
||||
|
||||
@@ -15,6 +15,8 @@ import { platformDirs, readJson, root } from './repo.mjs';
|
||||
const RUNNERS = {
|
||||
'linux-x64': 'ubuntu-24.04',
|
||||
'linux-arm64': 'ubuntu-24.04-arm',
|
||||
'darwin-x64': 'macos-15-intel',
|
||||
'darwin-arm64': 'macos-latest',
|
||||
};
|
||||
|
||||
function runnerFor(platform) {
|
||||
@@ -56,6 +58,7 @@ const target = process.argv[2];
|
||||
const matrices = {
|
||||
ci: ciMatrix,
|
||||
'release-prebuild': releasePrebuildMatrix,
|
||||
compatibility: () => ciMatrix().include.flatMap((row) => [20, 22, 24, 26].map((node) => ({ ...row, node }))),
|
||||
};
|
||||
|
||||
if (!target || !matrices[target]) {
|
||||
|
||||
@@ -44,45 +44,79 @@ export function packageDirs() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify one platform package's binaries against its `prebuilds.json`:
|
||||
* every declared binary exists, nothing undeclared sits in `bin/`, and each
|
||||
* file's ELF `e_machine` matches the package's declared `cpu`. Throws with
|
||||
* a remediation message on the first mismatch.
|
||||
* Verify platform metadata, complete bin/ payloads, executable permissions,
|
||||
* and native file formats before packing. Node addons must export Node-API.
|
||||
*/
|
||||
export function verifyPlatformBinaries(packageDir) {
|
||||
const manifest = readJson(path.join(packageDir, 'package.json'));
|
||||
const prebuilds = readJson(path.join(packageDir, 'prebuilds.json'));
|
||||
const cpu = manifest.cpu?.[0];
|
||||
if (cpu === undefined || !(cpu in E_MACHINE)) {
|
||||
throw new Error(`${manifest.name}: unsupported or missing "cpu" in package.json (expected one of: ${Object.keys(E_MACHINE).join(', ')})`);
|
||||
const os = manifest.os?.[0];
|
||||
if (!(cpu in E_MACHINE) || !['linux', 'darwin'].includes(os)) {
|
||||
throw new Error(`${manifest.name}: unsupported or missing os/cpu metadata`);
|
||||
}
|
||||
if (prebuilds.platform !== `${os}-${cpu}`) {
|
||||
throw new Error(`${manifest.name}: prebuild platform disagrees with package os/cpu`);
|
||||
}
|
||||
|
||||
const declared = new Set();
|
||||
for (const binary of prebuilds.binaries) {
|
||||
if (typeof binary.path !== 'string' || !/^bin\/(?:[a-z0-9-]+\/)?[a-z0-9._-]+$/.test(binary.path)) {
|
||||
throw new Error(`${manifest.name}: binary path must name a file inside bin/`);
|
||||
}
|
||||
if (declared.has(binary.path)) throw new Error(`${manifest.name}: duplicate binary path ${binary.path}`);
|
||||
declared.add(binary.path);
|
||||
const executable = binary.kind === 'static-musl' && binary.tool === 'landlock-run' && os === 'linux';
|
||||
const addon = binary.kind === 'node-api' && binary.tool === 'flock' && binary.napi === 8;
|
||||
if (!executable && !addon) throw new Error(`${manifest.name}: unsupported binary kind/tool/NAPI for ${binary.path}`);
|
||||
if (addon && os === 'linux' && !['glibc', 'musl'].includes(binary.libc)) {
|
||||
throw new Error(`${manifest.name}: Linux addon must declare glibc or musl`);
|
||||
}
|
||||
if (addon && os === 'darwin' && binary.libc !== undefined) {
|
||||
throw new Error(`${manifest.name}: macOS addon must not declare a Linux libc`);
|
||||
}
|
||||
|
||||
const file = path.join(packageDir, binary.path);
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error(`${manifest.name}: missing ${binary.path} — run \`pnpm build:native\` on a ${prebuilds.platform} host (or assemble release artifacts) before packing.`);
|
||||
if (!fs.existsSync(file)) throw new Error(`${manifest.name}: missing ${binary.path} — build this platform before packing`);
|
||||
if (!fs.lstatSync(file).isFile()) throw new Error(`${manifest.name}: ${binary.path} is not a regular file`);
|
||||
if (executable) {
|
||||
try { fs.accessSync(file, fs.constants.X_OK); }
|
||||
catch { throw new Error(`${manifest.name}: ${binary.path} is not executable`); }
|
||||
}
|
||||
try {
|
||||
fs.accessSync(file, fs.constants.X_OK);
|
||||
} catch {
|
||||
// Only reachable when the mode was mangled somewhere between build and
|
||||
// here (e.g. an archive step that normalized permissions) — the build
|
||||
// itself always produces 755.
|
||||
throw new Error(`${manifest.name}: ${binary.path} is not executable — a pack/extract step stripped the mode bit.`);
|
||||
const data = fs.readFileSync(file);
|
||||
if (os === 'linux') {
|
||||
if (data.length < 64 || data.readUInt32LE(0) !== 0x464c457f || data[4] !== 2 || data[5] !== 1) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} is not a little-endian ELF64 binary`);
|
||||
}
|
||||
if (data.readUInt16LE(18) !== E_MACHINE[cpu]) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} has the wrong ELF architecture`);
|
||||
}
|
||||
if (data.readUInt16LE(16) !== (executable ? 2 : 3)) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} has the wrong ELF file type`);
|
||||
}
|
||||
} else {
|
||||
const expectedCpu = cpu === 'x64' ? 0x01000007 : 0x0100000c;
|
||||
if (data.length < 32 || data.readUInt32LE(0) !== 0xfeedfacf) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} is not a Mach-O 64-bit bundle`);
|
||||
}
|
||||
if (data.readUInt32LE(4) !== expectedCpu || data.readUInt32LE(12) !== 8) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} has the wrong Mach-O architecture or file type`);
|
||||
}
|
||||
}
|
||||
const machine = fs.readFileSync(file).readUInt16LE(18);
|
||||
if (machine !== E_MACHINE[cpu]) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} has ELF e_machine ${machine}, expected ${E_MACHINE[cpu]} for ${cpu} — the binary was built for a different architecture.`);
|
||||
if (addon && (!data.includes(Buffer.from('napi_register_module_v1'))
|
||||
|| !data.includes(Buffer.from('node_api_module_get_api_version_v1')))) {
|
||||
throw new Error(`${manifest.name}: ${binary.path} does not export the Node-API entry points`);
|
||||
}
|
||||
}
|
||||
|
||||
const declared = prebuilds.binaries.map((binary) => path.basename(binary.path)).sort();
|
||||
const binDir = path.join(packageDir, 'bin');
|
||||
const actual = fs.existsSync(binDir) ? fs.readdirSync(binDir).sort() : [];
|
||||
const extra = actual.filter((name) => !declared.includes(name));
|
||||
if (extra.length) {
|
||||
throw new Error(`${manifest.name}: bin/ contains files not declared in prebuilds.json: ${extra.join(', ')}`);
|
||||
function files(dir, prefix) {
|
||||
if (!fs.existsSync(dir)) return [];
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const name = prefix + '/' + entry.name;
|
||||
return entry.isDirectory() ? files(path.join(dir, entry.name), name) : [name];
|
||||
});
|
||||
}
|
||||
|
||||
return { name: manifest.name, count: prebuilds.binaries.length };
|
||||
const extra = files(path.join(packageDir, 'bin'), 'bin').filter((name) => !declared.has(name));
|
||||
if (extra.length) throw new Error(`${manifest.name}: undeclared bin/ files: ${extra.join(', ')}`);
|
||||
return { name: manifest.name, count: declared.size };
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ import path from 'node:path';
|
||||
const packageDir = process.cwd();
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
|
||||
|
||||
for (const file of ['lib/index.js', 'lib/index.d.ts']) {
|
||||
const exportedFiles = Object.values(manifest.exports)
|
||||
.flatMap((entry) => typeof entry === 'string' ? [entry] : Object.values(entry))
|
||||
.filter((file) => typeof file === 'string' && file.startsWith('./lib/'));
|
||||
for (const file of exportedFiles) {
|
||||
if (!fs.existsSync(path.join(packageDir, file))) {
|
||||
console.error(`verify-entry-lib: ${manifest.name} has no ${file} — run \`pnpm build:ts\` before packing.`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
* `pnpm run build:native` would ship an EMPTY platform package — the
|
||||
* binary's absence surfacing only at runtime as a failed probe on every
|
||||
* consumer — and a binary copied across packages would advertise an
|
||||
* architecture it cannot execute. The check is presence + ELF `e_machine`
|
||||
* against the package's declared `cpu`. `verify-packed-install.mjs`
|
||||
* architecture it cannot execute. Checks cover ELF/Mach-O format, architecture,
|
||||
* declared payloads, and Node-API exports. `verify-packed-install.mjs`
|
||||
* separately pins the installed tarball bytes to the workspace build.
|
||||
*
|
||||
* Runs from each platform package's `prepack` hook (pnpm sets the script
|
||||
@@ -23,7 +23,7 @@ const packageDir = process.argv[2] ? path.resolve(root, process.argv[2]) : proce
|
||||
|
||||
try {
|
||||
const { name, count } = verifyPlatformBinaries(packageDir);
|
||||
console.log(`verify-launcher-binary: ${name} — ${count} binaries present with the right ELF architecture.`);
|
||||
console.log(`verify-launcher-binary: ${name} — ${count} binaries present with the right native format and architecture.`);
|
||||
} catch (error) {
|
||||
console.error(`verify-launcher-binary: ${error instanceof Error ? error.message : error}`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
* exactly what a consumer install needs. `pnpm pack` already produced the
|
||||
* bytes `pnpm publish` would upload; this script checks the payload
|
||||
* (coverage, concrete dependency versions, NO lifecycle install scripts —
|
||||
* this family has no install fallback on purpose), unpacks the entry plus
|
||||
* this family has no install fallback on purpose), installs the entry plus
|
||||
* THIS host's platform tarball into a throwaway consumer OUTSIDE the repo,
|
||||
* byte-pins the installed binary against the workspace build it was packed
|
||||
* from, and drives the INSTALLED entry under plain `node` — resolution,
|
||||
* probe, and a real confinement world-proof through the installed launcher.
|
||||
*
|
||||
* On non-Linux hosts (no platform package exists) it instead proves the
|
||||
* documented degradation: resolution falls back to a nonexistent path and
|
||||
* the probe reports `unusable`.
|
||||
* On non-Linux hosts it proves that Landlock remains unavailable, while
|
||||
* supported POSIX hosts independently exercise the flock binding.
|
||||
*
|
||||
* Usage: `node scripts/verify-packed-install.mjs [tarball-dir] [--current-platform-only]`.
|
||||
* The flag skips the all-platforms tarball-presence check for
|
||||
@@ -56,7 +55,7 @@ function run(command, commandArgs, options = {}) {
|
||||
});
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) {
|
||||
process.exit(result.status ?? 1);
|
||||
throw new Error(`${command} failed (status=${result.status}, signal=${result.signal})`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,19 +97,6 @@ function packageInstallDir(packageName) {
|
||||
return path.join(tempRoot, 'node_modules', ...packageName.split('/'));
|
||||
}
|
||||
|
||||
function unpackTarball(manifest) {
|
||||
const extractRoot = fs.mkdtempSync(path.join(tempRoot, 'extract-'));
|
||||
run('tar', ['-xzf', tarballPath(manifest), '-C', extractRoot]);
|
||||
|
||||
const source = path.join(extractRoot, 'package');
|
||||
const destination = packageInstallDir(manifest.name);
|
||||
fs.rmSync(destination, { recursive: true, force: true });
|
||||
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
||||
fs.renameSync(source, destination);
|
||||
fs.rmSync(extractRoot, { recursive: true, force: true });
|
||||
console.log(`Unpacked ${manifest.name} -> ${path.relative(tempRoot, destination)}`);
|
||||
}
|
||||
|
||||
const manifests = packageDirs().map((dir) => ({ dir, manifest: readJson(path.join(root, dir, 'package.json')) }));
|
||||
const entryManifest = manifests.find(({ manifest }) => manifest.name === entryPackageName)?.manifest;
|
||||
if (!entryManifest) throw new Error(`missing source manifest for ${entryPackageName}`);
|
||||
@@ -144,16 +130,16 @@ for (const { manifest } of expectedTarballs) {
|
||||
}
|
||||
|
||||
// Throwaway ESM consumer, built from local tarballs only — no registry.
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'nalr-packed-install-'));
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'native-system-packed-'));
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
path.join(tempRoot, 'package.json'),
|
||||
`${JSON.stringify({ name: 'nalr-packed-install-check', version: '0.0.0', private: true, type: 'module' }, null, 2)}\n`,
|
||||
`${JSON.stringify({ name: 'native-system-packed-check', version: '0.0.0', private: true, type: 'module', dependencies: Object.fromEntries([entryManifest, ...(currentPlatformEntry ? [currentPlatformEntry.manifest] : [])].map((manifest) => [manifest.name, `file:${tarballPath(manifest)}`])) }, null, 2)}\n`,
|
||||
);
|
||||
console.log(`Verifying packed install in ${tempRoot}`);
|
||||
|
||||
unpackTarball(entryManifest);
|
||||
run('npm', ['install', '--offline', '--no-audit', '--no-fund', '--package-lock=false'], { cwd: tempRoot });
|
||||
if (currentPlatformEntry) {
|
||||
unpackTarball(currentPlatformEntry.manifest);
|
||||
|
||||
// Byte-pin: the installed binary must be the workspace build it was packed
|
||||
// from — any divergence means the tarball did not carry the built bytes.
|
||||
@@ -181,6 +167,7 @@ import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-system';
|
||||
import { tryLockExclusive } from '@deepseek-ai/node-addon-system/flock';
|
||||
|
||||
const requireLandlock = process.env.NALR_REQUIRE_LANDLOCK === '1';
|
||||
const platformPackage = '@deepseek-ai/node-addon-system-' + process.platform + '-' + process.arch;
|
||||
@@ -213,11 +200,35 @@ if (process.platform === 'linux') {
|
||||
console.log('confinement world-proof passed through the installed launcher');
|
||||
}
|
||||
} else {
|
||||
assert.ok(!fs.existsSync(resolved), 'no platform package exists for this host — the fallback path must not exist');
|
||||
assert.ok(!fs.existsSync(resolved), 'Landlock has no executable for this host');
|
||||
assert.equal(probe(resolved), 'unusable');
|
||||
console.log('non-linux host: fallback resolution and unusable probe verified');
|
||||
}
|
||||
|
||||
if (process.platform === 'linux' || process.platform === 'darwin') {
|
||||
const lockRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'native-system-flock-'));
|
||||
const handles = [];
|
||||
try {
|
||||
const lock = path.join(lockRoot, 'lock');
|
||||
const a = fs.openSync(lock, 'wx+', 0o600);
|
||||
handles.push(a);
|
||||
const b = fs.openSync(lock, 'r+');
|
||||
handles.push(b);
|
||||
await tryLockExclusive(a);
|
||||
await assert.rejects(tryLockExclusive(b), { code: 'EAGAIN' });
|
||||
fs.closeSync(a);
|
||||
handles.splice(handles.indexOf(a), 1);
|
||||
await tryLockExclusive(b);
|
||||
console.log('installed Node-API flock: exclusion and close release verified');
|
||||
} finally {
|
||||
for (const fd of handles) fs.closeSync(fd);
|
||||
fs.rmSync(lockRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
`);
|
||||
run(process.execPath, [driver], { cwd: tempRoot });
|
||||
|
||||
console.log('Packed install verification passed.');
|
||||
} finally {
|
||||
fs.rmSync(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Release verification. Always: every published package carries one shared
|
||||
* version, and — when running from a tag or publishing — the
|
||||
* `node-addon-system-vX.Y.Z` tag matches it. With `--prebuilds`: every platform package's declared
|
||||
* binaries exist with the right ELF architecture (run after
|
||||
* binaries exist with the right native format and architecture (run after
|
||||
* `assemble-prebuilds.mjs` or a local `build:native`).
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user