a5fd25f10a
- 客户端接入 tauri-plugin-updater:原生侧注册插件,删除自研更新下载命令、下载进度事件与安装器启动逻辑 - 客户端更新服务与更新提示改走官方插件接口,删除自研清单解析、版本比较与下载实现 - 更新能力只授予客户端主窗口,移除只为自研清单放行的 OSS 白名单与 CSP 连接项 - 新增构建期更新检查开关:开发态默认关闭,agc 启动不请求更新清单、不显示更新入口 - 发布脚本按渠道生成官方更新插件清单与签名,universal macOS 产物同时挂两个平台键,缺签名失败关闭 - 发布脚本按渠道上传安装包、签名与渠道清单,并为 dev-win 生成旧协议 sha256 迁移清单 - 构建期按渠道注入更新端点配置,渠道与目标平台不匹配时发布失败关闭 - Jenkins 流水线新增渠道参数与签名凭据注入,归档补充签名与迁移清单 - 新增发布上传 dry-run 开关,只打印 ossutil 命令且不回显凭据 - 更新技术方案与开发运维文档,登记 macOS 渠道落地待办
188 lines
5.9 KiB
JavaScript
188 lines
5.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { test } from 'node:test';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import {
|
|
compareVersions,
|
|
createChannelConfig,
|
|
createLegacyUpdateManifest,
|
|
createUpdateManifest,
|
|
nextPatchVersion,
|
|
resolveManifestPlatformKeys,
|
|
resolveReleaseChannel,
|
|
selectReleaseArtifact,
|
|
updateManifestUrl,
|
|
} from './build-release.mjs';
|
|
|
|
const windowsTarget = 'x86_64-pc-windows-msvc';
|
|
const universalTarget = 'universal-apple-darwin';
|
|
|
|
function withEnv(overrides, run) {
|
|
const previous = new Map();
|
|
for (const [key, value] of Object.entries(overrides)) {
|
|
previous.set(key, process.env[key]);
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
try {
|
|
return run();
|
|
} finally {
|
|
for (const [key, value] of previous) {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
function withSignedArtifact(fileName, run) {
|
|
const directory = mkdtempSync(path.join(os.tmpdir(), 'agc-release-test-'));
|
|
try {
|
|
const artifact = path.join(directory, fileName);
|
|
writeFileSync(artifact, 'installation package');
|
|
writeFileSync(`${artifact}.sig`, 'signature-content\n');
|
|
return run(artifact);
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
test('selects an explicit release artifact when configured', () => {
|
|
const artifactPath = fileURLToPath(
|
|
new URL('../package.json', import.meta.url),
|
|
);
|
|
withEnv({ AGC_UPDATE_ARTIFACT: artifactPath }, () => {
|
|
assert.equal(selectReleaseArtifact([]), artifactPath);
|
|
});
|
|
});
|
|
|
|
test('does not select unsupported files', () => {
|
|
assert.equal(
|
|
selectReleaseArtifact(['/tmp/latest.json', '/tmp/readme.txt']),
|
|
null,
|
|
);
|
|
});
|
|
|
|
test('resolves the channel from the target platform and rejects mismatches', () => {
|
|
assert.equal(resolveReleaseChannel({}, windowsTarget), 'dev-win');
|
|
assert.equal(resolveReleaseChannel({}, universalTarget), 'dev-mac');
|
|
assert.equal(
|
|
resolveReleaseChannel({ AGC_UPDATE_CHANNEL: 'dev-mac' }, universalTarget),
|
|
'dev-mac',
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
resolveReleaseChannel({ AGC_UPDATE_CHANNEL: 'dev-mac' }, windowsTarget),
|
|
/只能用于 darwin 目标/u,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
resolveReleaseChannel({ AGC_UPDATE_CHANNEL: 'beta-win' }, windowsTarget),
|
|
/未知发布渠道/u,
|
|
);
|
|
});
|
|
|
|
test('channel manifest URL and build-time endpoint follow the channel', () => {
|
|
withEnv({ AGC_UPDATE_OSS_BASE_URL: undefined }, () => {
|
|
assert.equal(
|
|
updateManifestUrl('dev-win'),
|
|
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-win/latest.json',
|
|
);
|
|
assert.deepEqual(createChannelConfig('dev-mac'), {
|
|
plugins: {
|
|
updater: {
|
|
endpoints: [
|
|
'https://agc-dev.oss-rg-china-mainland.aliyuncs.com/agc/dev-mac/latest.json',
|
|
],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
test('universal macOS builds publish one artifact under both platform keys', () => {
|
|
assert.deepEqual(resolveManifestPlatformKeys(universalTarget), [
|
|
'darwin-aarch64',
|
|
'darwin-x86_64',
|
|
]);
|
|
assert.deepEqual(resolveManifestPlatformKeys(windowsTarget), [
|
|
'windows-x86_64',
|
|
]);
|
|
});
|
|
|
|
test('channel manifest carries version, platform keys and signature', () => {
|
|
withSignedArtifact('陶泥儿_0.1.48_x64-setup.exe', (artifact) => {
|
|
withEnv({ AGC_UPDATE_RELEASE_NOTES: '修复与改进' }, () => {
|
|
const manifest = createUpdateManifest(artifact, {
|
|
channel: 'dev-win',
|
|
target: windowsTarget,
|
|
publishedAt: '2026-09-17T00:00:00.000Z',
|
|
});
|
|
assert.match(manifest.version, /^\d+\.\d+\.\d+$/u);
|
|
assert.equal(manifest.notes, '修复与改进');
|
|
assert.equal(manifest.pub_date, '2026-09-17T00:00:00.000Z');
|
|
assert.deepEqual(Object.keys(manifest.platforms), ['windows-x86_64']);
|
|
assert.equal(
|
|
manifest.platforms['windows-x86_64'].signature,
|
|
'signature-content',
|
|
);
|
|
assert.match(
|
|
manifest.platforms['windows-x86_64'].url,
|
|
new RegExp(`/agc/dev-win/${manifest.version}/`, 'u'),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
test('missing signature fails the channel manifest closed', () => {
|
|
const directory = mkdtempSync(path.join(os.tmpdir(), 'agc-release-test-'));
|
|
try {
|
|
const artifact = path.join(directory, '陶泥儿_0.1.48_x64-setup.exe');
|
|
writeFileSync(artifact, 'installation package');
|
|
assert.throws(
|
|
() =>
|
|
createUpdateManifest(artifact, {
|
|
channel: 'dev-win',
|
|
target: windowsTarget,
|
|
}),
|
|
/缺少更新包签名/u,
|
|
);
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('legacy manifest keeps the sha256 contract of published clients', () => {
|
|
withSignedArtifact('陶泥儿_0.1.48_x64-setup.exe', (artifact) => {
|
|
const legacy = createLegacyUpdateManifest(artifact, {
|
|
channel: 'dev-win',
|
|
});
|
|
assert.match(legacy.version, /^\d+\.\d+\.\d+$/u);
|
|
assert.equal(legacy.sha256.length, 64);
|
|
assert.equal(legacy.size, 'installation package'.length);
|
|
assert.match(legacy.downloadUrl, /\/agc\/dev-win\/[\d.]+\//u);
|
|
});
|
|
});
|
|
|
|
test('next release version follows the higher local or channel version', () => {
|
|
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');
|
|
});
|
|
|
|
test('release upload forces overwrite for artifact, signature and channel pointers', () => {
|
|
const source = readFileSync(
|
|
new URL('./release-upload.mjs', import.meta.url),
|
|
'utf8',
|
|
);
|
|
assert.equal(
|
|
(source.match(/runOssutil\(\[\s*'cp',\s*'--force'/gu) ?? []).length,
|
|
4,
|
|
);
|
|
assert.match(source, /agc\/\$\{channel\}\/latest\.json/u);
|
|
assert.match(source, /agc\/latest\.json/u);
|
|
});
|