按审计结论收口 M3:CI 接线、并发口径、feature 单一推导与文档对齐
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m22s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m52s
Project CI / Native shell tests (pull_request) Failing after 1m12s
Project CI / Frontend tests (pull_request) Failing after 44s
Project CI / Backend tests (pull_request) Successful in 3m28s
Project CI / Repository checks (pull_request) Successful in 1m50s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m20s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m31s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m14s

- CI:把 bundled-resources:test(13 条,跨平台、注入假执行器)接进 ai-game-creator-shell:check:web,Linux CI 即可覆盖准备步骤逻辑
- 并发:按「无实际场景不做并发支持」定论——不加锁,但替换失败改为可读错误并保留已生成的 staging;技术方案 §4.3 与风险表同步改写
- feature:新增 cargo-features.resolveEditorFeatures 作为唯一推导入口(env > 命令行 --features > 平台默认),dev 与发布入口的 cargo 参数、随包资源准备步骤共用同一集合;--no-bundle 也执行 staging(app 构建本身需要随包资源),只跳过总号发布
- 文档:pitfalls 的校验收口限定到 source 派生内容;里程碑验收清单按实况修正(命令 --、prepared 只查存在性、Windows 侧已复跑)
- 验证:准备步骤 13/13、发布入口 39/39、dev 入口 12/12、声明门禁一致
This commit is contained in:
2026-09-28 10:54:29 +08:00
parent 3b4f9b2e10
commit 3fba76fd6e
9 changed files with 80 additions and 24 deletions
@@ -11,6 +11,7 @@ import {
} from './agc-global-version.mjs';
import {
defaultEditorFeatures,
resolveEditorFeatures,
withDefaultCargoFeatures,
} from './cargo-features.mjs';
import {
@@ -435,11 +436,11 @@ function writeChannelConfigFile(channel, target, includeNodeRuntime = false) {
/// 随包资源必须在打包工具之前生成:构建脚本只做只读校验,不再生成。
export function stageBundledResources(
target,
{ prepare = prepareBundledResources } = {},
{ prepare = prepareBundledResources, features } = {},
) {
const summaries = prepare({
target,
features: new Set(defaultEditorFeatures(target)),
features: new Set(features ?? defaultEditorFeatures(target)),
profile: 'release',
log: (line) => console.log(`[ai-game-creator-shell] ${line}`),
});
@@ -465,10 +466,12 @@ export function runTauriBuild(
}
const tauriArguments = buildTauriBuildArguments(args, context.target);
const { channel, target } = context;
const features = resolveEditorFeatures({ argv: args, target });
if (!args.includes('--no-bundle')) {
stageRuntime(target);
stageBundled(target);
}
// app 构建本身就需要随包资源,因此 --no-bundle 也要 staging;只有总号发布是打包专属。
stageBundled(target, { features });
const configPath = writeChannelConfigFile(
channel,
target,
@@ -839,12 +839,20 @@ test('release stages Node before Tauri and injects its resource mapping only for
},
});
assert.deepEqual(events, ['stage', 'bundled', 'build']);
events.length = 0;
runTauriBuild(['--no-bundle', '--target', windowsTarget], context, {
stageRuntime() {
assert.fail('no-bundle must not stage resources');
assert.fail('no-bundle must not stage node runtime');
},
stageBundled() {
assert.fail('no-bundle must not stage bundled resources');
stageBundled(target, options) {
// app 构建本身就需要随包资源,因此 --no-bundle 也要 staging,
// 且必须拿到与 cargo 相同的 feature 集(避免「cargo 开、staging 没开」)。
assert.equal(target, windowsTarget);
assert.ok(
options?.features?.length > 0,
'bundled staging 必须收到 feature 集',
);
events.push('bundled');
},
spawn(_binary, args) {
const config = JSON.parse(
@@ -22,3 +22,48 @@ export function defaultEditorFeatures(target) {
? ['cocos-editor-execute', 'unity-editor-execute', 'godot-editor-execute']
: [];
}
/// 单一声明式解析:本次构建实际生效的编辑器 feature 集。
/// 解析顺序:环境变量 `AGC_DEV_CARGO_FEATURES` > 命令行 `--features` > 目标平台默认值。
/// dev 入口、发布入口的 cargo 参数与随包资源准备步骤都必须用这里的返回值,
/// 否则会出现「cargo 开了 feature、staging 没开(或反之)」的窗口。
export function resolveEditorFeatures({
argv = [],
target,
env = process.env,
} = {}) {
// 顺序:环境变量(dev 的显式覆盖)> 命令行 --features > 目标平台默认值。
const fromEnv = String(env.AGC_DEV_CARGO_FEATURES ?? '').trim();
if (fromEnv) {
return fromEnv
.split(',')
.map((name) => name.trim())
.filter(Boolean);
}
const fromArgv = featuresFromArgv(argv);
if (fromArgv) {
return fromArgv;
}
return defaultEditorFeatures(target);
}
/// 从 argv 里解析 `--features a,b` / `--features=a,b`;没有则返回 null。
export function featuresFromArgv(argv = []) {
for (let index = 0; index < argv.length; index += 1) {
const value = String(argv[index]);
if (value.startsWith('--features=')) {
return value
.slice('--features='.length)
.split(',')
.map((name) => name.trim())
.filter(Boolean);
}
if (value === '--features' && argv[index + 1] !== undefined) {
return String(argv[index + 1])
.split(',')
.map((name) => name.trim())
.filter(Boolean);
}
}
return null;
}
@@ -498,7 +498,14 @@ function stageAtomically(unitPath, builder) {
throw error;
}
rmSync(unitPath, { recursive: true, force: true });
renameSync(stagingPath, unitPath);
try {
renameSync(stagingPath, unitPath);
} catch (error) {
// 并发调用未做加锁(见技术方案 §4.3):这里只保证失败可读、且不丢已经生成好的 staging。
fail(
`替换 ${unitPath} 失败(${error.code ?? error.message}):同一资源目录可能正被另一个准备步骤进程写入;staging 已保留在 ${stagingPath},确认没有并发进程后重试`,
);
}
}
function prepareCodex({
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
import { buildLocalRustProcessEnv } from '../../../scripts/dev.mjs';
import {
defaultEditorFeatures,
resolveEditorFeatures,
withDefaultCargoFeatures,
} from './cargo-features.mjs';
import {
@@ -57,18 +57,11 @@ function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) {
// 开发和发行构建使用同一平台编辑器 feature 集合。
// 可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭。
function readDevCargoFeatures(env = process.env) {
const override = env.AGC_DEV_CARGO_FEATURES;
if (override !== undefined) {
return override
.split(',')
.map((value) => value.trim())
.filter(Boolean);
}
return defaultEditorFeatures(process.platform);
function readDevCargoFeatures(argv = []) {
return resolveEditorFeatures({ argv, target: process.platform });
}
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
function withDevCargoFeatures(argv, features = readDevCargoFeatures(argv)) {
return withDefaultCargoFeatures(argv, features);
}
@@ -1,7 +1,7 @@
// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
// 来源:build_support/package-layout.json。不要手工编辑本文件。
// 修改随包资源布局请编辑声明文件,然后运行
// npm run agc:package-layout:sync(在仓库根目录)
// npm run agc:bundled-resources:sync(在仓库根目录)
// 门禁会校验两者一致(npm run agc:typecheck 链内含 check-package-layout.mjs)。
pub const DECLARATION_SCHEMA: &str = "agc-package-layout.v1";