diff --git a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md index 314b98419..ec42456a8 100644 --- a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md +++ b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md @@ -91,6 +91,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,不推高客户端版本号。判定失败或勾选强制触发时按"需要发布"处理。 - 上传:安装包与 `.sig` 上传到 `agc///`,清单以 `--force` 覆盖上传到 `agc//latest.json`,保证 latest 指针与清单内 URL 指向已存在的对象。 - Jenkins 流水线需要新增渠道参数与签名凭据;签名私钥与密码只以受保护凭据注入当前进程,不写入 workspace、日志或归档产物。 - 归档证据:安装包、`.sig`、渠道清单与源码 commit。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index d13920f03..5784a9a5c 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -137,7 +137,7 @@ BgFilter 对已经落入私有 OSS 的生成原图、动作抽取帧和手动去 `Genarrative-Scheduled-Revision-Trigger` 是唯一的定时入口,每小时检查一次(`H * * * *`,分钟由 Jenkins 按 Job 名散列,不等同于整点)。它只用 `git ls-remote` 解析 `SOURCE_BRANCH`(默认 `master`)的远端 HEAD,不 checkout 工作区;解析出的完整 commit 与上一次触发过的 revision 相同则标记 `NOT_BUILT` 并结束,不触发任何下游。 -revision 变化时,调度管线把同一个完整 commit 通过 `COMMIT_HASH` 同时传给 `Genarrative-Full-Build-And-Deploy` 与 `Genarrative-Agc-Windows-Build`,两条管线都按这个 commit 检出(Full Job 继续把 `env.SOURCE_COMMIT` 透传给 Web / API / Stdb 的 Build、Publish、Deploy),因此两个产物必然来自同一个版本,不会各自解析分支 HEAD 造成漂移。两条下游管线自身不带任何定时触发器,也不在管线内部做版本比较。Full Job 默认以 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal` 对仅供开发使用的 dev 服务器执行 Stdb → API → Web 完整发布,不进入人工 rollout gate;三个下游 Build 都由 Full Job 显式传 `PUBLISH_AFTER_BUILD=false`,统一 Build 完成后仍由 Full Job 按固定顺序发布。人工维护窗口才选择 `pause-after-stdb`,且必须配置 `STDB_API_ROLLOUT_APPROVERS`。 +revision 变化时,调度管线把同一个完整 commit 通过 `COMMIT_HASH` 同时传给 `Genarrative-Full-Build-And-Deploy` 与 `Genarrative-Agc-Windows-Build`,两条管线都按这个 commit 检出(Full Job 继续把 `env.SOURCE_COMMIT` 透传给 Web / API / Stdb 的 Build、Publish、Deploy),因此两个产物必然来自同一个版本,不会各自解析分支 HEAD 造成漂移。两条下游管线自身不带任何定时触发器,也不在管线内部做版本比较。Windows 客户端发布额外按路径过滤:调度管线比较「上一轮已触发的 revision」与本次 revision 之间的变更路径,只有出现 `apps/ai-game-creator-shell/`、`packages/`、`server-rs/crates/`、`plugins/agc-cocos-editor/`、`apps/desktop-shell/src-tauri/icons/`、`package.json` 或 `package-lock.json` 时才触发 `Genarrative-Agc-Windows-Build`,纯文档或流水线自身的提交只触发 Full Build、不推高客户端版本号;判定取消或失败一律按「需要发布」处理,勾选 `FORCE_TRIGGER` 可强制两条都触发。Full Job 默认以 `DEPLOY_TARGET=development`、`STDB_API_ROLLOUT_MODE=normal` 对仅供开发使用的 dev 服务器执行 Stdb → API → Web 完整发布,不进入人工 rollout gate;三个下游 Build 都由 Full Job 显式传 `PUBLISH_AFTER_BUILD=false`,统一 Build 完成后仍由 Full Job 按固定顺序发布。人工维护窗口才选择 `pause-after-stdb`,且必须配置 `STDB_API_ROLLOUT_APPROVERS`。 调度状态是调度 Job 工作区里的 `.jenkins-last-triggered-revision`,构建描述同时回显本次 revision 与结果。工作区被清理(例如 `Wipe Out Workspace`)或状态文件缺失时,下一次运行按“版本变化”处理并触发一次,之后恢复稳定;需要重建同一版本时勾选 `FORCE_TRIGGER`。Job 按仓库内 `jenkins/scheduled-revision-trigger-job-config.xml` 创建:`scriptPath=jenkins/Jenkinsfile.scheduled-revision-trigger`、Git 入口 `ssh://git@127.0.0.1:2222/GenarrativeAI/Genarrative.git`、凭据 `genarrative-local-gitea-ssh`、`` 留空(定时器写在 Jenkinsfile 里)。推送后必须让三个 live Job 各自加载一次新 Jenkinsfile,并只读核对 `config.xml`:Full 与 AGC 不再有 cron,定时只来自新调度 Job;只改 Jenkinsfile 而不确认 live 配置时,旧 cron 仍会继续触发。 diff --git a/jenkins/Jenkinsfile.scheduled-revision-trigger b/jenkins/Jenkinsfile.scheduled-revision-trigger index 621a35129..e403d407a 100644 --- a/jenkins/Jenkinsfile.scheduled-revision-trigger +++ b/jenkins/Jenkinsfile.scheduled-revision-trigger @@ -21,6 +21,7 @@ pipeline { FULL_BUILD_JOB_NAME = 'Genarrative-Full-Build-And-Deploy' AGC_BUILD_JOB_NAME = 'Genarrative-Agc-Windows-Build' REVISION_STATE_FILE = '.jenkins-last-triggered-revision' + AGC_SCOPE_CACHE_DIR = '.agc-release-scope-cache' } parameters { @@ -57,6 +58,63 @@ pipeline { } } + // 只有在「本轮到达的提交」里出现 AGC 相关路径时,Windows 客户端才发布新版本; + // 纯文档或流水线自身的提交仍然触发 Full Build,但不再推高客户端版本号。 + stage('Resolve AGC Release Scope') { + when { + expression { return env.REVISION_CHANGED == 'true' } + } + steps { + withCredentials([sshUserPrivateKey(credentialsId: env.GIT_REMOTE_CREDENTIAL_ID, keyFileVariable: 'GENARRATIVE_GIT_SSH_KEY')]) { + script { + // 判定失败一律按「需要发布」处理,避免这段逻辑影响其它下游管线。 + def scope = 'changed' + try { + scope = sh(script: '''#!/usr/bin/env bash + set -uo pipefail + export GIT_SSH_COMMAND="ssh -i ${GENARRATIVE_GIT_SSH_KEY:?缺少 Git SSH 凭据} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" + previous="$(cat "${REVISION_STATE_FILE}" 2>/dev/null || true)" + if [[ -z "${previous}" ]]; then + echo changed + exit 0 + fi + mkdir -p "${AGC_SCOPE_CACHE_DIR}" + if [[ ! -d "${AGC_SCOPE_CACHE_DIR}/.git" ]]; then + git -C "${AGC_SCOPE_CACHE_DIR}" init --quiet + git -C "${AGC_SCOPE_CACHE_DIR}" remote add origin "${GIT_REMOTE_URL}" 2>/dev/null || true + fi + refspec="+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}" + if ! git -C "${AGC_SCOPE_CACHE_DIR}" fetch --quiet --depth=200 --no-tags --filter=blob:none origin "${refspec}"; then + git -C "${AGC_SCOPE_CACHE_DIR}" fetch --quiet --depth=200 --no-tags origin "${refspec}" || { echo changed; exit 0; } + fi + if ! git -C "${AGC_SCOPE_CACHE_DIR}" cat-file -e "${previous}^{commit}" 2>/dev/null; then + echo "浅取窗口内没有 ${previous},按需要发布处理" >&2 + echo changed + exit 0 + fi + changed_paths="$(git -C "${AGC_SCOPE_CACHE_DIR}" diff --name-only "${previous}" "${REMOTE_REVISION}" 2>/dev/null || true)" + while IFS= read -r changed_path; do + [[ -z "${changed_path}" ]] && continue + case "${changed_path}" in + apps/ai-game-creator-shell/*|packages/*|server-rs/crates/*|plugins/agc-cocos-editor/*|apps/desktop-shell/src-tauri/icons/*|package.json|package-lock.json) + echo changed + exit 0 + ;; + esac + done <<< "${changed_paths}" + echo unchanged + ''', returnStdout: true).trim() + } catch (error) { + echo "AGC 发布范围判定失败,按需要发布处理:${error}" + scope = 'changed' + } + env.AGC_RELEASE_SCOPE = (scope == 'unchanged') ? 'unchanged' : 'changed' + echo "AGC 发布范围:${env.AGC_RELEASE_SCOPE}(上一轮已触发 revision=${env.LAST_TRIGGERED_REVISION ?: '无'})" + } + } + } + } + stage('Trigger Downstream Pipelines') { when { expression { return env.REVISION_CHANGED == 'true' } @@ -71,9 +129,17 @@ pipeline { string(name: 'DATABASE_BACKUP_MODE', value: 'skip'), ] build job: env.FULL_BUILD_JOB_NAME, wait: false, propagate: false, parameters: pinnedParameters - build job: env.AGC_BUILD_JOB_NAME, wait: false, propagate: false, parameters: pinnedParameters + def agcTriggered = false + if (params.FORCE_TRIGGER || env.AGC_RELEASE_SCOPE != 'unchanged') { + build job: env.AGC_BUILD_JOB_NAME, wait: false, propagate: false, parameters: pinnedParameters + agcTriggered = true + } else { + echo "本轮提交不含 AGC 相关路径,跳过 ${env.AGC_BUILD_JOB_NAME};需要强制发布时勾选 FORCE_TRIGGER" + } writeFile file: env.REVISION_STATE_FILE, text: pinnedRevision - currentBuild.description = "已触发 ${env.FULL_BUILD_JOB_NAME} 与 ${env.AGC_BUILD_JOB_NAME}:${env.SOURCE_BRANCH}@${pinnedRevision.take(12)}" + currentBuild.description = agcTriggered + ? "已触发 ${env.FULL_BUILD_JOB_NAME} 与 ${env.AGC_BUILD_JOB_NAME}:${env.SOURCE_BRANCH}@${pinnedRevision.take(12)}" + : "已触发 ${env.FULL_BUILD_JOB_NAME}(AGC 渠道未发布:本次提交不含 AGC 相关路径):${env.SOURCE_BRANCH}@${pinnedRevision.take(12)}" echo currentBuild.description } } diff --git a/jenkins/scheduled-revision-trigger-job-config.xml b/jenkins/scheduled-revision-trigger-job-config.xml index 70a48e081..5b9e25f3a 100644 --- a/jenkins/scheduled-revision-trigger-job-config.xml +++ b/jenkins/scheduled-revision-trigger-job-config.xml @@ -1,7 +1,7 @@ - 按小时检查源码分支版本,只有版本变化时用同一个 commit 触发 Full Build 与 AGC Windows Build。 + 按小时检查源码分支版本,只有版本变化时用同一个 commit 触发 Full Build;AGC Windows Build 额外按变更路径过滤,只有本轮提交触及客户端相关路径时才触发。 false