diff --git a/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs b/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs index 651e342d0..eb5394595 100644 --- a/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs +++ b/apps/ai-game-creator-shell/scripts/build-macos-ci.mjs @@ -75,6 +75,22 @@ const context = resolveReleaseContext(['--target=universal-apple-darwin']); const partition = resolveReleasePartition(context.channel, context.target); const version = await prepareReleaseVersion(context); +// 幂等边界:workspace 会保留上一轮产物。先删掉本次将要写出的对象,否则 +// 1) hdiutil 会因同名 DMG 已存在直接失败(首次实跑即命中); +// 2) 上一轮遗留的 `.sig` 会让验签门禁把「本轮其实没签」判成通过。 +// 只删本次要写出的确切路径,不动其它版本产物与编译缓存。 +const macosBundle = path.join(context.bundleRoot, 'macos'); +for (const stale of [ + path.join(macosBundle, '陶泥儿.app.tar.gz'), + path.join(macosBundle, '陶泥儿.app.tar.gz.sig'), + path.join(macosBundle, `陶泥儿_${version}_universal.dmg`), + path.join(macosBundle, `陶泥儿_${version}_universal.dmg.sha256`), + path.join(context.bundleRoot, 'latest.json'), + path.join(context.bundleRoot, 'release-notes.txt'), +]) { + fs.rmSync(stale, { force: true }); +} + const args = [ '--target=universal-apple-darwin', '--bundles', @@ -110,6 +126,8 @@ try { fs.symlinkSync('/Applications', path.join(stage, 'Applications')); command('hdiutil', [ 'create', + // 前面已删除同名对象;这里再要求显式覆盖,避免残留文件让构建以「文件已存在」失败。 + '-ov', '-volname', '陶泥儿', '-srcfolder', diff --git a/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs b/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs index 4673179f7..377d29a4b 100644 --- a/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs +++ b/apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs @@ -116,4 +116,13 @@ test('macOS release entry verifies the updater signature before uploading', () = !entry.includes("'--no-sign'"), '--no-sign 会同时跳过 updater 签名,产物缺少 .sig', ); + // workspace 会跨构建保留产物:必须先删本次要写的对象,否则会因同名 DMG 失败, + // 或让上一轮遗留的 .sig 让验签门禁误通过。 + for (const required of [ + "path.join(macosBundle, '陶泥儿.app.tar.gz.sig')", + 'fs.rmSync(stale, { force: true })', + "'-ov'", + ]) { + assert.ok(entry.includes(required), required); + } });