e3682fd06f
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 7m31s
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 7m32s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 7m33s
Project CI / AI game creator shell Rust shard 2/4 (push) Successful in 7m40s
Project CI / AI game creator shell Rust smoke (push) Successful in 2m21s
Project CI / AI game creator shell Rust crates (push) Successful in 3m11s
Project CI / Native shell tests (push) Successful in 16m15s
Project CI / Frontend tests (push) Successful in 14m57s
Project CI / Repository checks (push) Successful in 14m48s
Project CI / Backend tests (push) Successful in 20m7s
Project CI / AI game creator shell web tests (push) Successful in 6m20s
移除服务器选择并按来源隔离登录凭据 新增官网客户端下载入口和匿名平台聚合接口 根据发布清单自动展示Windows与macOS首装包 补齐Mac首装元数据和上传顺序校验 同步定向测试与下载发布规范
198 lines
5.9 KiB
JavaScript
198 lines
5.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { test } from 'node:test';
|
|
|
|
import {
|
|
createReleaseUploadPlan,
|
|
formatOssutilCommand,
|
|
readReleaseDryRun,
|
|
uploadReleaseArtifacts,
|
|
} from './release-oss.mjs';
|
|
|
|
test('dry run only accepts explicit truthy values', () => {
|
|
assert.equal(readReleaseDryRun({}), false);
|
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: '1' }), true);
|
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: ' true ' }), true);
|
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: '0' }), false);
|
|
assert.equal(readReleaseDryRun({ AGC_RELEASE_DRY_RUN: '' }), false);
|
|
});
|
|
|
|
test('printed upload command keeps arguments and hides credentials', () => {
|
|
const command = formatOssutilCommand({
|
|
binary: 'ossutil',
|
|
args: [
|
|
'cp',
|
|
'--force',
|
|
'陶泥儿 0.1.48.exe',
|
|
'oss://agc-dev/agc/dev-win/x.exe',
|
|
],
|
|
endpoint: 'oss-rg-china-mainland.aliyuncs.com',
|
|
credentials: true,
|
|
});
|
|
assert.match(command, /^ossutil cp --force /u);
|
|
assert.match(command, /"陶泥儿 0\.1\.48\.exe"/u);
|
|
assert.match(command, /oss:\/\/agc-dev\/agc\/dev-win\/x\.exe/u);
|
|
assert.match(
|
|
command,
|
|
/--access-key-id <redacted> --access-key-secret <redacted>/u,
|
|
);
|
|
});
|
|
|
|
function withReleaseFixture(channel, architecture, run) {
|
|
const root = mkdtempSync(path.join(os.tmpdir(), 'agc-upload-plan-'));
|
|
try {
|
|
const artifact = path.join(
|
|
root,
|
|
channel === 'dev-win'
|
|
? '陶泥儿_1.2.3_x64-setup.exe'
|
|
: '陶泥儿.app.tar.gz',
|
|
);
|
|
const downloadArtifact =
|
|
channel === 'dev-win'
|
|
? artifact
|
|
: path.join(root, `陶泥儿_1.2.3_${architecture}.dmg`);
|
|
const manifestPath = path.join(root, 'latest.json');
|
|
const legacyManifestPath =
|
|
channel === 'dev-win' ? path.join(root, 'legacy-latest.json') : null;
|
|
for (const file of [
|
|
artifact,
|
|
`${artifact}.sig`,
|
|
downloadArtifact,
|
|
manifestPath,
|
|
legacyManifestPath,
|
|
].filter(Boolean)) {
|
|
writeFileSync(file, 'fixture');
|
|
}
|
|
return run({
|
|
artifact,
|
|
downloadArtifact,
|
|
channel,
|
|
manifest: { version: '1.2.3' },
|
|
manifestPath,
|
|
legacyManifestPath,
|
|
});
|
|
} finally {
|
|
rmSync(root, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
const uploadOptions = {
|
|
bucket: 'agc-dev',
|
|
endpoint: 'oss-rg-china-mainland.aliyuncs.com',
|
|
log: () => {},
|
|
};
|
|
|
|
for (const architecture of ['aarch64', 'x64']) {
|
|
test(`uploads every ${architecture} Mac object before the channel pointer`, () => {
|
|
withReleaseFixture('dev-mac', architecture, (release) => {
|
|
const calls = [];
|
|
uploadReleaseArtifacts(release, {
|
|
...uploadOptions,
|
|
spawn: (binary, args, options) => {
|
|
assert.equal(binary, 'ossutil');
|
|
assert.equal(options.shell, false);
|
|
assert.deepEqual(args.slice(0, 2), ['cp', '--force']);
|
|
calls.push({ source: args[2], destination: args[3] });
|
|
return { status: 0 };
|
|
},
|
|
});
|
|
assert.deepEqual(
|
|
calls.map(({ source }) => source),
|
|
[
|
|
release.artifact,
|
|
`${release.artifact}.sig`,
|
|
release.downloadArtifact,
|
|
release.manifestPath,
|
|
],
|
|
);
|
|
assert.equal(
|
|
calls[2].destination,
|
|
`oss://agc-dev/agc/dev-mac/1.2.3/陶泥儿_1.2.3_${architecture}.dmg`,
|
|
);
|
|
assert.equal(
|
|
calls[3].destination,
|
|
'oss://agc-dev/agc/dev-mac/latest.json',
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
test('Windows uploads the shared installer once and publishes migration metadata last', () => {
|
|
withReleaseFixture('dev-win', 'x64', (release) => {
|
|
const plan = createReleaseUploadPlan(release, 'agc-dev');
|
|
assert.deepEqual(
|
|
plan.map(({ source }) => source),
|
|
[
|
|
release.artifact,
|
|
`${release.artifact}.sig`,
|
|
release.manifestPath,
|
|
release.legacyManifestPath,
|
|
],
|
|
);
|
|
assert.equal(plan.at(-1).destination, 'oss://agc-dev/agc/latest.json');
|
|
const calls = [];
|
|
uploadReleaseArtifacts(release, {
|
|
...uploadOptions,
|
|
spawn: (_binary, args) => {
|
|
assert.deepEqual(args.slice(0, 2), ['cp', '--force']);
|
|
calls.push(args[3]);
|
|
return { status: 0 };
|
|
},
|
|
});
|
|
assert.deepEqual(
|
|
calls,
|
|
plan.map(({ destination }) => destination),
|
|
);
|
|
});
|
|
});
|
|
|
|
for (const failedArtifactIndex of [0, 1, 2]) {
|
|
test(`failed Mac object ${failedArtifactIndex} prevents both later objects and latest publication`, () => {
|
|
withReleaseFixture('dev-mac', 'aarch64', (release) => {
|
|
const destinations = [];
|
|
assert.throws(
|
|
() =>
|
|
uploadReleaseArtifacts(release, {
|
|
...uploadOptions,
|
|
spawn: (_binary, args) => {
|
|
destinations.push(args[3]);
|
|
return {
|
|
status: destinations.length - 1 === failedArtifactIndex ? 1 : 0,
|
|
};
|
|
},
|
|
}),
|
|
/OSS 上传失败/u,
|
|
);
|
|
assert.equal(destinations.length, failedArtifactIndex + 1);
|
|
assert.ok(
|
|
destinations.every(
|
|
(destination) => !destination.endsWith('/latest.json'),
|
|
),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
test('dry run prints the complete plan without spawning uploads or exposing credentials', () => {
|
|
withReleaseFixture('dev-mac', 'aarch64', (release) => {
|
|
const output = [];
|
|
uploadReleaseArtifacts(release, {
|
|
...uploadOptions,
|
|
dryRun: true,
|
|
accessKeyId: 'fixture-id',
|
|
accessKeySecret: 'fixture-secret',
|
|
spawn: () => assert.fail('dry run must never execute ossutil'),
|
|
log: (line) => output.push(line),
|
|
});
|
|
assert.equal(
|
|
output.filter((line) => line.startsWith('[dry-run]')).length,
|
|
4,
|
|
);
|
|
assert.match(output.join('\n'), /\.dmg/u);
|
|
assert.match(output.at(-1), /未写入任何 OSS 对象/u);
|
|
assert.doesNotMatch(output.join('\n'), /fixture-id|fixture-secret|已上传/u);
|
|
});
|
|
});
|