diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index 50f3050c7..542010560 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -1,7 +1,7 @@ { "name": "@genarrative/ai-game-creator-shell", "private": true, - "version": "0.1.12", + "version": "0.1.19", "type": "module", "scripts": { "dev": "node scripts/start-tauri-dev.mjs", @@ -9,6 +9,7 @@ "dev-stack": "node scripts/start-dev-stack.mjs", "build": "node scripts/build-release.mjs", "release:upload": "node scripts/release-upload.mjs", + "bump-version": "node scripts/bump-version.mjs", "skill-pack:check": "node scripts/check-skill-pack.mjs", "skill-pack:sync": "node scripts/check-skill-pack.mjs --write", "skill-pack:test": "node --test scripts/check-skill-pack.test.mjs", diff --git a/apps/ai-game-creator-shell/scripts/build-release.mjs b/apps/ai-game-creator-shell/scripts/build-release.mjs index 03a61ba9d..501b5b20b 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.mjs @@ -5,6 +5,7 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; const appRoot = fileURLToPath(new URL('..', import.meta.url)); +const repoRoot = path.resolve(appRoot, '../..'); const defaultReleaseTarget = 'x86_64-pc-windows-msvc'; const releaseTarget = process.env.AGC_BUILD_TARGET?.trim() || defaultReleaseTarget; @@ -49,16 +50,23 @@ function parseVersion(value, label) { return value; } -export function nextPatchVersion(localVersion, remoteVersion) { - const local = parseVersion(localVersion, '本地版本'); - const remote = - remoteVersion == null ? null : parseVersion(remoteVersion, 'OSS版本'); - const base = remote && compareVersions(remote, local) > 0 ? remote : local; - const [major, minor, patch] = base.split('.').map(Number); - if (patch === Number.MAX_SAFE_INTEGER) { - throw new Error(`版本号 patch 已达到上限:${base}`); +export function bumpVersion(current, target = 'patch') { + const [major, minor, patch] = + parseVersion(current, '当前版本').split('.').map(Number); + if (target === 'major') return `${major + 1}.0.0`; + if (target === 'minor') return `${major}.${minor + 1}.0`; + if (target === 'patch' || target == null) { + if (patch === Number.MAX_SAFE_INTEGER) { + throw new Error(`版本号 patch 已达到上限:${current}`); + } + return `${major}.${minor}.${patch + 1}`; } - return `${major}.${minor}.${patch + 1}`; + if (typeof target === 'string' && /^\d+\.\d+\.\d+$/u.test(target)) { + return parseVersion(target, '指定版本'); + } + throw new Error( + `无法识别的版本目标:${String(target)}(支持 patch / minor / major / 明确的三段版本号)`, + ); } async function readRemoteVersion() { @@ -88,14 +96,52 @@ function replaceVersionLine(source, version, pattern, label) { return source.replace(pattern, `$1${version}$3`); } -export async function prepareReleaseVersion() { - const localVersion = parseVersion(readPackageJson().version, '本地版本'); - const remoteVersion = await readRemoteVersion(); - const requestedVersion = process.env.AGC_RELEASE_VERSION?.trim(); - const nextVersion = requestedVersion - ? parseVersion(requestedVersion, '指定版本') - : nextPatchVersion(localVersion, remoteVersion); +const versionFileSources = [ + { + file: packageJsonPath, + pattern: /("version"\s*:\s*")([^"]+)(")/u, + label: 'package.json', + }, + { + file: rootPackageLockPath, + pattern: /("apps\/ai-game-creator-shell"\s*:\s*\{\s*\n\s*"name"\s*:\s*"@genarrative\/ai-game-creator-shell"\s*,\s*\n\s*"version"\s*:\s*")([^"]+)(")/u, + label: 'package-lock.json', + }, + { + file: tauriConfigPath, + pattern: /("version"\s*:\s*")([^"]+)(")/u, + label: 'tauri.conf.json', + }, + { + file: cargoManifestPath, + pattern: /(^\[package\][\s\S]*?^version\s*=\s*")([^"]+)(")/mu, + label: 'Cargo.toml', + }, + { + file: cargoLockPath, + pattern: /(^name\s*=\s*"genarrative-ai-game-creator-shell"\s*\nversion\s*=\s*")([^"]+)(")/mu, + label: 'Cargo.lock', + }, +]; +export function readLocalVersion() { + return parseVersion(readPackageJson().version, '本地版本'); +} + +export function validateVersionConsistency(expectedVersion) { + for (const source of versionFileSources) { + const match = fs.readFileSync(source.file, 'utf8').match(source.pattern); + const actual = match ? match[2].trim() : '<未找到>'; + if (actual !== expectedVersion) { + throw new Error( + `AGC 版本号不一致:${source.label} 应为 ${expectedVersion},实际 ${actual}`, + ); + } + } +} + +export function writeVersionFiles(version) { + const nextVersion = parseVersion(version, '目标版本'); const packageSource = fs.readFileSync(packageJsonPath, 'utf8'); fs.writeFileSync( packageJsonPath, @@ -151,14 +197,47 @@ export async function prepareReleaseVersion() { ), ); - console.log( - requestedVersion - ? `[ai-game-creator-shell] 使用指定版本 ${nextVersion}(本地 ${localVersion} / OSS ${remoteVersion ?? '不存在'})` - : `[ai-game-creator-shell] 版本 ${localVersion} / OSS ${remoteVersion ?? '不存在'} -> ${nextVersion}`, - ); return nextVersion; } +export function assertVersionCommitted() { + const repoPaths = versionFileSources.map((source) => + path.relative(repoRoot, source.file).replaceAll('\\', '/'), + ); + const changed = + spawnSync( + 'git', + ['diff', '--quiet', 'HEAD', '--', ...repoPaths], + { cwd: repoRoot }, + ).status === 1; + if (changed) { + throw new Error( + '版本文件相对 HEAD 存在未提交改动,请先提交后再发布:运行 npm --prefix apps/ai-game-creator-shell run bump-version -- --commit', + ); + } +} + +export function prepareReleaseVersion() { + const localVersion = readLocalVersion(); + validateVersionConsistency(localVersion); + console.log( + `[ai-game-creator-shell] 使用仓库版本 ${localVersion}(不再自动递增或自由指定版本)`, + ); + return localVersion; +} + +export async function assertVersionNotBelowOss(version) { + const remoteVersion = await readRemoteVersion(); + if (remoteVersion && compareVersions(version, remoteVersion) < 0) { + throw new Error( + `仓库版本 ${version} 低于线上 OSS 版本 ${remoteVersion}。请先运行 npm --prefix apps/ai-game-creator-shell run bump-version 提升版本后再发布。`, + ); + } + console.log( + `[ai-game-creator-shell] 线上 OSS 版本 ${remoteVersion ?? '不存在'},发布版本 ${version}`, + ); +} + export function runTauriBuild(args = []) { const noBundle = args.includes('--no-bundle'); const hasTarget = args.includes('--target'); 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 d2853082f..5e00983c9 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.test.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.test.mjs @@ -1,16 +1,19 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { test } from 'node:test'; +import { fileURLToPath } from 'node:url'; import { + bumpVersion, compareVersions, createUpdateManifest, - nextPatchVersion, selectReleaseArtifact, } from './build-release.mjs'; test('selects an explicit release artifact when configured', () => { - const artifactPath = new URL('../package.json', import.meta.url).pathname; + const artifactPath = fileURLToPath( + new URL('../package.json', import.meta.url), + ); const previous = process.env.AGC_UPDATE_ARTIFACT; process.env.AGC_UPDATE_ARTIFACT = artifactPath; try { @@ -30,7 +33,7 @@ test('does not select unsupported files', () => { test('manifest contains version, download URL and integrity fields', () => { const manifest = createUpdateManifest( - new URL('../package.json', import.meta.url).pathname, + fileURLToPath(new URL('../package.json', import.meta.url)), ); assert.match(manifest.version, /^\d+\.\d+\.\d+$/u); assert.match( @@ -46,7 +49,7 @@ test('manifest preserves multiline release notes', () => { process.env.AGC_UPDATE_RELEASE_NOTES = '第一行\n第二行\r\n第三行'; try { const manifest = createUpdateManifest( - new URL('../package.json', import.meta.url).pathname, + fileURLToPath(new URL('../package.json', import.meta.url)), ); assert.equal(manifest.releaseNotes, '第一行\n第二行\r\n第三行'); } finally { @@ -55,11 +58,14 @@ test('manifest preserves multiline release notes', () => { } }); -test('next release version follows the higher local or OSS version', () => { +test('bump version follows the requested target', () => { assert.equal(compareVersions('0.1.15', '0.1.12'), 1); - assert.equal(nextPatchVersion('0.1.12', '0.1.15'), '0.1.16'); - assert.equal(nextPatchVersion('0.1.18', '0.1.15'), '0.1.19'); - assert.equal(nextPatchVersion('0.1.12', null), '0.1.13'); + assert.equal(compareVersions('0.1.12', '0.1.12'), 0); + assert.equal(bumpVersion('0.1.19', 'patch'), '0.1.20'); + assert.equal(bumpVersion('0.1.19'), '0.1.20'); + assert.equal(bumpVersion('0.1.19', 'minor'), '0.2.0'); + assert.equal(bumpVersion('0.1.19', 'major'), '1.0.0'); + assert.equal(bumpVersion('0.1.12', '0.1.25'), '0.1.25'); }); test('release upload forces overwrite for versioned artifact and latest pointer', () => { @@ -72,3 +78,12 @@ test('release upload forces overwrite for versioned artifact and latest pointer' 2, ); }); + +test('release upload verifies the version is committed and not below OSS', () => { + const source = readFileSync( + new URL('./release-upload.mjs', import.meta.url), + 'utf8', + ); + assert.match(source, /assertVersionCommitted\(\)/u); + assert.match(source, /assertVersionNotBelowOss\(/u); +}); diff --git a/apps/ai-game-creator-shell/scripts/bump-version.mjs b/apps/ai-game-creator-shell/scripts/bump-version.mjs new file mode 100644 index 000000000..a5cb61483 --- /dev/null +++ b/apps/ai-game-creator-shell/scripts/bump-version.mjs @@ -0,0 +1,121 @@ +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const appRoot = fileURLToPath(new URL('..', import.meta.url)); +const repoRoot = path.resolve(appRoot, '../..'); + +const { + bumpVersion, + readLocalVersion, + writeVersionFiles, +} = await import('./build-release.mjs'); + +const versionFiles = [ + 'apps/ai-game-creator-shell/package.json', + 'package-lock.json', + 'apps/ai-game-creator-shell/src-tauri/tauri.conf.json', + 'apps/ai-game-creator-shell/src-tauri/Cargo.toml', + 'apps/ai-game-creator-shell/src-tauri/Cargo.lock', +]; + +function parseArgs(argv) { + const args = argv.slice(2); + const explicitIndex = args.indexOf('--version'); + const explicit = explicitIndex >= 0 ? args[explicitIndex + 1] : null; + const target = + explicit ?? + args.find((arg) => arg === 'patch' || arg === 'minor' || arg === 'major') ?? + 'patch'; + return { target, commit: args.includes('--commit') }; +} + +function runGit(args) { + const result = spawnSync('git', args, { cwd: repoRoot, stdio: 'inherit' }); + if (result.error) throw result.error; + return result.status ?? 1; +} + +function hasUncommittedVersionChanges() { + return ( + spawnSync('git', ['diff', '--quiet', '--', ...versionFiles], { + cwd: repoRoot, + }).status === 1 + ); +} + +function otherStagedFiles() { + const result = spawnSync( + 'git', + ['diff', '--cached', '--name-only'], + { cwd: repoRoot, encoding: 'utf8' }, + ); + if (result.status !== 0) { + throw new Error('无法读取已暂存文件列表'); + } + const versionSet = new Set(versionFiles); + return result.stdout + .split('\n') + .map((file) => file.trim()) + .filter(Boolean) + .filter((file) => !versionSet.has(file)); +} + +const { target, commit } = parseArgs(process.argv); + +// 提交前先确认没有其他已暂存改动,避免把无关改动一并提交;若存在则在改动任何文件前中止。 +if (commit) { + const others = otherStagedFiles(); + if (others.length > 0) { + console.error( + `[bump-version] 检测到其他已暂存改动,为避免误提交,请先单独处理后再运行 --commit:\n ${others.join('\n ')}`, + ); + console.error( + '提示:用 git restore --staged 取消暂存,或先提交这些改动。', + ); + process.exit(1); + } +} + +const current = readLocalVersion(); + +// 若版本文件已带着一次未提交的提升(例如先默认跑过一次),再 --commit 时不再重复递增,直接提交现有改动。 +const next = + commit && hasUncommittedVersionChanges() + ? current + : bumpVersion(current, target); + +writeVersionFiles(next); + +if (!commit) { + console.log( + `[bump-version] 版本 ${current} -> ${next}(已写入版本文件,未创建提交;如需提交加 --commit)`, + ); + process.exit(0); +} + +const addStatus = runGit(['add', ...versionFiles]); +if (addStatus !== 0) process.exit(addStatus); + +const hasDiff = + spawnSync('git', ['diff', '--cached', '--quiet'], { + cwd: repoRoot, + }).status === 1; + +if (!hasDiff) { + console.log(`[bump-version] 版本未变化(已是 ${current}),未创建提交`); + process.exit(0); +} + +const commitStatus = runGit([ + 'commit', + '-m', + `提升 AGC 版本至 ${next}`, + '-m', + `- AGC 客户端版本提升至 ${next}`, + '-m', + '- 同步更新 package.json、根 package-lock.json、tauri.conf.json、Cargo.toml、Cargo.lock 中的 AGC 包条目', +]); +if (commitStatus !== 0) process.exit(commitStatus); + +console.log(`[bump-version] 已提交版本 ${next}(本地提交,未推送)`); diff --git a/apps/ai-game-creator-shell/scripts/release-upload.mjs b/apps/ai-game-creator-shell/scripts/release-upload.mjs index ee5af4452..e71b27dd0 100644 --- a/apps/ai-game-creator-shell/scripts/release-upload.mjs +++ b/apps/ai-game-creator-shell/scripts/release-upload.mjs @@ -9,8 +9,13 @@ if (!/^[a-z0-9][a-z0-9.-]{1,62}$/u.test(bucket) || /[\r\n\0]/u.test(endpoint)) { } process.env.AGC_UPDATE_OSS_BASE_URL ||= `https://${bucket}.${endpoint}/agc`; -const { generateUpdateManifest, prepareReleaseVersion, runTauriBuild } = - await import('./build-release.mjs'); +const { + assertVersionCommitted, + assertVersionNotBelowOss, + generateUpdateManifest, + prepareReleaseVersion, + runTauriBuild, +} = await import('./build-release.mjs'); function runOssutil(args) { const binary = process.env.OSSUTIL_BIN?.trim() || 'ossutil'; @@ -36,7 +41,9 @@ function runOssutil(args) { if (result.status !== 0) process.exit(result.status ?? 1); } -await prepareReleaseVersion(); +const releaseVersion = await prepareReleaseVersion(); +assertVersionCommitted(); +await assertVersionNotBelowOss(releaseVersion); runTauriBuild([]); const { artifact, manifestPath, manifest } = generateUpdateManifest(); const artifactKey = `agc/${manifest.version}/${path.basename(artifact)}`; diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.lock b/apps/ai-game-creator-shell/src-tauri/Cargo.lock index f4ca27d53..4196e769b 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.lock +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.lock @@ -1703,7 +1703,7 @@ dependencies = [ [[package]] name = "genarrative-ai-game-creator-shell" -version = "0.1.12" +version = "0.1.19" dependencies = [ "agent-runtime-core", "axum", diff --git a/apps/ai-game-creator-shell/src-tauri/Cargo.toml b/apps/ai-game-creator-shell/src-tauri/Cargo.toml index 7aa0e1cb4..1d3b2b20d 100644 --- a/apps/ai-game-creator-shell/src-tauri/Cargo.toml +++ b/apps/ai-game-creator-shell/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "genarrative-ai-game-creator-shell" -version = "0.1.12" +version = "0.1.19" edition = "2021" publish = false diff --git a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json index 1eb8e0319..f9d8c1b0f 100644 --- a/apps/ai-game-creator-shell/src-tauri/tauri.conf.json +++ b/apps/ai-game-creator-shell/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Genarrative AI Game Creator", - "version": "0.1.12", + "version": "0.1.19", "identifier": "world.genarrative.ai-game-creator", "build": { "beforeDevCommand": "npm --prefix ../.. run agc:serve", diff --git a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md index 29eae7a7f..757afc474 100644 --- a/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md +++ b/docs/technical/【技术方案】AGC客户端更新检查与下载-2026-08-31.md @@ -29,21 +29,62 @@ AGC 每次启动时由根窗口检查一次公开 OSS 更新清单。清单默 ## 发布约定 -当前发布目标固定为 Windows x64 NSIS。执行 `npm run ai-game-creator-shell:build` 会先读取 -`VITE_AGC_UPDATE_MANIFEST_URL`(默认 `https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/latest.json`)的 -`latest.json`,取本地与 OSS 的较高版本并递增一个 patch,然后同步更新 package、Tauri 和 Cargo -版本后再向 Tauri 传入 `--target x86_64-pc-windows-msvc` 构建。OSS 清单首次不存在时按本地版本递增; -OSS 请求失败、清单格式错误或版本无效会终止发布,避免覆盖线上版本。构建完成后自动扫描 `.exe` -安装包,并在 `apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/latest.json` -生成包含版本、下载地址、大小和 SHA-256 的清单。可通过 `AGC_BUILD_TARGET` 显式覆盖目标(发布仍应使用 -Windows x64),通过 `AGC_UPDATE_ARTIFACT` 指定要发布的安装包,通过 `AGC_UPDATE_OSS_BASE_URL` 指定 -OSS 前缀,通过 `AGC_RELEASE_VERSION` 指定三段版本号(仅在明确需要复现指定版本时使用),通过 -`AGC_UPDATE_RELEASE_NOTES` 写入发布说明,支持多行文本且保留内部换行;`--no-bundle` smoke 构建不会读取 OSS、修改版本或生成清单。 +> ⚠️ 发布新版本时,版本号提升只发生在构建/上传工作区内,**不会自动回写仓库**。 +> 🔑 版本号以仓库为准。发布流程不再自动递增版本,也不再支持 `AGC_RELEASE_VERSION` 自由指定版本。 +> 发布前必须先运行版本提升命令,把目标版本写回仓库并提交,发布只读取这份已提交的版本。 + +**发布前先提升版本(开发者执行)** + +执行以下任一命令,会把 AGC 版本写入 `package.json`、根 `package-lock.json`、 +`tauri.conf.json`、`Cargo.toml`、`Cargo.lock` 五处。**默认只写入版本文件、不会创建提交**; +需要创建提交时在命令末尾加 `--commit`(只做本地提交,push 由开发者自行确认): + +```bash +# 提升一个 patch(默认),只写入版本文件,例如 0.1.19 -> 0.1.20 +npm run ai-game-creator-shell:bump-version +npm run ai-game-creator-shell:bump-version -- patch + +# 提升版本并本地提交 +npm run ai-game-creator-shell:bump-version -- --commit + +# 提升 minor / major +npm run ai-game-creator-shell:bump-version -- minor +npm run ai-game-creator-shell:bump-version -- major + +# 指定明确的三段版本(一般只用于复现/回填) +npm run ai-game-creator-shell:bump-version -- --version 0.1.25 + +# 指定版本并提交 +npm run ai-game-creator-shell:bump-version -- --version 0.1.25 --commit +``` + +若先用默认方式写入版本(未提交),再补 `--commit`,命令会识别到工作区已提升、直接提交现有改动, +**不会重复递增**。提交前若检测到**除版本文件外的其他已暂存改动**,会中止并提示先处理,避免误提交无关内容。 +提交标题固定为“提升 AGC 版本至 x.y.z”。 + +**构建/发布只读仓库版本(`npm run ai-game-creator-shell:build` 与 `release:upload`)** + +构建与发布都不再改写版本文件,而是读取仓库版本。前者只校验五个版本来源一致(不一致会中止, +防止漂移);后者(`release:upload`)在上传前还会做两道校验: + +1. 校验版本相对 `HEAD` 已提交(`assertVersionCommitted`):若版本文件存在未提交改动会中止, + 确保本地发布与 Jenkins 检出的是同一个版本。 +2. 通过 `assertVersionNotBelowOss` 读取 OSS `latest.json`:若仓库版本**低于**线上版本会中止, + 并提示先运行 `bump-version`,避免回退线上版本;OSS 读取失败或清单格式错误同样会中止。 + 仓库版本等于或高于线上版本才放行。 + +当前发布目标固定为 Windows x64 NSIS,会向 Tauri 传入 `--target x86_64-pc-windows-msvc` 构建。 +构建完成后自动扫描 `.exe` 安装包,并在 +`apps/ai-game-creator-shell/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/latest.json` +生成包含版本、下载地址、大小和 SHA-256 的清单,版本取仓库 `package.json` 的 `version`。 +可通过 `AGC_BUILD_TARGET` 显式覆盖目标(发布仍应使用 Windows x64),通过 `AGC_UPDATE_ARTIFACT` +指定要发布的安装包,通过 `AGC_UPDATE_OSS_BASE_URL` 指定 OSS 前缀,通过 `AGC_UPDATE_RELEASE_NOTES` +写入发布说明(支持多行文本且保留内部换行);`--no-bundle` smoke 构建不会读取 OSS、修改版本或生成清单。 每次发布安装包上传完成后,再使用 ossutil 的 `--force` 覆盖上传同一目录生成的 `latest.json`,确保固定的 latest 指针和 `downloadUrl` 指向已存在的 OSS 对象;未显式强制覆盖时,ossutil 在目标已存在时会交互询问并按默认值跳过,不能作为 Jenkins 非交互发布方式。清单和安装包均使用公开可读对象,不在清单中保存凭据、签名或本地路径。构建脚本本身不负责上传 OSS,发布流水线通过 `release:upload` 完成上传。 如需一键构建并上传,可执行 `npm run ai-game-creator-shell:release:upload`。该命令要求本机已安装并配置 `ossutil`, -先按上述规则比较 OSS 版本、递增 patch、构建 Windows x64 NSIS,再上传安装包和 `latest.json`。默认上传到 +先按上述规则取仓库版本并校验、构建 Windows x64 NSIS,再上传安装包和 `latest.json`。默认上传到 `agc-dev` / `oss-rg-china-mainland.aliyuncs.com`,也可用 `AGC_OSS_BUCKET`、`AGC_OSS_ENDPOINT` 和 `OSSUTIL_BIN` 覆盖;本机执行时凭据由 ossutil 本机配置读取,不能写入仓库或命令行参数。 @@ -63,6 +104,6 @@ Jenkins Checkout 的 `git clean -fdx` 会清理该构建目录,因此每次全 Jenkins Job 在“Build and upload”阶段通过受保护凭据 ID `AliyunAccessKeyId` 和 `AliyunaccessKeySecret` 注入 AccessKey,仅在当前进程运行时传给 ossutil,不写入仓库、workspace 或构建日志; 本机运行仍使用 ossutil 配置。凭据必须具备 `PutObject` 权限;OSS 对客户端保持公共读即可,公共读本身不授予 -Jenkins 上传权限。由于版本号取决于 OSS 当前清单,Job 已关闭并发构建;若 Jenkins -上存在多个 AGC 发布 Job,还应使用同一个 Lockable Resource 串行化发布。Job 参数 -`AGC_RELEASE_VERSION` 留空时自动递增,填写后会使用指定版本并更新对应的 `latest.json`,因此回滚或测试旧版本前应确认不会覆盖线上更新入口。 +Jenkins 上传权限。由于版本号以仓库为准、发布只读仓库版本且禁止回退线上版本,Job 已关闭并发构建;若 Jenkins +上存在多个 AGC 发布 Job,还应使用同一个 Lockable Resource 串行化发布。发布前请先在仓库上运行 +`npm run ai-game-creator-shell:bump-version` 提升并提交版本,再触发发布流水线。 diff --git a/jenkins/Jenkinsfile.ai-game-creator-shell-build b/jenkins/Jenkinsfile.ai-game-creator-shell-build index 3e304cf63..31d5568be 100644 --- a/jenkins/Jenkinsfile.ai-game-creator-shell-build +++ b/jenkins/Jenkinsfile.ai-game-creator-shell-build @@ -21,7 +21,6 @@ pipeline { parameters { string(name: 'SOURCE_BRANCH', defaultValue: 'master', description: '源码分支') string(name: 'COMMIT_HASH', defaultValue: '', description: '可选,指定属于 SOURCE_BRANCH 的 Git commit') - string(name: 'AGC_RELEASE_VERSION', defaultValue: '', description: '可选,指定三段版本号;留空则按 OSS 与本地版本自动递增 patch') text(name: 'AGC_UPDATE_RELEASE_NOTES', defaultValue: '', description: '可选,支持多行文本,写入 latest.json 的发布说明') string(name: 'OSSUTIL_BIN', defaultValue: 'ossutil', description: 'ossutil 或 ossutil.exe 的绝对路径/命令名') } @@ -127,7 +126,6 @@ pipeline { withEnv([ "PATH=${env.AGC_WINDOWS_PATH}", "OSSUTIL_BIN=${params.OSSUTIL_BIN}", - "AGC_RELEASE_VERSION=${params.AGC_RELEASE_VERSION}", "AGC_UPDATE_RELEASE_NOTES=${params.AGC_UPDATE_RELEASE_NOTES}", ]) { powershell ''' diff --git a/package-lock.json b/package-lock.json index 47cf1217e..627be402a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,7 +93,7 @@ }, "apps/ai-game-creator-shell": { "name": "@genarrative/ai-game-creator-shell", - "version": "0.1.12", + "version": "0.1.19", "dependencies": { "@cubone/react-file-manager": "^1.35.0", "@genarrative/image-canvas-core": "0.1.0", diff --git a/package.json b/package.json index 80aa34e94..ec97e50ac 100644 --- a/package.json +++ b/package.json @@ -172,6 +172,7 @@ "ai-game-creator-shell:dev-server": "npm --prefix apps/ai-game-creator-shell run dev-server", "ai-game-creator-shell:build": "npm --prefix apps/ai-game-creator-shell run build --", "ai-game-creator-shell:release:upload": "npm --prefix apps/ai-game-creator-shell run release:upload", + "ai-game-creator-shell:bump-version": "npm --prefix apps/ai-game-creator-shell run bump-version --", "ai-game-creator-shell:llm-status": "npm --prefix apps/ai-game-creator-shell run llm-status --", "ai-game-creator-shell:agent-task": "npm --prefix apps/ai-game-creator-shell run agent-task --", "agc:chat": "npm --prefix apps/ai-game-creator-shell run chat --",