给共享产物选择器补版本守卫,避免旧安装包被发出去
Project CI / AI game creator shell Rust crates (push) Successful in 1m34s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m3s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Successful in 1m34s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m3s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
- build-release:新增导出的 assertArtifactVersionMatches,generateUpdateManifest 选中的产物若文件名带 _x.y.z_ 版本段就必须等于本轮版本,否则失败关闭(macOS 无版本段的 .app.tar.gz 交给身份断言) - build-release.test:新增残留旧安装包被拒绝、同版本通过、无版本段返回 null 三条用例 - decision-log 与 pitfalls 补记该通用守卫与完整测试批次(apps/ai-game-creator-shell/scripts/*.test.mjs 110 passed)
This commit is contained in:
@@ -117,6 +117,26 @@ function ossBaseUrl() {
|
||||
).replace(/\/+$/u, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 产物文件名里的版本必须等于本轮发布版本。
|
||||
*
|
||||
* `selectReleaseArtifact()` 是按目录扫描 + 优先级挑产物,构建目录里残留的旧版本安装包
|
||||
* (例如 `..._0.1.153_x64-setup.exe`)会被挑中,于是「清单写新版本、对象是旧版本」。
|
||||
* 名字里没有版本号的产物(例如 macOS 的 `<产品名>.app.tar.gz`)返回 null,由各入口的
|
||||
* 身份断言负责;Windows 这类带版本号的安装包在这里失败关闭。
|
||||
*/
|
||||
export function assertArtifactVersionMatches(artifactPath, version) {
|
||||
const match = path.basename(artifactPath).match(/_(\d+\.\d+\.\d+)_/u);
|
||||
if (!match) return null;
|
||||
if (match[1] !== version) {
|
||||
throw new Error(
|
||||
`发布产物版本与本次发布不一致:产物 ${match[1]},本次 ${version}(${path.basename(artifactPath)});` +
|
||||
'构建目录里可能残留了上一轮安装包,请清理后再发布',
|
||||
);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function readPackageJson() {
|
||||
return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
}
|
||||
@@ -669,6 +689,7 @@ export async function generateUpdateManifest(
|
||||
if (!artifact) {
|
||||
throw new Error(`未找到可发布的 AGC 安装包:${bundleRoot}`);
|
||||
}
|
||||
assertArtifactVersionMatches(artifact, readPackageJson().version);
|
||||
const downloadArtifact = selectFirstInstallArtifact(files, {
|
||||
target,
|
||||
version: readPackageJson().version,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
import {
|
||||
agcReleasePathPatterns,
|
||||
assertArtifactVersionMatches,
|
||||
buildRelease,
|
||||
buildTauriBuildArguments,
|
||||
compareVersions,
|
||||
@@ -1133,3 +1134,31 @@ test('scheduler skips the full build only for non-deploy paths', () => {
|
||||
assert.ok(skipLine.includes(pattern), `Full Build 跳过模式缺少 ${pattern}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects a stale installer picked up from the build directory', () => {
|
||||
// generateUpdateManifest() 是按目录扫描挑产物:残留的旧版本安装包会被挑中,
|
||||
// 必须在这里失败关闭,而不是把「清单新版本 + 对象旧版本」发出去。
|
||||
assert.throws(
|
||||
() =>
|
||||
assertArtifactVersionMatches(
|
||||
'/bundle/nsis/陶泥儿开发版_0.1.153_x64-setup.exe',
|
||||
'0.1.154',
|
||||
),
|
||||
/发布产物版本与本次发布不一致:产物 0\.1\.153,本次 0\.1\.154/u,
|
||||
);
|
||||
assert.equal(
|
||||
assertArtifactVersionMatches(
|
||||
'/bundle/nsis/陶泥儿开发版_0.1.154_x64-setup.exe',
|
||||
'0.1.154',
|
||||
),
|
||||
'0.1.154',
|
||||
);
|
||||
// macOS 更新包名里没有版本号,交给各入口的身份断言处理。
|
||||
assert.equal(
|
||||
assertArtifactVersionMatches(
|
||||
'/bundle/macos/陶泥儿开发版.app.tar.gz',
|
||||
'0.1.154',
|
||||
),
|
||||
null,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9704,8 +9704,9 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- 背景:用只读核对脚本 `scripts/check-agc-update-channel-manifests.mjs` 检查**已发布**的 OSS 渠道清单时发现,线上 `dev-mac/latest.json`(`version=0.1.142`、`commit=c07c10c0c`)指向的更新包解开后是 `CFBundleShortVersionString=0.1.139`、`CFBundleIdentifier=world.genarrative.ai-game-creator.release`、`CFBundleName=陶泥儿 Release`。签名验签、对象存在、`.sig` 与清单文本一致这些都对——错的是**版本与渠道身份**:dev 渠道的 arm64 客户端会被指向一个旧版的 release 身份包。
|
||||
- 根因:`build-macos-ci.mjs` 以前只清理「本轮要写的确切文件名」,mac 构建目录里上一轮/其它渠道身份留下的 `*.app.tar.gz` 不会被删;`generateUpdateManifest()` 是按目录扫描 + 优先级选产物,于是选中了残留文件。mac 构建机复用 workspace,这类残留会长期存在。
|
||||
- 决策(构建期失败关闭):mac 发布入口在构建前按后缀清空 `macos/` 下的 `*.app.tar.gz`、`*.app.tar.gz.sig`、`*.dmg`、`*.dmg.sha256`;构建后读 `.app/Contents/Info.plist`,断言 `CFBundleShortVersionString` 等于本轮发布版本、`CFBundleIdentifier`/`CFBundleName` 等于该渠道安装身份;生成清单后再断言清单选中的更新包就是本轮那一个。任一不符直接中止,不写 OSS。
|
||||
- 决策(产物选择器本身也要挡旧版本):`generateUpdateManifest()` 是共享入口,Windows 侧靠 Jenkins 的 `git clean -fdx` 才没踩到同一个坑,所以再补一道与平台无关的守卫——`assertArtifactVersionMatches()` 要求文件名里出现形如 `_0.1.154_` 的版本段时必须等于本轮版本(残留的 `_0.1.153_` 安装包会被挑中并因此失败关闭);macOS 的 `<产品名>.app.tar.gz` 不含版本段,返回 `null`,由 mac 入口的身份断言负责。
|
||||
- 决策(只读核对也要看包内身份):`check:agc-update-channel-manifests` 在 `AGC_UPDATE_VERIFY_DOWNLOAD=1` 时下载 mac 更新包、解出 `Info.plist` 做同样断言;同时按 2026-09-21 决策断言 mac 渠道只登记 `darwin-aarch64`(不再要求 universal 双键)。
|
||||
- 决策(口径回归):macOS 现行契约是 arm64 单架构(2026-09-21 决策),里程碑里「两个 macOS 平台键指向 universal 产物」的旧文字按现行决策改写;不得据此重新切回 universal,除非按该决策给出的恢复路径补齐按架构的 Node 运行时。
|
||||
- 影响范围:`apps/ai-game-creator-shell/scripts/build-macos-ci.mjs`、新增 `apps/ai-game-creator-shell/scripts/macos-release-identity.mjs` 与其 `.test.mjs`、`apps/ai-game-creator-shell/scripts/prepare-macos-codex.test.mjs`、`jenkins/Jenkinsfile.ai-game-creator-shell-macos-build`、`scripts/check-agc-update-channel-manifests.mjs`、两份里程碑与本文件、pitfalls。
|
||||
- 验证:`node --test macos-release-identity.test.mjs prepare-macos-codex.test.mjs verify-updater-signature.test.mjs build-release.test.mjs cargo-features.test.mjs` → 59 passed(新增 6 条,回归用例直接喂线上那份 0.1.139 release 身份 plist,必须抛错);`npm run check:production-ops`、`check:encoding`、`check:doc-index`、prettier、eslint、`git diff --check` 通过;只读核对对线上 `dev-win` 全 PASS(含 158 MiB 产物下载验签与旧协议 sha256 一致),对线上 `dev-mac` 精确报出上面两条 FAIL。
|
||||
- 验证:`node --test apps/ai-game-creator-shell/scripts/*.test.mjs` → **110 passed / 0 failed**(其中定向批次 `macos-release-identity / prepare-macos-codex / verify-updater-signature / build-release / cargo-features` 60 passed;新增的 mac 身份回归用例直接喂线上那份 0.1.139 release 身份 plist,必须抛错;新增的产物版本守卫用例喂 `_0.1.153_` 残留安装包,必须抛错);`npm run check:production-ops`、`check:encoding`、`check:doc-index`、prettier、eslint、`git diff --check` 通过;只读核对对线上 `dev-win` 全 PASS(含 158 MiB 产物下载验签与旧协议 sha256 一致),对线上 `dev-mac` 精确报出上面两条 FAIL。
|
||||
- 边界(未完成):修复只保证「以后再发不会再错」,线上 `dev-mac/latest.json` 仍指向那份坏包;需要一次带 Jenkins 凭据与授权的 mac 重新发布,然后重跑只读核对才算了结。Apple 代码签名与公证仍是 `adhoc`。
|
||||
|
||||
@@ -6105,4 +6105,4 @@ Cocos Creator 根目录由 `package.json.creator.version` 与普通 `assets/`
|
||||
- **根因**:`build-macos-ci.mjs` 以前只 `rmSync` 「本轮要写的确切文件名」,构建目录里上一轮(或其它渠道身份)留下的 `*.app.tar.gz` 不会被清;而 `generateUpdateManifest()` 是**扫描构建目录、按优先级挑产物**(同名优先级再按字典序),于是挑走了残留的 release 身份包。mac 构建机是复用 workspace 的,这类残留会长期存在。
|
||||
- **判据**:只读核对要**打开产物看身份**,不能只看「地址存在 + 签名匹配」。`npm run check:agc-update-channel-manifests`(`AGC_UPDATE_VERIFY_DOWNLOAD=1`)现在会解出 mac 包的 `Info.plist`,断言「包内版本 == 清单版本」且「包内 identifier/产品名 == 本渠道身份」;本轮对线上取样得到两条 FAIL,正是这个缺陷。
|
||||
- **处理(2026-09-28 已修)**:构建前按后缀清空 `macos/` 下的 `*.app.tar.gz`、`*.app.tar.gz.sig`、`*.dmg`、`*.dmg.sha256`;构建后读 `.app/Contents/Info.plist` 断言版本/identifier/产品名;生成清单后再断言 `release.artifact` 就是本轮那一个。守卫在 `apps/ai-game-creator-shell/scripts/macos-release-identity.mjs`,回归用例直接用线上那份 0.1.139 release 身份包(`node --test` 59 passed)。
|
||||
- **教训**:凡是「按目录扫描挑产物」的发布步骤,都要么先清空同类产物、要么按本轮预期路径断言;只删「本轮要写的名字」等于把上一轮的坏包留在候选集里。还有一条更一般的:核对线上清单时,先看 decision-log 的现行口径(这里 macOS 已是 arm64 单架构),别拿过期里程碑文字当契约。
|
||||
- **教训**:凡是「按目录扫描挑产物」的发布步骤,都要么先清空同类产物、要么按本轮预期路径断言;只删「本轮要写的名字」等于把上一轮的坏包留在候选集里。现在共享入口 `generateUpdateManifest()` 也补了与平台无关的 `assertArtifactVersionMatches()`(文件名带 `_0.1.153_` 这类版本段时必须是本轮版本),所以即使将来某个流水线不再 `git clean -fdx`,旧安装包也会被拒绝而不是被发出去。还有一条更一般的:核对线上清单时,先看 decision-log 的现行口径(这里 macOS 已是 arm64 单架构),别拿过期里程碑文字当契约。
|
||||
|
||||
Reference in New Issue
Block a user