From 5e6231493ea69c0ea1d38d484911ae04aa0c60bd Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Sun, 11 Oct 2026 10:32:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(pack):=20Windows=20=E6=89=93=E5=8C=85?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE=E5=B8=A6=E7=9B=AE=E5=BD=95=E5=89=8D=E7=BC=80?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E4=B8=8E=20macOS=20=E4=BA=A7=E7=89=A9?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI 在 f85acad 上红了:`✗ zip 内未找到条目 node.exe: release/nodeagent-win-x64.zip` (而本地 macOS 全绿 —— 方向恰好相反)。根因是两平台用了不同打包器: - macOS/Linux: `cd && zip -r .` → 条目是裸名 `node.exe` - Windows : `Compress-Archive -Path '\*'` → 条目带前缀 `/node.exe` 两处一起修: 1. `scripts/lib/zip-pe-arch.mjs`:条目名匹配改用 **basename**,兼容两种打包方式。 已用构造的带前缀 zip 实测通过,并注入错架构验证守卫仍会红。 2. `scripts/pack.mjs`:Windows 分支改用 .NET `ZipFile::CreateFromDirectory` —— 它打包**目录内容**,条目同样是裸名,从根上消除两平台产物结构差异。 否则 CI 校验的产物与发布产物**不同构**,校验本身的价值打折; 且「解压后双击 install.cmd」的说明会因多一层目录而失配。 教训:**「本地全绿」≠「Windows 绿」** —— 平台差异只有真 runner 能暴露(第 N 次实证)。 --- scripts/lib/zip-pe-arch.mjs | 8 +++++++- scripts/pack.mjs | 14 +++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/scripts/lib/zip-pe-arch.mjs b/scripts/lib/zip-pe-arch.mjs index cbcd01f..49cd354 100644 --- a/scripts/lib/zip-pe-arch.mjs +++ b/scripts/lib/zip-pe-arch.mjs @@ -49,7 +49,13 @@ export function readZipEntry(zipPath, entryName) { const commentLen = buf.readUInt16LE(off + 32); const localOff = buf.readUInt32LE(off + 42); const name = buf.toString('utf8', off + 46, off + 46 + nameLen); - if (name === entryName) { + // 条目名匹配用 **basename**,而不是完整名 —— 因为两平台的打包器不一样: + // macOS/Linux:`cd && zip -r .` → 条目是裸名 `node.exe` + // Windows :`Compress-Archive -Path \*` → 条目带前缀 `/node.exe` + // 2026-10-11 真例:只比完整名会让「本地(macOS)全绿、CI(Windows)全红」, + // 且方向恰好相反 —— 而 CI 是唯一会跑 Windows 打包路径的地方。 + const entryBase = name.replace(/\\/g, '/').split('/').pop(); + if (entryBase === entryName) { const lNameLen = buf.readUInt16LE(localOff + 26); const lExtraLen = buf.readUInt16LE(localOff + 28); const start = localOff + 30 + lNameLen + lExtraLen; diff --git a/scripts/pack.mjs b/scripts/pack.mjs index 9d3084f..f7ea913 100644 --- a/scripts/pack.mjs +++ b/scripts/pack.mjs @@ -249,15 +249,23 @@ async function main() { // 把预共享密钥打进包里等于公开密钥。install.cmd 在文件缺失时会自动生成新密钥 // (生成在安装目录,只留在被控端本机),所以排除它不影响安装。 if (process.platform === 'win32') { - // Windows:GitHub runner / 多数 Windows 没有 Info-ZIP,改用 PowerShell 的 Compress-Archive。 - // 先删掉 PSK.txt(若存在)再压 —— Compress-Archive 无 -x 排除参数。 + // Windows:GitHub runner / 多数 Windows 没有 Info-ZIP。 + // 先删掉 PSK.txt(若存在)再压 —— 打包器无法按需排除单个文件。 const psk = join(PKG_DIR, 'PSK.txt'); if (existsSync(psk)) rmSync(psk, { force: true }); + // ⚠️ 刻意不用 `Compress-Archive -Path '\*'`:它会把**目录名当前缀**写进条目 + // (产物变成 `/node.exe`),而 macOS 的 `cd && zip -r .` 是**裸名** `node.exe` + // —— 两平台产物**结构不一致**。看着是小事,代价却不小: + // ① 按条目名取文件的校验会失配(2026-10-11 真例:CI 在 Windows 上报 + // 「zip 内未找到条目 node.exe」,而 macOS 本地全绿 → 方向恰好相反); + // ② CI 校验的产物与发布产物**不同构**,校验价值打折。 + // 改用 .NET 的 ZipFile::CreateFromDirectory —— 它打包**目录内容**,条目同样是裸名。 execFileSync( 'powershell', [ '-NoProfile', '-Command', - `Compress-Archive -Path '${PKG_DIR}\*' -DestinationPath '${zipOut}' -Force`, + `Add-Type -AssemblyName System.IO.Compression.FileSystem; ` + + `[IO.Compression.ZipFile]::CreateFromDirectory('${PKG_DIR}', '${zipOut}')`, ], { stdio: 'inherit' }, );