From e146859ea27eb2054f6098dfdf98badf0dfc2863 Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Thu, 17 Sep 2026 19:42:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=91=98=E8=A6=81=E9=94=9A?= =?UTF-8?q?=E7=82=B9=E6=94=AF=E6=8C=81=20CI=20=E5=85=9C=E5=BA=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 摘要锚点优先取渠道清单 commit,缺失时回退 CI 传入的上一次成功构建 COMMIT_HASH - Jenkins 构建阶段解析上一次成功构建的 COMMIT_HASH 并注入 AGC_UPDATE_PREVIOUS_COMMIT,读取失败保持为空 - 新增锚点优先级与非法值忽略的定向用例 - 技术方案补充锚点兜底说明 --- .../scripts/build-release.mjs | 12 +++++++ .../scripts/build-release.test.mjs | 36 +++++++++++++++++++ ...方案】AGC客户端更新检查与下载-2026-08-31.md | 2 +- .../Jenkinsfile.ai-game-creator-shell-build | 17 +++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/scripts/build-release.mjs b/apps/ai-game-creator-shell/scripts/build-release.mjs index e8c8e026d..4459fbe92 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.mjs @@ -196,9 +196,21 @@ async function readRemoteChannelManifest(channel = resolveReleaseChannel()) { return fetchManifest(updateManifestUrl(channel), 'OSS 渠道清单'); } +/** + * 摘要锚点:上次发布对应的提交。 + * + * 首选渠道清单里的 `commit`(发布产物自己的事实来源);清单缺该字段时(首次启用 + * 摘要、或更换渠道后清单还没带过 commit)回退到 CI 传入的 `AGC_UPDATE_PREVIOUS_COMMIT` + * —— 它是上一次成功构建的 COMMIT_HASH,同样指向用户拿到的那个版本。 + */ export async function resolvePreviousReleaseCommit( channel = resolveReleaseChannel(), + { override = process.env.AGC_UPDATE_PREVIOUS_COMMIT } = {}, ) { + const explicit = override?.trim(); + if (explicit && /^[0-9a-f]{7,40}$/u.test(explicit)) { + return explicit; + } const manifest = await readRemoteChannelManifest(channel); const commit = typeof manifest?.commit === 'string' ? manifest.commit.trim() : ''; diff --git a/apps/ai-game-creator-shell/scripts/build-release.test.mjs b/apps/ai-game-creator-shell/scripts/build-release.test.mjs index cc2379f01..eb583e380 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.test.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.test.mjs @@ -22,6 +22,7 @@ import { formatReleaseNotes, nextPatchVersion, resolveManifestPlatformKeys, + resolvePreviousReleaseCommit, resolveReleaseChannel, resolveRemoteHighWaterVersion, selectReleaseArtifact, @@ -241,6 +242,41 @@ test('version high water ignores the windows migration pointer for other channel ); }); +test('release notes anchor prefers the explicit commit and falls back to the manifest', async () => { + await withStubbedFetch( + () => jsonResponse({ version: '0.1.61', commit: 'abcdef1234567890' }), + async () => { + assert.equal( + await resolvePreviousReleaseCommit('dev-win', { + override: '6017d46088c04199e99cf89f347b12d67591475e', + }), + '6017d46088c04199e99cf89f347b12d67591475e', + ); + // 覆盖值非法时忽略,继续用清单里的 commit。 + assert.equal( + await resolvePreviousReleaseCommit('dev-win', { + override: 'not-a-sha', + }), + 'abcdef1234567890', + ); + assert.equal( + await resolvePreviousReleaseCommit('dev-win', { override: ' ' }), + 'abcdef1234567890', + ); + }, + ); + + await withStubbedFetch( + () => jsonResponse({ version: '0.1.61' }), + async () => { + assert.equal( + await resolvePreviousReleaseCommit('dev-win', { override: undefined }), + null, + ); + }, + ); +}); + test('release upload forces overwrite for artifact, signature and channel pointers', () => { const source = readFileSync( new URL('./release-upload.mjs', import.meta.url), diff --git a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md index 6b2f0841b..3c7201a22 100644 --- a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md +++ b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md @@ -92,7 +92,7 @@ - 发布入口:`npm run ai-game-creator-shell:release:upload`(构建 + 按渠道上传);仅构建不发布的 smoke 使用 `--no-bundle` 分支,不读远端版本、不改版本、不生成清单。 - 渠道由构建参数显式指定,并按目标平台校验:Windows 目标只允许 `dev-win`,macOS 目标只允许 `dev-mac`;未显式指定时按目标平台取默认渠道。 - 定时调度只在本轮到达的提交包含 AGC 相关路径(客户端、共享包、`server-rs/crates`、AGC 插件、桌面壳图标、根依赖清单)时才触发渠道发布;纯文档或流水线自身的提交只跑 Full Build,不推高客户端版本号。判定失败或勾选强制触发时按"需要发布"处理。 -- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题(短 SHA)`,最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt`。`AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。 +- 更新摘要自动生成:发布脚本用渠道清单里的 `commit` 字段(上一次发布的提交)到本次提交之间、且只覆盖客户端相关路径的提交列表生成 `notes`(每条 `- 提交标题(短 SHA)`,最多 12 条、主题 80 字、整体 900 字,超出折叠或截断),同时写入旧协议清单的 `releaseNotes` 和归档文件 `release-notes.txt`。`AGC_UPDATE_RELEASE_NOTES` 非空时以手动文案为准;无法判定起点(缺少上次 `commit` 或本地没有该提交)时不写摘要。清单缺少 `commit` 时回退用上一次成功构建的 `COMMIT_HASH`(CI 通过 `AGC_UPDATE_PREVIOUS_COMMIT` 传入)作为锚点,因此首次启用摘要或更换渠道后也能立即产出摘要。 - 清单里的 `commit` 是非标准字段:更新插件忽略未知字段,发布脚本用它定位下一次摘要的起点。 - 上传:安装包与 `.sig` 上传到 `agc///`,清单以 `--force` 覆盖上传到 `agc//latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。 - Jenkins 流水线需要新增渠道参数与签名凭据;签名私钥与密码只以受保护凭据注入当前进程,不写入 workspace、日志或归档产物。 diff --git a/jenkins/Jenkinsfile.ai-game-creator-shell-build b/jenkins/Jenkinsfile.ai-game-creator-shell-build index db0bbf23b..11010b29b 100644 --- a/jenkins/Jenkinsfile.ai-game-creator-shell-build +++ b/jenkins/Jenkinsfile.ai-game-creator-shell-build @@ -122,6 +122,22 @@ pipeline { stage('Build and upload') { steps { + script { + // 摘要锚点兜底:清单里还没有 commit 字段时(首次启用摘要 / 换渠道), + // 用上一次成功构建的 COMMIT_HASH 作为「上次发布提交」。读取失败保持为空, + // 发布脚本会退回清单锚点或干脆不写摘要。 + def anchor = '' + try { + def previousBuild = currentBuild.previousSuccessfulBuild + anchor = (previousBuild?.buildVariables?.COMMIT_HASH ?: '').toString().trim() + } catch (error) { + echo "读取上一次成功构建的 commit 失败,跳过摘要锚点兜底:${error}" + } + env.AGC_UPDATE_PREVIOUS_COMMIT = anchor + if (anchor) { + echo "更新摘要锚点兜底:${anchor.take(12)}" + } + } withCredentials([ string(credentialsId: 'AliyunAccessKeyId', variable: 'AGC_OSS_ACCESS_KEY_ID'), string(credentialsId: 'AliyunaccessKeySecret', variable: 'AGC_OSS_ACCESS_KEY_SECRET'), @@ -134,6 +150,7 @@ pipeline { "AGC_RELEASE_VERSION=${params.AGC_RELEASE_VERSION}", "AGC_UPDATE_CHANNEL=${params.AGC_UPDATE_CHANNEL}", "AGC_RELEASE_DRY_RUN=${params.AGC_RELEASE_DRY_RUN ? '1' : '0'}", + "AGC_UPDATE_PREVIOUS_COMMIT=${env.AGC_UPDATE_PREVIOUS_COMMIT ?: ''}", "AGC_UPDATE_RELEASE_NOTES=${params.AGC_UPDATE_RELEASE_NOTES}", ]) { powershell '''