mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
568 lines
17 KiB
JavaScript
568 lines
17 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const {
|
|
pruneRuntimeArtifact,
|
|
} = require('../../scripts/ql3-prune-runtime-artifact.cjs');
|
|
|
|
function fixture() {
|
|
const temporaryRoot = fs.mkdtempSync(
|
|
path.join(os.tmpdir(), 'ql3-runtime-artifact-pruner-'),
|
|
);
|
|
const root = path.join(temporaryRoot, 'node_modules', '@qinglong');
|
|
fs.mkdirSync(root, { recursive: true });
|
|
return {
|
|
root,
|
|
close() {
|
|
fs.rmSync(temporaryRoot, { recursive: true, force: true });
|
|
},
|
|
};
|
|
}
|
|
|
|
function writePackage(root, directory, name) {
|
|
const packageRoot = path.join(root, directory);
|
|
const dist = path.join(packageRoot, 'dist');
|
|
fs.mkdirSync(dist, { recursive: true });
|
|
const manifest = {
|
|
name,
|
|
version: '3.0.0-alpha.0',
|
|
main: 'dist/index.js',
|
|
exports: { '.': './dist/index.js' },
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(packageRoot, 'package.json'),
|
|
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
);
|
|
return { dist, manifest, packageRoot };
|
|
}
|
|
|
|
test('prunes only deployment waste while preserving runtime package semantics', () => {
|
|
const current = fixture();
|
|
try {
|
|
const runtime = writePackage(
|
|
current.root,
|
|
'runtime-core',
|
|
'@qinglong/runtime-core',
|
|
);
|
|
runtime.manifest.types = 'dist/index.d.ts';
|
|
runtime.manifest.typesVersions = {
|
|
'*': { '*': ['dist/index.d.ts'] },
|
|
};
|
|
runtime.manifest.files = ['dist/**/*.js', 'dist/**/*.d.ts'];
|
|
runtime.manifest.scripts = { build: 'tsc -p tsconfig.json' };
|
|
runtime.manifest.dependencies = { semver: '7.7.4' };
|
|
runtime.manifest.devDependencies = { typescript: '5.9.3' };
|
|
runtime.manifest.exports = {
|
|
'.': {
|
|
types: './dist/index.d.ts',
|
|
require: './dist/index.js',
|
|
default: './dist/index.js',
|
|
},
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(runtime.packageRoot, 'package.json'),
|
|
`${JSON.stringify(runtime.manifest, null, 2)}\n`,
|
|
);
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
application.manifest.bin = {
|
|
'ql3-local-application': 'dist/cli.js',
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(application.packageRoot, 'package.json'),
|
|
`${JSON.stringify(application.manifest, null, 2)}\n`,
|
|
);
|
|
const executable = path.join(application.dist, 'cli.js');
|
|
fs.writeFileSync(path.join(application.dist, 'index.js'), "'use strict';\n");
|
|
fs.writeFileSync(
|
|
executable,
|
|
"#!/usr/bin/env node\n'use strict';\n//# sourceMappingURL=cli.js.map\n",
|
|
{ mode: 0o755 },
|
|
);
|
|
const retained = path.join(runtime.dist, 'index.js');
|
|
fs.writeFileSync(
|
|
retained,
|
|
"'use strict';\n//# sourceMappingURL=not-terminal.js.map\nmodule.exports = {};\n",
|
|
);
|
|
fs.writeFileSync(path.join(runtime.dist, 'index.d.ts'), 'export {};\n');
|
|
fs.writeFileSync(path.join(runtime.dist, 'index.js.map'), '{}\n');
|
|
|
|
const report = pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: [
|
|
'@qinglong/runtime-core',
|
|
'@qinglong/local-application',
|
|
],
|
|
});
|
|
|
|
assert.deepEqual(report.development, { files: 2, bytes: 14 });
|
|
assert.equal(report.sourceMapDirectives.files, 1);
|
|
assert.equal(report.packageManifests.files, 2);
|
|
assert.equal(report.packageManifests.compactedFiles, 2);
|
|
assert.equal(report.packageManifests.compactedBytes > 0, true);
|
|
assert.equal(report.packageManifests.projectedFiles, 1);
|
|
assert.equal(report.packageManifests.projectedBytes > 0, true);
|
|
assert.equal(report.savedBytes > report.development.bytes, true);
|
|
assert.equal(fs.existsSync(path.join(runtime.dist, 'index.d.ts')), false);
|
|
assert.equal(fs.existsSync(path.join(runtime.dist, 'index.js.map')), false);
|
|
assert.equal(
|
|
fs.readFileSync(executable, 'utf8'),
|
|
"#!/usr/bin/env node\n'use strict';\n",
|
|
);
|
|
assert.equal(fs.statSync(executable).mode & 0o777, 0o755);
|
|
assert.match(fs.readFileSync(retained, 'utf8'), /not-terminal\.js\.map/);
|
|
const runtimeManifest = JSON.parse(
|
|
fs.readFileSync(path.join(runtime.packageRoot, 'package.json'), 'utf8'),
|
|
);
|
|
assert.deepEqual(runtimeManifest.exports, {
|
|
'.': {
|
|
require: './dist/index.js',
|
|
default: './dist/index.js',
|
|
},
|
|
});
|
|
assert.deepEqual(runtimeManifest.dependencies, { semver: '7.7.4' });
|
|
for (const developmentField of [
|
|
'devDependencies',
|
|
'files',
|
|
'scripts',
|
|
'types',
|
|
'typesVersions',
|
|
]) {
|
|
assert.equal(developmentField in runtimeManifest, false);
|
|
}
|
|
assert.equal(
|
|
fs.readFileSync(path.join(runtime.packageRoot, 'package.json'), 'utf8'),
|
|
`${JSON.stringify(runtimeManifest)}\n`,
|
|
);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('projects runtime exports from static imports and explicit Profile entries', () => {
|
|
const current = fixture();
|
|
try {
|
|
const runtime = writePackage(
|
|
current.root,
|
|
'runtime-core',
|
|
'@qinglong/runtime-core',
|
|
);
|
|
runtime.manifest.exports = {
|
|
'.': './dist/index.js',
|
|
'./used': './dist/used.js',
|
|
'./lazy': './dist/lazy.js',
|
|
'./unused': './dist/unused.js',
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(runtime.packageRoot, 'package.json'),
|
|
`${JSON.stringify(runtime.manifest, null, 2)}\n`,
|
|
);
|
|
for (const name of ['index', 'used', 'lazy', 'unused']) {
|
|
fs.writeFileSync(path.join(runtime.dist, `${name}.js`), "'use strict';\n");
|
|
}
|
|
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
application.manifest.exports = {
|
|
'.': './dist/index.js',
|
|
'./process': './dist/process.js',
|
|
'./unused': './dist/unused.js',
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(application.packageRoot, 'package.json'),
|
|
`${JSON.stringify(application.manifest, null, 2)}\n`,
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'index.js'),
|
|
[
|
|
"'use strict';",
|
|
"require('@qinglong/runtime-core/used');",
|
|
"require.resolve('@qinglong/runtime-core/used');",
|
|
"const load = () => import('@qinglong/runtime-core/lazy');",
|
|
'module.exports = { load };',
|
|
'',
|
|
].join('\n'),
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'process.js'),
|
|
"'use strict';\nmodule.exports = {};\n",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'unused.js'),
|
|
"'use strict';\n",
|
|
);
|
|
|
|
const report = pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: [
|
|
'@qinglong/local-application',
|
|
'@qinglong/local-application/process',
|
|
],
|
|
});
|
|
|
|
const projectedRuntime = JSON.parse(
|
|
fs.readFileSync(path.join(runtime.packageRoot, 'package.json'), 'utf8'),
|
|
);
|
|
const projectedApplication = JSON.parse(
|
|
fs.readFileSync(
|
|
path.join(application.packageRoot, 'package.json'),
|
|
'utf8',
|
|
),
|
|
);
|
|
assert.deepEqual(Object.keys(projectedRuntime.exports), [
|
|
'./used',
|
|
'./lazy',
|
|
]);
|
|
assert.deepEqual(Object.keys(projectedApplication.exports), [
|
|
'.',
|
|
'./process',
|
|
]);
|
|
assert.deepEqual(report.packageManifests.runtimeExports, {
|
|
keysBefore: 7,
|
|
keysAfter: 4,
|
|
keysRemoved: 3,
|
|
bytes: report.packageManifests.runtimeExports.bytes,
|
|
excludedSpecifiers: 0,
|
|
});
|
|
assert.equal(report.packageManifests.runtimeExports.bytes > 0, true);
|
|
assert.equal(fs.existsSync(path.join(runtime.dist, 'unused.js')), false);
|
|
assert.equal('main' in projectedRuntime, false);
|
|
assert.deepEqual(report.runtimeJavaScript, {
|
|
filesBefore: 7,
|
|
filesAfter: 4,
|
|
filesRemoved: 3,
|
|
bytesRemoved: report.runtimeJavaScript.bytesRemoved,
|
|
});
|
|
assert.equal(report.runtimeJavaScript.bytesRemoved > 0, true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('allows only explicitly excluded and declared optional feature imports', () => {
|
|
const current = fixture();
|
|
try {
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
application.manifest.devDependencies = {
|
|
'@qinglong/ai': application.manifest.version,
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(application.packageRoot, 'package.json'),
|
|
`${JSON.stringify(application.manifest, null, 2)}\n`,
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'index.js'),
|
|
"'use strict';\nconst load = () => import('@qinglong/ai/profile');\nmodule.exports = { load };\n",
|
|
);
|
|
|
|
assert.throws(
|
|
() =>
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
}),
|
|
/is not installed/,
|
|
);
|
|
const report = pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
excludedInternalPackages: ['@qinglong/ai'],
|
|
});
|
|
assert.equal(
|
|
report.packageManifests.runtimeExports.excludedSpecifiers,
|
|
1,
|
|
);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects dynamic internal specifiers before pruning any file', () => {
|
|
const current = fixture();
|
|
try {
|
|
const runtime = writePackage(
|
|
current.root,
|
|
'runtime-core',
|
|
'@qinglong/runtime-core',
|
|
);
|
|
fs.writeFileSync(path.join(runtime.dist, 'index.js'), "'use strict';\n");
|
|
const map = path.join(runtime.dist, 'index.js.map');
|
|
fs.writeFileSync(map, '{}\n');
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'index.js'),
|
|
"'use strict';\nconst name = 'runtime-core';\nrequire('@qinglong/' + name);\n",
|
|
);
|
|
|
|
assert.throws(
|
|
() =>
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
}),
|
|
/non-literal module load/,
|
|
);
|
|
assert.equal(fs.existsSync(map), true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects unknown entries and missing retained targets before mutation', () => {
|
|
const current = fixture();
|
|
try {
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
const map = path.join(application.dist, 'index.js.map');
|
|
fs.writeFileSync(map, '{}\n');
|
|
|
|
assert.throws(
|
|
() =>
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/not-installed'],
|
|
}),
|
|
/is not installed/,
|
|
);
|
|
assert.equal(fs.existsSync(map), true);
|
|
assert.throws(
|
|
() =>
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
}),
|
|
/runtime export target is missing/,
|
|
);
|
|
assert.equal(fs.existsSync(map), true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('retains relative, dynamic, cyclic, bin, migration and asset closure', () => {
|
|
const current = fixture();
|
|
try {
|
|
const runtime = writePackage(
|
|
current.root,
|
|
'runtime-core',
|
|
'@qinglong/runtime-core',
|
|
);
|
|
runtime.manifest.exports = {
|
|
'.': './dist/index.js',
|
|
'./used': './dist/used.js',
|
|
'./unused': './dist/unused.js',
|
|
};
|
|
fs.writeFileSync(
|
|
path.join(runtime.packageRoot, 'package.json'),
|
|
`${JSON.stringify(runtime.manifest, null, 2)}\n`,
|
|
);
|
|
for (const name of ['index', 'used', 'unused']) {
|
|
fs.writeFileSync(path.join(runtime.dist, `${name}.js`), "'use strict';\n");
|
|
}
|
|
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
application.manifest.bin = { ql3: 'dist/cli.js' };
|
|
fs.writeFileSync(
|
|
path.join(application.packageRoot, 'package.json'),
|
|
`${JSON.stringify(application.manifest, null, 2)}\n`,
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'index.js'),
|
|
[
|
|
"'use strict';",
|
|
"require('./cycle-a');",
|
|
"require('@qinglong/runtime-core/used');",
|
|
'',
|
|
].join('\n'),
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'cycle-a.js'),
|
|
"'use strict';\nconst load = () => import('./cycle-b.js');\nmodule.exports = { load };\n",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'cycle-b.js'),
|
|
"'use strict';\nrequire('./cycle-a');\n",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'cli.js'),
|
|
"#!/usr/bin/env node\n'use strict';\nrequire('./bin-support');\n",
|
|
{ mode: 0o755 },
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'bin-support.js'),
|
|
"'use strict';\n",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(application.dist, 'unused.js'),
|
|
"'use strict';\n",
|
|
);
|
|
const migrationDirectory = path.join(application.dist, 'migration');
|
|
fs.mkdirSync(migrationDirectory);
|
|
fs.writeFileSync(
|
|
path.join(migrationDirectory, 'ledger.js'),
|
|
"'use strict';\n",
|
|
);
|
|
const assetDirectory = path.join(application.dist, 'assets');
|
|
fs.mkdirSync(assetDirectory);
|
|
fs.writeFileSync(path.join(assetDirectory, 'schema.json'), '{}\n');
|
|
|
|
const report = pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
});
|
|
|
|
for (const relative of [
|
|
'index.js',
|
|
'cycle-a.js',
|
|
'cycle-b.js',
|
|
'cli.js',
|
|
'bin-support.js',
|
|
'migration/ledger.js',
|
|
'assets/schema.json',
|
|
]) {
|
|
assert.equal(fs.existsSync(path.join(application.dist, relative)), true);
|
|
}
|
|
assert.equal(fs.existsSync(path.join(application.dist, 'unused.js')), false);
|
|
assert.equal(fs.existsSync(path.join(runtime.dist, 'used.js')), true);
|
|
assert.equal(fs.existsSync(path.join(runtime.dist, 'index.js')), false);
|
|
assert.equal(fs.existsSync(path.join(runtime.dist, 'unused.js')), false);
|
|
const runtimeManifest = JSON.parse(
|
|
fs.readFileSync(path.join(runtime.packageRoot, 'package.json'), 'utf8'),
|
|
);
|
|
assert.deepEqual(runtimeManifest.exports, {
|
|
'./used': './dist/used.js',
|
|
});
|
|
assert.equal('main' in runtimeManifest, false);
|
|
assert.equal(report.runtimeJavaScript.filesRemoved, 3);
|
|
assert.equal(report.runtimeJavaScript.bytesRemoved > 0, true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('retains main closure for a package without exports', () => {
|
|
const current = fixture();
|
|
try {
|
|
const leaf = writePackage(current.root, 'leaf', '@qinglong/leaf');
|
|
delete leaf.manifest.exports;
|
|
fs.writeFileSync(
|
|
path.join(leaf.packageRoot, 'package.json'),
|
|
`${JSON.stringify(leaf.manifest, null, 2)}\n`,
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(leaf.dist, 'index.js'),
|
|
"'use strict';\nrequire('./support');\n",
|
|
);
|
|
fs.writeFileSync(path.join(leaf.dist, 'support.js'), "'use strict';\n");
|
|
fs.writeFileSync(path.join(leaf.dist, 'unused.js'), "'use strict';\n");
|
|
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/leaf'],
|
|
});
|
|
|
|
assert.equal(fs.existsSync(path.join(leaf.dist, 'index.js')), true);
|
|
assert.equal(fs.existsSync(path.join(leaf.dist, 'support.js')), true);
|
|
assert.equal(fs.existsSync(path.join(leaf.dist, 'unused.js')), false);
|
|
assert.equal(
|
|
JSON.parse(
|
|
fs.readFileSync(path.join(leaf.packageRoot, 'package.json'), 'utf8'),
|
|
).main,
|
|
'dist/index.js',
|
|
);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects escaping and missing relative targets before mutation', () => {
|
|
const current = fixture();
|
|
try {
|
|
const application = writePackage(
|
|
current.root,
|
|
'local-application',
|
|
'@qinglong/local-application',
|
|
);
|
|
const entry = path.join(application.dist, 'index.js');
|
|
const map = path.join(application.dist, 'index.js.map');
|
|
fs.writeFileSync(entry, "'use strict';\nrequire('../../outside');\n");
|
|
fs.writeFileSync(map, '{}\n');
|
|
|
|
assert.throws(
|
|
() =>
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
}),
|
|
/escapes its package/,
|
|
);
|
|
assert.equal(fs.existsSync(map), true);
|
|
fs.writeFileSync(entry, "'use strict';\nrequire('./missing');\n");
|
|
assert.throws(
|
|
() =>
|
|
pruneRuntimeArtifact(current.root, {
|
|
entrySpecifiers: ['@qinglong/local-application'],
|
|
}),
|
|
/target is missing/,
|
|
);
|
|
assert.equal(fs.existsSync(map), true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('rejects symbolic links before making partial changes', () => {
|
|
const current = fixture();
|
|
try {
|
|
const runtime = writePackage(
|
|
current.root,
|
|
'runtime-core',
|
|
'@qinglong/runtime-core',
|
|
);
|
|
const map = path.join(runtime.dist, 'index.js.map');
|
|
fs.writeFileSync(map, '{}\n');
|
|
fs.symlinkSync(map, path.join(runtime.dist, 'linked.map'));
|
|
|
|
assert.throws(
|
|
() => pruneRuntimeArtifact(current.root),
|
|
/runtime package contains a symbolic link/,
|
|
);
|
|
assert.equal(fs.existsSync(map), true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|
|
|
|
test('validates package manifests before deleting development files', () => {
|
|
const current = fixture();
|
|
try {
|
|
const runtime = writePackage(
|
|
current.root,
|
|
'runtime-core',
|
|
'@qinglong/runtime-core',
|
|
);
|
|
const map = path.join(runtime.dist, 'index.js.map');
|
|
fs.writeFileSync(map, '{}\n');
|
|
fs.writeFileSync(path.join(runtime.packageRoot, 'package.json'), '{');
|
|
|
|
assert.throws(() => pruneRuntimeArtifact(current.root), SyntaxError);
|
|
assert.equal(fs.existsSync(map), true);
|
|
} finally {
|
|
current.close();
|
|
}
|
|
});
|