623e007fae
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m36s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m37s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m42s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m51s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m55s
Project CI / AI game creator shell Rust crates (push) Successful in 4m15s
Project CI / Repository checks (push) Successful in 4m45s
Project CI / Frontend tests (push) Successful in 7m47s
Project CI / Native shell tests (push) Successful in 9m33s
Project CI / Backend tests (push) Successful in 10m9s
Project CI / AI game creator shell web tests (push) Successful in 4m38s
区分发布渠道与系统,支持 dev、release 和自定义渠道 允许网站通过服务端配置选择客户端下载检测渠道 接入模板库灰度权限并阻断退出和切号后的异步操作 补齐发布、下载、灰度与会话竞态测试及当前规范
131 lines
3.6 KiB
JavaScript
131 lines
3.6 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
|
import path from 'node:path';
|
|
|
|
import { resolveReleasePartition } from './build-release.mjs';
|
|
|
|
/**
|
|
* 发布上传的 OSS 命令行整理:把 ossutil 参数与凭据整理成可执行或可打印的形式,
|
|
* 便于在 dry-run 下核对将要执行的上传,同时保证任何输出都不回显凭据明文。
|
|
*/
|
|
const redactedCredential = '<redacted>';
|
|
|
|
export function readReleaseDryRun(env = process.env) {
|
|
const value = env.AGC_RELEASE_DRY_RUN?.trim().toLowerCase();
|
|
return value === '1' || value === 'true';
|
|
}
|
|
|
|
function quoteArgument(value) {
|
|
return /[\s"']/u.test(value) ? JSON.stringify(value) : value;
|
|
}
|
|
|
|
export function formatOssutilCommand({ binary, args, endpoint, credentials }) {
|
|
const parts = [binary, ...args, '--endpoint', endpoint];
|
|
if (credentials) {
|
|
parts.push(
|
|
'--access-key-id',
|
|
redactedCredential,
|
|
'--access-key-secret',
|
|
redactedCredential,
|
|
);
|
|
}
|
|
return parts.map(quoteArgument).join(' ');
|
|
}
|
|
|
|
export function createReleaseUploadPlan(
|
|
{
|
|
artifact,
|
|
downloadArtifact,
|
|
channel,
|
|
target,
|
|
manifest,
|
|
manifestPath,
|
|
legacyManifestPath,
|
|
},
|
|
bucket,
|
|
) {
|
|
if (
|
|
!artifact ||
|
|
!downloadArtifact ||
|
|
!manifestPath ||
|
|
!manifest?.version ||
|
|
!channel ||
|
|
!target
|
|
) {
|
|
throw new Error('发布结果缺少渠道、构建目标、更新包、首装包或清单');
|
|
}
|
|
const partition = resolveReleasePartition(channel, target);
|
|
if (legacyManifestPath && partition !== 'dev-win') {
|
|
throw new Error('旧协议迁移清单只属于 dev 渠道的 Windows 系统');
|
|
}
|
|
const prefix = `oss://${bucket}/agc/${partition}`;
|
|
const artifacts = [
|
|
...new Set(
|
|
[artifact, `${artifact}.sig`, downloadArtifact].map((file) =>
|
|
path.resolve(file),
|
|
),
|
|
),
|
|
];
|
|
const plan = artifacts.map((source) => ({
|
|
source,
|
|
destination: `${prefix}/${manifest.version}/${path.basename(source)}`,
|
|
}));
|
|
plan.push({ source: manifestPath, destination: `${prefix}/latest.json` });
|
|
if (legacyManifestPath) {
|
|
plan.push({
|
|
source: legacyManifestPath,
|
|
destination: `oss://${bucket}/agc/latest.json`,
|
|
});
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
export function uploadReleaseArtifacts(
|
|
release,
|
|
{
|
|
bucket,
|
|
endpoint,
|
|
binary = 'ossutil',
|
|
accessKeyId,
|
|
accessKeySecret,
|
|
dryRun = false,
|
|
spawn = spawnSync,
|
|
log = console.log,
|
|
},
|
|
) {
|
|
if (Boolean(accessKeyId) !== Boolean(accessKeySecret)) {
|
|
throw new Error('OSS AccessKey ID 和 Secret 必须同时提供');
|
|
}
|
|
const plan = createReleaseUploadPlan(release, bucket);
|
|
for (const { source, destination } of plan) {
|
|
// 全部安装对象成功后才执行 latest 指针;失败立即终止,不发布悬空链接。
|
|
const args = ['cp', '--force', source, destination];
|
|
if (dryRun) {
|
|
log(
|
|
`[dry-run] ${formatOssutilCommand({ binary, args, endpoint, credentials: Boolean(accessKeyId) })}`,
|
|
);
|
|
continue;
|
|
}
|
|
const credentials = accessKeyId
|
|
? ['--access-key-id', accessKeyId, '--access-key-secret', accessKeySecret]
|
|
: [];
|
|
const result = spawn(
|
|
binary,
|
|
[...args, '--endpoint', endpoint, ...credentials],
|
|
{
|
|
stdio: 'inherit',
|
|
shell: false,
|
|
},
|
|
);
|
|
if (result.error)
|
|
throw new Error(`无法执行 ${binary},请先安装并配置 ossutil`);
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
`OSS 上传失败(退出码 ${result.status ?? 1}):${destination}`,
|
|
);
|
|
}
|
|
log(`[ai-game-creator-shell] 已上传 ${destination}`);
|
|
}
|
|
if (dryRun) log('[ai-game-creator-shell] dry-run:未写入任何 OSS 对象');
|
|
return plan;
|
|
}
|