Files
Genarrative/apps/ai-game-creator-shell/scripts/macos-release-identity.mjs
T
kdletters e23b0e871b
Project CI / AI game creator shell Rust crates (push) Successful in 1m45s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m4s
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / Backend tests (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
修复 macOS 渠道发布挑中残留产物,改为构建期核对包内版本与渠道身份
- build-macos-ci:构建前按后缀清空 macos/ 下的 *.app.tar.gz / *.sig / *.dmg / *.dmg.sha256,构建后读 Info.plist 核对版本与渠道身份,生成清单后再断言选中本轮更新包
- 新增 macos-release-identity.mjs 与单测:回归用例直接用线上 dev-mac 里那份 0.1.139 release 身份包
- prepare-macos-codex.test:残留清理断言改成按后缀全覆盖,并校验清理在构建前、身份核对在验签前
- Jenkinsfile.ai-game-creator-shell-macos-build:mac Job 增加身份守卫用例
- check:agc-update-channel-manifests:下载后解出 mac 包内 Info.plist 核对版本与身份,并按 2026-09-21 决策只要求 darwin-aarch64
- 文档:两份里程碑按现行决策修正口径并记录线上缺陷,decision-log 记决策与边界,pitfalls 记录「按目录扫描挑产物会选中残留」
2026-09-28 22:09:57 +08:00

108 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* macOS 发布产物的身份与版本守卫。
*
* 为什么单独抽出来:2026-09-28 线上核对发现 `dev-mac/0.1.142` 清单指向的更新包
* 其实是 **0.1.139 的 release 身份包**(`world.genarrative.ai-game-creator.release`)。
* 根因是 mac 构建目录里会留下上一轮(甚至上一个渠道身份)的 `*.app.tar.gz`,而
* `generateUpdateManifest()` 是按优先级扫描目录挑产物的——于是「清单写 0.1.142、
* 包里是 0.1.139 且是另一个渠道身份」。这类错误在客户端上表现为「更新后版本没变
* 或渠道身份被换掉」,只能靠构建期失败关闭拦住。
*
* 约束:只做纯函数与显式断言,方便单测复现线上那份坏产物;不在这里读文件系统。
*/
/** 会被构建期残留影响的 mac 产物后缀:更新包、签名、首装 DMG 与其摘要。 */
const STALE_MACOS_ARTIFACT_SUFFIXES = [
'.app.tar.gz',
'.app.tar.gz.sig',
'.dmg',
'.dmg.sha256',
];
function extractPlistString(plistText, key) {
const pattern = new RegExp(
`<key>${key}</key>\\s*<string>([^<]*)</string>`,
'u',
);
const match = plistText.match(pattern);
return match ? match[1] : null;
}
/** 从 `Info.plist` 文本里读出发布相关的身份字段;缺字段返回 null,由断言决定是否致命。 */
export function readMacosAppInfoIdentity(plistText) {
if (typeof plistText !== 'string' || plistText.trim().length === 0) {
throw new Error('Info.plist 内容为空,无法核对产物身份');
}
return {
version: extractPlistString(plistText, 'CFBundleShortVersionString'),
identifier: extractPlistString(plistText, 'CFBundleIdentifier'),
name: extractPlistString(plistText, 'CFBundleName'),
displayName: extractPlistString(plistText, 'CFBundleDisplayName'),
};
}
/**
* 断言本轮构建出来的 `.app` 就是本渠道本轮该发的产物。
*
* 三个字段都要对上:版本必须等于即将写进清单的版本;identifier 与产品名必须来自
* 渠道安装身份(`channel-identity.mjs`),否则同一台机器上的 dev / release 会互相顶掉。
*/
export function assertMacosAppMatchesChannelIdentity({
identity,
expectedVersion,
expectedProductName,
expectedIdentifier,
}) {
const problems = [];
if (identity.version !== expectedVersion) {
problems.push(
`版本不一致:产物 ${identity.version ?? '(缺失)'},本轮清单 ${expectedVersion}`,
);
}
if (identity.identifier !== expectedIdentifier) {
problems.push(
`bundle identifier 不一致:产物 ${identity.identifier ?? '(缺失)'},本渠道 ${expectedIdentifier}`,
);
}
const names = [identity.name, identity.displayName].filter(Boolean);
if (
names.length === 0 ||
names.some((value) => value !== expectedProductName)
) {
problems.push(
`产品名不一致:产物 ${names.join(' / ') || '(缺失)'},本渠道 ${expectedProductName}`,
);
}
if (problems.length > 0) {
throw new Error(
`macOS 产物身份核对失败:${problems.join(';')}。` +
'这通常意味着构建目录里残留了上一轮/其它渠道的产物,或渠道身份没有注入 Tauri 构建。',
);
}
}
/**
* 列出构建前必须清掉的残留产物:构建目录里的更新包/签名/首装包只属于本轮,
* 留着就会让按目录扫描的清单生成挑到旧文件。
*/
export function listStaleMacosArtifacts(filePaths) {
return filePaths.filter((filePath) =>
STALE_MACOS_ARTIFACT_SUFFIXES.some((suffix) => filePath.endsWith(suffix)),
);
}
/** 断言清单最终选中的更新包就是本轮写出的那一个,避免「签名对但选错包」。 */
export function assertManifestArtifactMatchesExpected({
artifactPath,
expectedPath,
}) {
if (!artifactPath || !expectedPath) {
throw new Error('清单产物路径缺失,无法核对本轮更新包');
}
if (artifactPath !== expectedPath) {
throw new Error(
`清单选中的更新包不是本轮产物:选中 ${artifactPath},本轮应为 ${expectedPath}`,
);
}
}