mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-08 14:29:29 +08:00
497 lines
20 KiB
JavaScript
497 lines
20 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs/promises');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const {
|
|
prestartBackend,
|
|
dependencyFailure,
|
|
probeLimits,
|
|
repairDependencies,
|
|
cleanSqliteBuildArtifacts,
|
|
} = require('../../cli/src/internal/maintenance/upgradePrestart.cjs');
|
|
|
|
for (const exitCode of [0, 1])
|
|
test(`prestart exit ${exitCode} reaps children retaining output pipes before removing its workspace`, async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-probe-exit-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
statics = path.join(root, 'static'),
|
|
survivor = path.join(root, 'survivor');
|
|
await fs.mkdir(path.join(source, 'node_modules'), { recursive: true });
|
|
await fs.mkdir(path.join(statics, 'build/bootstrap'), { recursive: true });
|
|
await fs.writeFile(path.join(source, 'version.yaml'), 'version: 2.23.0');
|
|
await fs.writeFile(path.join(statics, 'build/app.js'), 'backend');
|
|
await fs.writeFile(
|
|
path.join(statics, 'build-info.json'),
|
|
JSON.stringify({ sourceCommit: 'a'.repeat(40) }),
|
|
);
|
|
await fs.writeFile(
|
|
path.join(statics, 'build/bootstrap/upgradeProbe.js'),
|
|
`
|
|
const fs=require('node:fs'),crypto=require('node:crypto');
|
|
require('node:child_process').spawn(process.execPath,['-e',${JSON.stringify(
|
|
`setTimeout(()=>require('node:fs').writeFileSync(${JSON.stringify(
|
|
survivor,
|
|
)},'escaped'),600);`,
|
|
)}],{stdio:'inherit'});
|
|
process.send({type:'upgrade-probe',ok:${
|
|
exitCode === 0
|
|
},message:'simulated prestart failure',build:{sourceCommit:'a'.repeat(40),version:'2.23.0',backendSha256:crypto.createHash('sha256').update('backend').digest('hex')}},()=>process.exit(${exitCode}));
|
|
`,
|
|
);
|
|
const operation = prestartBackend(
|
|
{ source, static: statics },
|
|
{ target: { arch: 'armv6' }, limits: { heapMb: 128, timeoutMs: 3000 } },
|
|
);
|
|
if (exitCode) await assert.rejects(operation, /simulated prestart failure/);
|
|
else await operation;
|
|
assert.deepEqual((await fs.readdir(root)).sort(), ['source', 'static']);
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
await assert.rejects(fs.access(survivor), { code: 'ENOENT' });
|
|
});
|
|
|
|
for (const phase of ['install', 'rebuild'])
|
|
test(`dependency repair ${phase} failure preserves original dependencies, cleans workspace and uses configured proxy`, async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-repair-failure-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
bin = path.join(root, 'bin');
|
|
await fs.mkdir(path.join(source, 'node_modules'), { recursive: true });
|
|
await fs.mkdir(bin);
|
|
await fs.writeFile(
|
|
path.join(source, 'package.json'),
|
|
JSON.stringify({
|
|
packageManager: 'pnpm@8.3.1',
|
|
dependencies: { sqlite3: 'legacy-fixture' },
|
|
}),
|
|
);
|
|
await fs.writeFile(path.join(source, 'pnpm-lock.yaml'), 'frozen lock');
|
|
await fs.writeFile(path.join(source, 'node_modules/old'), 'preserved');
|
|
const proxy = path.join(root, 'network.json');
|
|
await fs.writeFile(
|
|
path.join(bin, 'pnpm'),
|
|
`#!${process.execPath}
|
|
const fs=require('node:fs');
|
|
if(process.argv[2]==='--version') {console.log('8.3.1');process.exit(0);}
|
|
fs.writeFileSync(${JSON.stringify(
|
|
proxy,
|
|
)},JSON.stringify({https:process.env.HTTPS_PROXY,no:process.env.no_proxy,options:process.env.NODE_OPTIONS}));
|
|
fs.mkdirSync('node_modules',{recursive:true});fs.writeFileSync('node_modules/partial','partial');
|
|
if(process.argv[2]===${JSON.stringify(
|
|
phase,
|
|
)}) {console.error('simulated repair failure');process.exit(12);}
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
await assert.rejects(
|
|
repairDependencies(source, {
|
|
target: { arch: 'armv6' },
|
|
env: {
|
|
PATH: bin,
|
|
HTTPS_PROXY: 'http://127.0.0.1:7890',
|
|
no_proxy: 'localhost,127.0.0.1',
|
|
NODE_OPTIONS: '--require untrusted',
|
|
},
|
|
}),
|
|
/simulated repair failure/,
|
|
);
|
|
assert.equal(
|
|
await fs.readFile(path.join(source, 'node_modules/old'), 'utf8'),
|
|
'preserved',
|
|
);
|
|
assert.deepEqual(await fs.readdir(path.join(source, 'node_modules')), [
|
|
'old',
|
|
]);
|
|
assert.deepEqual((await fs.readdir(root)).sort(), [
|
|
'bin',
|
|
'network.json',
|
|
'source',
|
|
]);
|
|
const network = JSON.parse(await fs.readFile(proxy));
|
|
assert.equal(network.https, 'http://127.0.0.1:7890');
|
|
assert.equal(network.no, 'localhost,127.0.0.1');
|
|
assert.doesNotMatch(network.options, /untrusted/);
|
|
});
|
|
|
|
test('cancelled dependency repair kills descendants and retains the original tree', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-repair-cancel-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
bin = path.join(root, 'bin'),
|
|
started = path.join(root, 'started'),
|
|
survivor = path.join(root, 'survivor');
|
|
await fs.mkdir(path.join(source, 'node_modules'), { recursive: true });
|
|
await fs.mkdir(bin);
|
|
await fs.writeFile(
|
|
path.join(source, 'package.json'),
|
|
JSON.stringify({
|
|
packageManager: 'pnpm@8.3.1',
|
|
dependencies: { sqlite3: 'legacy-fixture' },
|
|
}),
|
|
);
|
|
await fs.writeFile(path.join(source, 'pnpm-lock.yaml'), 'frozen lock');
|
|
await fs.writeFile(path.join(source, 'node_modules/old'), 'preserved');
|
|
await fs.writeFile(
|
|
path.join(bin, 'pnpm'),
|
|
`#!${process.execPath}
|
|
if(process.argv[2]==='--version'){console.log('8.3.1');process.exit(0);}
|
|
require('node:child_process').spawn(process.execPath,['-e',${JSON.stringify(
|
|
`process.on('SIGTERM',()=>{});require('node:fs').writeFileSync(${JSON.stringify(
|
|
started,
|
|
)},'started');setTimeout(()=>require('node:fs').writeFileSync(${JSON.stringify(
|
|
survivor,
|
|
)},'escaped'),1400);`,
|
|
)}],{stdio:'inherit'});
|
|
setInterval(()=>{},1000);
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
const controller = new AbortController();
|
|
const operation = repairDependencies(source, {
|
|
target: { arch: 'armv6' },
|
|
env: { PATH: bin },
|
|
signal: controller.signal,
|
|
});
|
|
const rejection = assert.rejects(operation, /cancel repair/);
|
|
let seen = false;
|
|
for (let i = 0; i < 150; i++) {
|
|
if (await fs.stat(started).catch(() => null)) {
|
|
seen = true;
|
|
break;
|
|
}
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
}
|
|
controller.abort(new Error('cancel repair'));
|
|
await rejection;
|
|
assert.ok(seen);
|
|
assert.equal(
|
|
await fs.readFile(path.join(source, 'node_modules/old'), 'utf8'),
|
|
'preserved',
|
|
);
|
|
assert.deepEqual((await fs.readdir(root)).sort(), [
|
|
'bin',
|
|
'source',
|
|
'started',
|
|
]);
|
|
await new Promise((r) => setTimeout(r, 1500));
|
|
await assert.rejects(fs.access(survivor), { code: 'ENOENT' });
|
|
});
|
|
|
|
test('real Node resolution failures distinguish broken dependency entries and internal imports from backend source', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-missing-entry-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const directory = path.join(root, 'node_modules/broken-entry');
|
|
await fs.mkdir(directory, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(directory, 'package.json'),
|
|
JSON.stringify({ main: 'missing.js' }),
|
|
);
|
|
const load = require('node:module').createRequire(
|
|
path.join(root, 'package.json'),
|
|
);
|
|
assert.throws(
|
|
() => load('broken-entry'),
|
|
(error) => dependencyFailure(error),
|
|
);
|
|
await fs.writeFile(
|
|
path.join(directory, 'missing.js'),
|
|
"require('./missing-internal-file')",
|
|
);
|
|
assert.throws(
|
|
() => load('broken-entry'),
|
|
(error) => dependencyFailure(error),
|
|
);
|
|
await fs.writeFile(
|
|
path.join(root, 'api.js'),
|
|
"require('./missing-api-route')",
|
|
);
|
|
assert.throws(
|
|
() => load('./api.js'),
|
|
(error) => !dependencyFailure(error),
|
|
);
|
|
});
|
|
|
|
test('ARMv6 repair isolates configuration, uses frozen install, one compiler job and interpreted Node', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-repair-armv6-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
bin = path.join(root, 'bin');
|
|
await fs.mkdir(source);
|
|
await fs.mkdir(bin);
|
|
await fs.writeFile(
|
|
path.join(source, 'package.json'),
|
|
JSON.stringify({
|
|
packageManager: 'pnpm@8.3.1',
|
|
dependencies: { sqlite3: 'legacy-fixture' },
|
|
}),
|
|
);
|
|
await fs.writeFile(path.join(source, 'pnpm-lock.yaml'), 'frozen lock');
|
|
await fs.writeFile(
|
|
path.join(bin, 'pnpm'),
|
|
`#!${process.execPath}
|
|
const fs=require('node:fs');
|
|
if(process.argv[2]==='--version') {console.error('harmless runtime warning'); console.log('8.3.1');}
|
|
else if(process.argv[2]==='install') {
|
|
if(!process.argv.includes('--frozen-lockfile')||!process.argv.includes('--prod')) throw Error('not frozen production install');
|
|
fs.mkdirSync('node_modules');fs.writeFileSync('node_modules/flags.json',JSON.stringify({
|
|
args:process.argv,options:process.env.NODE_OPTIONS,source:process.env.npm_config_build_from_source,
|
|
processConfig:process.env.npm_config_force_process_config,jobs:process.env.npm_config_jobs,make:process.env.MAKEFLAGS
|
|
}));
|
|
} else if(process.argv.slice(2).join(' ')!=='rebuild sqlite3 @whyour/sqlite3') throw Error('unexpected command');
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
await repairDependencies(source, {
|
|
target: { arch: 'armv6' },
|
|
env: {
|
|
PATH: bin,
|
|
NODE_OPTIONS: '--require untrusted',
|
|
npm_config_nodedir: '/wrong-node',
|
|
},
|
|
});
|
|
const flags = JSON.parse(
|
|
await fs.readFile(path.join(source, 'node_modules/flags.json')),
|
|
);
|
|
assert.match(flags.options, /--jitless/);
|
|
assert.match(flags.options, /max-old-space-size=256/);
|
|
assert.equal(flags.source, 'true');
|
|
assert.equal(flags.processConfig, 'true');
|
|
assert.equal(flags.jobs, '1');
|
|
assert.equal(flags.make, '-j1');
|
|
assert.deepEqual((await fs.readdir(root)).sort(), ['bin', 'source']);
|
|
});
|
|
|
|
test('successful legacy repair publishes only portable runtime files from a pnpm alias', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-repair-portable-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
bin = path.join(root, 'bin');
|
|
const outside = path.join(root, 'outside');
|
|
await fs.mkdir(path.join(source, 'node_modules'), { recursive: true });
|
|
await fs.mkdir(bin);
|
|
await fs.mkdir(outside);
|
|
await fs.writeFile(path.join(outside, 'keep'), 'unrelated file');
|
|
await fs.writeFile(
|
|
path.join(source, 'node_modules/old'),
|
|
'previous dependency',
|
|
);
|
|
await fs.writeFile(
|
|
path.join(source, 'package.json'),
|
|
JSON.stringify({
|
|
packageManager: 'pnpm@8.3.1',
|
|
dependencies: { sqlite3: 'npm:@whyour/sqlite3@1.1.2' },
|
|
}),
|
|
);
|
|
await fs.writeFile(path.join(source, 'pnpm-lock.yaml'), 'frozen lock');
|
|
await fs.writeFile(
|
|
path.join(bin, 'pnpm'),
|
|
`#!${process.execPath}
|
|
const fs = require('node:fs');
|
|
if (process.argv[2] === '--version') { console.log('8.3.1'); process.exit(0); }
|
|
const pkg = 'node_modules/.pnpm/@whyour+sqlite3@1.1.2/node_modules/@whyour/sqlite3';
|
|
if (process.argv[2] === 'install') {
|
|
fs.mkdirSync(pkg + '/lib/binding/napi-v6-linux-arm', {recursive:true});
|
|
fs.writeFileSync(pkg + '/package.json', JSON.stringify({binary:{module_path:'./lib/binding/napi-v{napi_build_version}-{platform}-{arch}'}}));
|
|
fs.symlinkSync('.pnpm/@whyour+sqlite3@1.1.2/node_modules/@whyour/sqlite3', 'node_modules/sqlite3');
|
|
} else if (process.argv[2] === 'rebuild') {
|
|
fs.writeFileSync(pkg + '/lib/binding/napi-v6-linux-arm/node_sqlite3.node', 'compiled addon');
|
|
for (const name of ['build', 'build-tmp-napi-v6']) {
|
|
fs.mkdirSync(pkg + '/' + name + '/node_gyp_bins', {recursive:true});
|
|
fs.symlinkSync('/usr/bin/python3', pkg + '/' + name + '/node_gyp_bins/python3');
|
|
}
|
|
fs.symlinkSync(${JSON.stringify(outside)}, pkg + '/build-tmp-napi-v3');
|
|
} else { throw Error('unexpected command'); }
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
await repairDependencies(source, {
|
|
target: { arch: 'armv6' },
|
|
env: { PATH: bin },
|
|
});
|
|
const sqlite = path.join(source, 'node_modules/sqlite3');
|
|
assert.equal(
|
|
await fs.readFile(
|
|
path.join(sqlite, 'lib/binding/napi-v6-linux-arm/node_sqlite3.node'),
|
|
'utf8',
|
|
),
|
|
'compiled addon',
|
|
);
|
|
for (const name of ['build', 'build-tmp-napi-v3', 'build-tmp-napi-v6'])
|
|
await assert.rejects(fs.lstat(path.join(sqlite, name)), { code: 'ENOENT' });
|
|
assert.equal(
|
|
await fs.readFile(path.join(outside, 'keep'), 'utf8'),
|
|
'unrelated file',
|
|
);
|
|
await assert.rejects(fs.access(path.join(source, 'node_modules/old')), {
|
|
code: 'ENOENT',
|
|
});
|
|
const {
|
|
inventory,
|
|
} = require('../../cli/src/internal/maintenance/platformBundle.cjs');
|
|
await inventory({ source });
|
|
});
|
|
|
|
test('cleanup retains sqlite3 runtimes loaded from build/Release and never follows a build symlink', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-sqlite-release-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const sqlite = path.join(root, 'sqlite3'), outside = path.join(root, 'outside');
|
|
await fs.mkdir(path.join(sqlite, 'build/Release'), { recursive: true });
|
|
await fs.mkdir(path.join(sqlite, 'build/node_gyp_bins'));
|
|
await fs.mkdir(path.join(outside, 'node_gyp_bins'), { recursive: true });
|
|
await fs.writeFile(path.join(outside, 'node_gyp_bins/keep'), 'untouched');
|
|
await fs.writeFile(path.join(sqlite, 'package.json'), JSON.stringify({ name: 'sqlite3', binary: { napi_versions: [3, 6] } }));
|
|
await fs.writeFile(path.join(sqlite, 'build/Release/node_sqlite3.node'), 'runtime addon');
|
|
await fs.symlink('/usr/bin/python3', path.join(sqlite, 'build/node_gyp_bins/python3'));
|
|
await fs.symlink(outside, path.join(sqlite, 'build-tmp-napi-v3'));
|
|
await cleanSqliteBuildArtifacts(sqlite);
|
|
assert.equal(await fs.readFile(path.join(sqlite, 'build/Release/node_sqlite3.node'), 'utf8'), 'runtime addon');
|
|
await assert.rejects(fs.access(path.join(sqlite, 'build/node_gyp_bins')), { code: 'ENOENT' });
|
|
await assert.rejects(fs.lstat(path.join(sqlite, 'build-tmp-napi-v3')), { code: 'ENOENT' });
|
|
assert.equal(await fs.readFile(path.join(outside, 'node_gyp_bins/keep'), 'utf8'), 'untouched');
|
|
await require('../../cli/src/internal/maintenance/platformBundle.cjs').inventory({ source: sqlite });
|
|
});
|
|
|
|
test('repair classification excludes application, platform, config, database, OOM and timeouts', () => {
|
|
for (const error of [
|
|
{ code: 'MODULE_NOT_FOUND', message: "Cannot find module './api/missing'" },
|
|
{ message: 'Bundle platform mismatch' },
|
|
{ message: 'SQLITE_CANTOPEN' },
|
|
{ message: 'Upgrade prestart timed out' },
|
|
{
|
|
code: 'ERR_DLOPEN_FAILED',
|
|
message: 'node_modules/sqlite3/node.node',
|
|
signal: 'SIGKILL',
|
|
},
|
|
{ message: 'JavaScript heap out of memory' },
|
|
])
|
|
assert.equal(dependencyFailure(error), false, JSON.stringify(error));
|
|
assert.equal(
|
|
dependencyFailure({
|
|
code: 'MODULE_NOT_FOUND',
|
|
message: "Cannot find module 'decimal.js'",
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
dependencyFailure({
|
|
code: 'ERR_DLOPEN_FAILED',
|
|
message: 'dlopen /tmp/node_modules/sqlite3/node.node: invalid ELF header',
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
dependencyFailure({
|
|
message:
|
|
'Could not locate the bindings file /tmp/node_modules/sqlite3/bindings',
|
|
}),
|
|
true,
|
|
);
|
|
assert.deepEqual(probeLimits({ arch: 'armv6' }), {
|
|
heapMb: 128,
|
|
timeoutMs: 300000,
|
|
});
|
|
});
|
|
|
|
test('prestart deadline kills its process group and removes temporary data', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-prestart-timeout-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
statics = path.join(root, 'static');
|
|
await fs.mkdir(path.join(source, 'node_modules'), { recursive: true });
|
|
await fs.writeFile(path.join(source, 'version.yaml'), 'version: 2.23.0');
|
|
await fs.mkdir(path.join(statics, 'build/bootstrap'), { recursive: true });
|
|
await fs.writeFile(path.join(statics, 'build/app.js'), 'backend');
|
|
await fs.writeFile(
|
|
path.join(statics, 'build-info.json'),
|
|
JSON.stringify({ sourceCommit: 'a'.repeat(40) }),
|
|
);
|
|
const survivor = path.join(root, 'survivor');
|
|
await fs.writeFile(
|
|
path.join(statics, 'build/bootstrap/upgradeProbe.js'),
|
|
`
|
|
require('node:child_process').spawn(process.execPath,['-e',${JSON.stringify(
|
|
`setTimeout(()=>require('node:fs').writeFileSync(${JSON.stringify(
|
|
survivor,
|
|
)},'escaped'),2200)`,
|
|
)}],{stdio:'ignore'});
|
|
setInterval(()=>{},1000);
|
|
`,
|
|
);
|
|
await assert.rejects(
|
|
prestartBackend(
|
|
{ source, static: statics },
|
|
{
|
|
target: { arch: 'armv6' },
|
|
limits: { heapMb: 128, timeoutMs: 1000 },
|
|
},
|
|
),
|
|
/timed out/,
|
|
);
|
|
assert.deepEqual((await fs.readdir(root)).sort(), ['source', 'static']);
|
|
await new Promise((resolve) => setTimeout(resolve, 1600));
|
|
await assert.rejects(fs.access(survivor), { code: 'ENOENT' });
|
|
});
|
|
|
|
test('repair refuses an unsupported package manager before invoking a network command', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-repair-lock-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source');
|
|
await fs.mkdir(source);
|
|
await fs.writeFile(path.join(source, 'package.json'), '{}');
|
|
await assert.rejects(
|
|
repairDependencies(source, {
|
|
target: { arch: 'armv6' },
|
|
env: { PATH: '' },
|
|
}),
|
|
/Unsupported frozen-lockfile/,
|
|
);
|
|
assert.deepEqual(await fs.readdir(root), ['source']);
|
|
});
|
|
|
|
test('MikroORM dependency repair uses the frozen production tree without running a native rebuild', async (t) => {
|
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ql-repair-mikro-'));
|
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
|
const source = path.join(root, 'source'),
|
|
bin = path.join(root, 'bin');
|
|
await fs.mkdir(path.join(source, 'node_modules'), { recursive: true });
|
|
await fs.mkdir(bin);
|
|
await fs.writeFile(
|
|
path.join(source, 'package.json'),
|
|
JSON.stringify({
|
|
packageManager: 'pnpm@8.3.1',
|
|
dependencies: { '@mikro-orm/core': '7.2.5', '@mikro-orm/sql': '7.2.5' },
|
|
}),
|
|
);
|
|
await fs.writeFile(path.join(source, 'pnpm-lock.yaml'), 'frozen lock');
|
|
await fs.writeFile(path.join(source, 'node_modules/broken'), 'old');
|
|
await fs.writeFile(
|
|
path.join(bin, 'pnpm'),
|
|
`#!${process.execPath}
|
|
const fs=require('node:fs');
|
|
if(process.argv[2]==='--version') console.log('8.3.1');
|
|
else if(process.argv[2]==='install') {
|
|
if(!['--prod','--frozen-lockfile','--ignore-scripts'].every(x=>process.argv.includes(x))) throw Error('unsafe install');
|
|
fs.mkdirSync('node_modules');fs.writeFileSync('node_modules/repaired','new');
|
|
} else throw Error('MikroORM must not rebuild sqlite3');
|
|
`,
|
|
{ mode: 0o755 },
|
|
);
|
|
await repairDependencies(source, {
|
|
target: { arch: 'armv6' },
|
|
env: { PATH: bin },
|
|
});
|
|
assert.equal(
|
|
await fs.readFile(path.join(source, 'node_modules/repaired'), 'utf8'),
|
|
'new',
|
|
);
|
|
await assert.rejects(fs.access(path.join(source, 'node_modules/broken')), {
|
|
code: 'ENOENT',
|
|
});
|
|
assert.equal(
|
|
await fs.readFile(path.join(source, 'pnpm-lock.yaml'), 'utf8'),
|
|
'frozen lock',
|
|
);
|
|
assert.deepEqual((await fs.readdir(root)).sort(), ['bin', 'source']);
|
|
});
|