feat(native): add prebuilt Node-API flock support

This commit is contained in:
imccyu
2026-09-08 20:49:10 +08:00
parent 7264906f99
commit d927cbff99
93 changed files with 1904 additions and 551 deletions
+6 -3
View File
@@ -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)}`);
}
+82 -76
View File
@@ -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}`)
+3
View File
@@ -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]) {
+60 -26
View File
@@ -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 };
}
+4 -1
View File
@@ -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);
+34 -23
View File
@@ -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 });
}
+1 -1
View File
@@ -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`).
*/