修复渠道版本链回退并记录高水位规则
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell 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 shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell 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 shard 1/4 (push) Has been cancelled
- 发布脚本版本来源改为渠道清单与旧协议迁移指针取较大值,避免渠道启用初期把版本链改小 - 旧迁移指针只服务 dev-win,其它渠道不参与比较,旧指针 404 后自动只剩渠道清单 - 补充三条定向用例:旧指针高水位、两者取高、非 Windows 渠道不读旧指针 - 主规范记录版本高水位规则与 2026-09-17 首次渠道发布 0.1.57 退回 0.1.48、后以 0.1.60 纠偏的事实
This commit is contained in:
@@ -143,27 +143,57 @@ export function resolveManifestPlatformKeys(target = releaseTarget) {
|
||||
throw new Error(`不支持的发布目标:${target}`);
|
||||
}
|
||||
|
||||
async function readRemoteVersion(channel = resolveReleaseChannel()) {
|
||||
const manifestUrl = updateManifestUrl(channel);
|
||||
/** 旧协议迁移指针:只在迁移窗口内存在,是历史版本高水位的来源。 */
|
||||
function legacyBridgeManifestUrl() {
|
||||
return `${ossBaseUrl()}/latest.json`;
|
||||
}
|
||||
|
||||
async function readManifestVersion(manifestUrl, label) {
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(manifestUrl, {
|
||||
headers: { Accept: 'application/json' },
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`读取 OSS 渠道清单失败:${error.message}`);
|
||||
throw new Error(`读取 ${label} 失败:${error.message}`);
|
||||
}
|
||||
if (response.status === 404) return null;
|
||||
if (!response.ok) {
|
||||
throw new Error(`读取 OSS 渠道清单失败:HTTP ${response.status}`);
|
||||
throw new Error(`读取 ${label} 失败:HTTP ${response.status}`);
|
||||
}
|
||||
let manifest;
|
||||
try {
|
||||
manifest = await response.json();
|
||||
} catch (error) {
|
||||
throw new Error(`OSS 渠道清单不是有效 JSON:${error.message}`);
|
||||
throw new Error(`${label} 不是有效 JSON:${error.message}`);
|
||||
}
|
||||
return parseVersion(manifest?.version, 'OSS渠道清单 version');
|
||||
return parseVersion(manifest?.version, `${label} version`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 版本高水位:渠道清单与旧协议迁移指针取较大值。
|
||||
*
|
||||
* 只看渠道清单会在「渠道刚启用、旧指针还停在更高版本」时把版本链改小 ——
|
||||
* 2026-09-17 首次渠道发布就是这样把 0.1.57 退回 0.1.48 的。旧指针只服务
|
||||
* Windows 渠道,其它渠道不参与比较;旧指针 404(迁移窗口结束)后自动只剩渠道清单。
|
||||
*/
|
||||
export async function resolveRemoteHighWaterVersion(
|
||||
channel = resolveReleaseChannel(),
|
||||
) {
|
||||
const channelVersion = await readManifestVersion(
|
||||
updateManifestUrl(channel),
|
||||
'OSS 渠道清单',
|
||||
);
|
||||
if (channel !== 'dev-win') return channelVersion;
|
||||
const legacyVersion = await readManifestVersion(
|
||||
legacyBridgeManifestUrl(),
|
||||
'OSS 迁移指针',
|
||||
);
|
||||
if (channelVersion == null) return legacyVersion;
|
||||
if (legacyVersion == null) return channelVersion;
|
||||
return compareVersions(channelVersion, legacyVersion) >= 0
|
||||
? channelVersion
|
||||
: legacyVersion;
|
||||
}
|
||||
|
||||
function replaceVersionLine(source, version, pattern, label) {
|
||||
@@ -174,7 +204,7 @@ function replaceVersionLine(source, version, pattern, label) {
|
||||
export async function prepareReleaseVersion() {
|
||||
const channel = resolveReleaseChannel();
|
||||
const localVersion = parseVersion(readPackageJson().version, '本地版本');
|
||||
const remoteVersion = await readRemoteVersion(channel);
|
||||
const remoteVersion = await resolveRemoteHighWaterVersion(channel);
|
||||
const requestedVersion = process.env.AGC_RELEASE_VERSION?.trim();
|
||||
const nextVersion = requestedVersion
|
||||
? parseVersion(requestedVersion, '指定版本')
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
nextPatchVersion,
|
||||
resolveManifestPlatformKeys,
|
||||
resolveReleaseChannel,
|
||||
resolveRemoteHighWaterVersion,
|
||||
selectReleaseArtifact,
|
||||
updateManifestUrl,
|
||||
} from './build-release.mjs';
|
||||
@@ -49,6 +50,22 @@ function withSignedArtifact(fileName, run) {
|
||||
}
|
||||
}
|
||||
|
||||
function jsonResponse(body, status = 200) {
|
||||
return {
|
||||
status,
|
||||
ok: status >= 200 && status < 300,
|
||||
json: async () => body,
|
||||
};
|
||||
}
|
||||
|
||||
function withStubbedFetch(handler, run) {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url) => handler(String(url));
|
||||
return Promise.resolve(run()).finally(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
}
|
||||
|
||||
test('selects an explicit release artifact when configured', () => {
|
||||
const artifactPath = fileURLToPath(
|
||||
new URL('../package.json', import.meta.url),
|
||||
@@ -173,6 +190,47 @@ test('next release version follows the higher local or channel version', () => {
|
||||
assert.equal(nextPatchVersion('0.1.12', null), '0.1.13');
|
||||
});
|
||||
|
||||
test('version high water keeps the legacy pointer during the migration window', async () => {
|
||||
await withStubbedFetch(
|
||||
(url) =>
|
||||
url.endsWith('/agc/dev-win/latest.json')
|
||||
? jsonResponse({}, 404)
|
||||
: jsonResponse({ version: '0.1.57' }),
|
||||
async () => {
|
||||
assert.equal(await resolveRemoteHighWaterVersion('dev-win'), '0.1.57');
|
||||
// 旧指针 0.1.57 已是高水位,下一次发布必须是 0.1.58,不能退回渠道本地版本。
|
||||
assert.equal(nextPatchVersion('0.1.47', '0.1.57'), '0.1.58');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('version high water takes the higher of channel and legacy pointer', async () => {
|
||||
await withStubbedFetch(
|
||||
(url) =>
|
||||
url.endsWith('/agc/dev-win/latest.json')
|
||||
? jsonResponse({ version: '0.1.60' })
|
||||
: jsonResponse({ version: '0.1.57' }),
|
||||
async () => {
|
||||
assert.equal(await resolveRemoteHighWaterVersion('dev-win'), '0.1.60');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('version high water ignores the windows migration pointer for other channels', async () => {
|
||||
await withStubbedFetch(
|
||||
(url) => {
|
||||
assert.ok(
|
||||
!url.endsWith('/agc/latest.json'),
|
||||
'non-windows channel must not read the windows migration pointer',
|
||||
);
|
||||
return jsonResponse({ version: '0.1.12' });
|
||||
},
|
||||
async () => {
|
||||
assert.equal(await resolveRemoteHighWaterVersion('dev-mac'), '0.1.12');
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('release upload forces overwrite for artifact, signature and channel pointers', () => {
|
||||
const source = readFileSync(
|
||||
new URL('./release-upload.mjs', import.meta.url),
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
- 上一条的两个键不能合成单一 `darwin-universal` 键:更新插件按运行时实际架构解析清单键(Apple Silicon 命中 `darwin-aarch64`,Intel 命中 `darwin-x86_64`),不存在自动命中 `darwin-universal` 的情形。将来真要单独发该键,必须在客户端同时设置自定义 target,否则清单里这一项永远不会被读取。
|
||||
- 构建期要求:打开 `bundle.createUpdaterArtifacts` 以生成 `.sig`;构建环境提供签名私钥与密码(私钥内容不得入库);公钥写入客户端配置。公钥在首个带更新能力的版本发布后不可更换,更换等于放弃自动更新(只能手动重装)。
|
||||
- 版本递增按渠道独立进行:发布脚本读取该渠道远端 `latest.json` 的 `version`,与本地版本取较高者递增 patch;两个渠道的版本号互不影响。
|
||||
- 版本高水位:发布脚本取「渠道清单版本」与「旧协议迁移指针版本」(迁移窗口内)中的较大值再递增。只看渠道清单会在渠道启用初期把版本链改小 —— 2026-09-17 首次渠道发布即把旧指针的 0.1.57 退回 0.1.48,随后以显式 0.1.60 纠偏;迁移窗口结束(旧指针 404)后自动只剩渠道清单,`dev-mac` 不参与旧指针比较。
|
||||
- 迁移(旧协议 → 渠道清单):
|
||||
- 迁移起点:已发布客户端(含当前线上版本)内置自研清单地址 `agc/latest.json`(sha256 格式),下载与安装由自研 Rust 命令完成。
|
||||
- 迁移策略见「未决问题与决策」。迁移完成后,自研清单解析、下载命令、下载进度事件以及为此放行的 CSP / HTTP 白名单条目按「四不写」整条删除,不留兼容分支与墓碑说明。
|
||||
|
||||
Reference in New Issue
Block a user