mirror of
https://github.com/okxlin/release-factory.git
synced 2026-11-06 08:13:59 +00:00
Remove the development-only native compiler retained through optional peers before the final runtime copy. Keep TypeScript 5, fail closed on runtime dependencies or unsafe paths, and retain the existing image security gates.
86 lines
4.5 KiB
JavaScript
86 lines
4.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {test} from 'node:test';
|
|
import {pruneNativeTypeScript} from '../openclaw-builder/image/prune-native-typescript.mjs';
|
|
|
|
function fixture(t) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openclaw-native-typescript-'));
|
|
t.after(() => fs.rmSync(root, {recursive: true, force: true}));
|
|
const write = (name, data) => {
|
|
fs.mkdirSync(path.dirname(path.join(root, name)), {recursive: true});
|
|
fs.writeFileSync(path.join(root, name), data);
|
|
};
|
|
const pkg = (name, version) => {
|
|
const entry = `node_modules/.pnpm/${name.replace('/', '+')}@${version}`;
|
|
const directory = `${entry}/node_modules/${name}`;
|
|
write(`${directory}/package.json`, JSON.stringify({name, version}));
|
|
write(`${directory}/lib/tsc`, 'compiler');
|
|
return {entry, directory};
|
|
};
|
|
const link = (name, target) => {
|
|
fs.mkdirSync(path.dirname(path.join(root, name)), {recursive: true});
|
|
fs.symlinkSync(path.relative(path.dirname(path.join(root, name)), path.join(root, target)), path.join(root, name));
|
|
};
|
|
write('package.json', JSON.stringify({devDependencies: {typescript: '7.1.0-dev.20260920.1'}}));
|
|
const native = pkg('typescript', '7.1.0-dev.20260920.1');
|
|
const x64 = pkg('@typescript/typescript-linux-x64', '7.1.0-dev.20260920.1');
|
|
const arm64 = pkg('@typescript/typescript-linux-arm64', '7.1.0-dev.20260920.1');
|
|
const legacy = pkg('typescript', '5.9.3');
|
|
const peer = pkg('cosmiconfig', '9.0.2');
|
|
const unrelated = pkg('@typescript-eslint/parser', '7.0.0');
|
|
link('node_modules/typescript', native.directory);
|
|
link('node_modules/.pnpm/node_modules/typescript', native.directory);
|
|
link('node_modules/@typescript/typescript-linux-x64', x64.directory);
|
|
link('node_modules/.pnpm/node_modules/@typescript/typescript-linux-arm64', arm64.directory);
|
|
link(`${peer.entry}/node_modules/typescript`, native.directory);
|
|
link(`${unrelated.entry}/node_modules/typescript`, legacy.directory);
|
|
write('node_modules/.bin/tsc', 'native compiler launcher');
|
|
write(`${unrelated.entry}/node_modules/.bin/tsc`, 'legacy compiler launcher');
|
|
return {root, write, native, x64, arm64, legacy, peer, unrelated};
|
|
}
|
|
|
|
test('prunes native compiler payloads, peers and launcher while retaining TypeScript 5 and unrelated packages', t => {
|
|
const f = fixture(t);
|
|
assert.equal(pruneNativeTypeScript(f.root), 3);
|
|
for (const name of [f.native.entry, f.x64.entry, f.arm64.entry, 'node_modules/typescript',
|
|
'node_modules/@typescript/typescript-linux-x64', 'node_modules/.pnpm/node_modules/@typescript/typescript-linux-arm64',
|
|
'node_modules/.pnpm/node_modules/typescript', `${f.peer.entry}/node_modules/typescript`, 'node_modules/.bin/tsc']) {
|
|
assert.equal(fs.existsSync(path.join(f.root, name)), false, name);
|
|
assert.throws(() => fs.lstatSync(path.join(f.root, name)), {code: 'ENOENT'});
|
|
}
|
|
for (const name of [f.legacy.directory, f.peer.directory, f.unrelated.directory,
|
|
`${f.unrelated.entry}/node_modules/typescript`, `${f.unrelated.entry}/node_modules/.bin/tsc`]) {
|
|
assert.ok(fs.existsSync(path.join(f.root, name)), name);
|
|
}
|
|
assert.equal(pruneNativeTypeScript(f.root), 0, 'cleanup must be idempotent');
|
|
});
|
|
|
|
test('refuses direct runtime TypeScript dependencies before removing anything', t => {
|
|
const f = fixture(t);
|
|
for (const section of ['dependencies', 'optionalDependencies']) {
|
|
f.write('package.json', JSON.stringify({[section]: {typescript: '7.1.0-dev.20260920.1'}}));
|
|
assert.throws(() => pruneNativeTypeScript(f.root), /direct runtime dependency/);
|
|
assert.ok(fs.existsSync(path.join(f.root, f.x64.directory, 'lib/tsc')));
|
|
}
|
|
});
|
|
|
|
test('rejects a compiler directory symlink before changing packages', t => {
|
|
const f = fixture(t);
|
|
const entry = path.join(f.root, f.x64.entry);
|
|
fs.renameSync(entry, path.join(f.root, 'outside-store'));
|
|
fs.symlinkSync(path.join(f.root, 'outside-store'), entry);
|
|
assert.throws(() => pruneNativeTypeScript(f.root), /store entry must not be a symlink/);
|
|
assert.ok(fs.existsSync(path.join(f.root, f.native.directory, 'lib/tsc')));
|
|
assert.ok(fs.existsSync(path.join(f.root, f.x64.directory, 'lib/tsc')));
|
|
});
|
|
|
|
test('rejects a pnpm store symlink', t => {
|
|
const f = fixture(t);
|
|
const store = path.join(f.root, 'node_modules/.pnpm');
|
|
fs.renameSync(store, path.join(f.root, 'outside-store'));
|
|
fs.symlinkSync(path.join(f.root, 'outside-store'), store);
|
|
assert.throws(() => pruneNativeTypeScript(f.root), /pnpm store must not be a symlink/);
|
|
});
|