打包脚本先校验后落盘
vite-export-xhs-minitool/scripts/pack.mjs:抽出 encodeZip,makeZip 先把条目与字节算完并通过 index.html / 禁止条目 / 体积校验,最后才写 zip,失败时不再留半成品。 scripts/.pack.test.mjs:补测试覆盖 makeZip 内部校验失败时不留 zip。 resources/agc-skills/manifest.json:同步该 Skill 的内容指纹与 pack 版本。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schemaVersion": "agc-skill-pack.v1",
|
||||
"version": "2026-08-26.66",
|
||||
"version": "2026-08-26.67",
|
||||
"skills": [
|
||||
{
|
||||
"name": "agc-unity-editor",
|
||||
@@ -204,7 +204,7 @@
|
||||
"scripts/validate.mjs",
|
||||
"scripts/vite.config.xhs-minitool.mjs"
|
||||
],
|
||||
"sha256": "8a096ba5ac398ee45276e47d955691f834ecb0af8facadb43d52186f90458313"
|
||||
"sha256": "ad9b5a6dc1b71e0413a1a71780c60d17076d13fbf67517a939c57565e6d0a477"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+25
@@ -69,6 +69,31 @@ test('存在不支持文件时打包整体失败,且不删除任何文件', ()
|
||||
assert.equal(existsSync(join(BASE, 'no-delete-out')), false, '失败时不应写出 zip');
|
||||
});
|
||||
|
||||
test('makeZip 的校验也发生在落盘前,失败时不留 zip', () => {
|
||||
// 缺 index.html:文件扩展名都合法,预检会放行,失败点在 makeZip。
|
||||
const noIndex = writeBuiltDir('no-index');
|
||||
rmSync(join(noIndex, 'index.html'));
|
||||
const noIndexOut = join(BASE, 'no-index-out', 'xhs-minitool.zip');
|
||||
assert.throws(
|
||||
() => packMinitool({ cwd: BASE, viteBuiltDir: 'no-index', zipOut: noIndexOut }),
|
||||
/缺少 index\.html/,
|
||||
);
|
||||
assert.equal(existsSync(noIndexOut), false, '缺 index.html 时不应写出 zip');
|
||||
|
||||
// 含禁止条目:vite.config.js 扩展名在白名单里,但命中 FORBIDDEN_ENTRY。
|
||||
writeBuiltDir('forbidden-entry', { 'vite.config.js': 'export default {};\n' });
|
||||
const forbiddenOut = join(BASE, 'forbidden-entry-out', 'xhs-minitool.zip');
|
||||
assert.throws(
|
||||
() => packMinitool({
|
||||
cwd: BASE,
|
||||
viteBuiltDir: 'forbidden-entry',
|
||||
zipOut: forbiddenOut,
|
||||
}),
|
||||
/禁止文件/,
|
||||
);
|
||||
assert.equal(existsSync(forbiddenOut), false, '含禁止条目时不应写出 zip');
|
||||
});
|
||||
|
||||
test('干净产物目录仍能正常打包', () => {
|
||||
writeBuiltDir('clean');
|
||||
const zipOut = join(BASE, 'clean-out', 'xhs-minitool.zip');
|
||||
|
||||
+23
-9
@@ -150,8 +150,8 @@ function collectZipEntries(rootDir) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function writeZip(rootDir, zipPath) {
|
||||
const entries = collectZipEntries(rootDir);
|
||||
/** 把已收集的条目编码成完整 zip 字节。只算内存,不落盘——校验没过时磁盘上不能留半成品。 */
|
||||
function encodeZip(entries) {
|
||||
const now = new Date();
|
||||
const dosTime =
|
||||
((now.getHours() << 11) | (now.getMinutes() << 5) | (now.getSeconds() >> 1)) & 0xffff;
|
||||
@@ -235,8 +235,15 @@ export function writeZip(rootDir, zipPath) {
|
||||
eocd.writeUInt32LE(localBuf.length, 16);
|
||||
eocd.writeUInt16LE(0, 20);
|
||||
|
||||
return Buffer.concat([localBuf, centralBuf, eocd]);
|
||||
}
|
||||
|
||||
/** 落盘版:写 zip 并返回条目名列表(保持既有导出名,供只想要 zip 的调用方使用)。 */
|
||||
export function writeZip(rootDir, zipPath) {
|
||||
const entries = collectZipEntries(rootDir);
|
||||
const buffer = encodeZip(entries);
|
||||
rmSync(zipPath, { force: true });
|
||||
writeFileSync(zipPath, Buffer.concat([localBuf, centralBuf, eocd]));
|
||||
writeFileSync(zipPath, buffer);
|
||||
return entries.map((entry) => entry.name);
|
||||
}
|
||||
|
||||
@@ -258,20 +265,27 @@ export function auditZipFile(file, size) {
|
||||
}
|
||||
|
||||
function makeZip(viteBuiltDir, zipPath) {
|
||||
const entries = writeZip(viteBuiltDir, zipPath);
|
||||
if (!entries.includes('index.html')) {
|
||||
// 先把条目和字节都算完、校验通过,最后才落盘:中途失败时预期输出路径上不能留一份半成品,
|
||||
// 更不能让宿主把半成品当成本次构建的产物。
|
||||
const entries = collectZipEntries(viteBuiltDir);
|
||||
const names = entries.map((entry) => entry.name);
|
||||
if (!names.includes('index.html')) {
|
||||
throw new Error('zip 根目录缺少 index.html(可能多套了一层目录)');
|
||||
}
|
||||
const bad = entries.find((entry) => FORBIDDEN_ENTRY.test(entry));
|
||||
const bad = names.find((entry) => FORBIDDEN_ENTRY.test(entry));
|
||||
if (bad) {
|
||||
throw new Error(`zip 内含禁止文件:${bad}`);
|
||||
}
|
||||
const size = statSync(zipPath).size;
|
||||
for (const item of auditZipFile(zipPath, size)) {
|
||||
const buffer = encodeZip(entries);
|
||||
for (const item of auditZipFile(zipPath, buffer.length)) {
|
||||
if (item.severity === 'ERROR') throw new Error(item.message);
|
||||
console.warn(`[warn] ${item.message}`);
|
||||
}
|
||||
console.log(`zip 已生成:${zipPath}(${entries.length} 个条目,${(size / MIB).toFixed(2)} MiB)`);
|
||||
rmSync(zipPath, { force: true });
|
||||
writeFileSync(zipPath, buffer);
|
||||
console.log(
|
||||
`zip 已生成:${zipPath}(${entries.length} 个条目,${(buffer.length / MIB).toFixed(2)} MiB)`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user