WIP: AGC 随包资源 staging 归位:统一跨平台构建目标与增量缓存(#519) #520
@@ -58,6 +58,8 @@ temp*build*/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/node-runtime/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/claude-agent/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/node-runtime-staging-*/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/plugins-staging-*/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/codex/*-staging-*/
|
||||
/apps/ai-game-creator-shell/.llm-drafts/
|
||||
/apps/ai-game-creator-shell/game-creator.config.local.json
|
||||
/apps/mobile-shell/.expo/
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
|
||||
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
|
||||
# “构建 -> 监听 -> 再构建”的自触发循环。
|
||||
resources/plugins/
|
||||
@@ -13,6 +13,10 @@
|
||||
"skill-pack:check": "node scripts/check-skill-pack.mjs",
|
||||
"skill-pack:sync": "node scripts/check-skill-pack.mjs --write",
|
||||
"skill-pack:test": "node --test scripts/check-skill-pack.test.mjs",
|
||||
"bundled-resources:check": "node scripts/check-package-layout.mjs",
|
||||
"bundled-resources:sync": "node scripts/check-package-layout.mjs --write",
|
||||
"bundled-resources:prepare": "node scripts/prepare-bundled-resources.mjs",
|
||||
"bundled-resources:test": "node --test scripts/prepare-bundled-resources.test.mjs",
|
||||
"llm-status": "node scripts/run-cli-with-config.mjs --llm-status",
|
||||
"agent-task": "node scripts/run-cli-with-config.mjs --agent-task",
|
||||
"config": "node scripts/game-creator-config-wizard.mjs",
|
||||
@@ -24,7 +28,7 @@
|
||||
"agent-runtime:supervisor-swarm-tool-plan-handoff-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite supervisor-swarm-tool-plan-handoff-runner-kill",
|
||||
"agent-runtime:steer-real-e2e": "node scripts/agent-runtime-steer-real-e2e.mjs",
|
||||
"agent-runtime:steer-runner-kill-real-e2e": "node scripts/agent-runtime-real-e2e.mjs --suite steer-runner-kill",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && node scripts/check-config.mjs"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit && npm run skill-pack:check && npm run bundled-resources:check && node scripts/check-config.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.285",
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from './agc-global-version.mjs';
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
readCargoTarget,
|
||||
resolveEditorFeatures,
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
@@ -19,6 +21,10 @@ import {
|
||||
resolveReleaseChannel,
|
||||
} from './channel-identity.mjs';
|
||||
import { prepareNsisToolsetForRelease } from './nsis-toolset.mjs';
|
||||
import {
|
||||
prepareBundledResources,
|
||||
supportedHostTarget,
|
||||
} from './prepare-bundled-resources.mjs';
|
||||
import { stageNodeRuntime } from './stage-node-runtime.mjs';
|
||||
|
||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
@@ -40,26 +46,7 @@ function defaultTarget() {
|
||||
}
|
||||
|
||||
function explicitBuildTarget(args) {
|
||||
let target;
|
||||
const separator = args.indexOf('--');
|
||||
const options = separator < 0 ? args : args.slice(0, separator);
|
||||
for (let index = 0; index < options.length; index += 1) {
|
||||
const argument = options[index];
|
||||
let value;
|
||||
if (argument === '--target' || argument === '-t') {
|
||||
value = options[++index];
|
||||
} else if (argument.startsWith('--target=')) {
|
||||
value = argument.slice('--target='.length);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (!value?.trim() || value.startsWith('-')) {
|
||||
throw new Error('--target 缺少有效目标');
|
||||
}
|
||||
if (target !== undefined) throw new Error('不能重复指定 --target');
|
||||
target = value.trim();
|
||||
}
|
||||
return target;
|
||||
return readCargoTarget(args);
|
||||
}
|
||||
|
||||
function validateReleaseTarget(target) {
|
||||
@@ -469,10 +456,32 @@ function writeChannelConfigFile(channel, target, includeNodeRuntime = false) {
|
||||
return configPath;
|
||||
}
|
||||
|
||||
/// 随包资源必须在打包工具之前生成:构建脚本只做只读校验,不再生成。
|
||||
export function stageBundledResources(
|
||||
target,
|
||||
{ prepare = prepareBundledResources, features } = {},
|
||||
) {
|
||||
const summaries = prepare({
|
||||
target,
|
||||
features: new Set(features ?? defaultEditorFeatures(target)),
|
||||
profile: 'release',
|
||||
log: (line) => console.log(`[ai-game-creator-shell] ${line}`),
|
||||
});
|
||||
for (const summary of summaries) {
|
||||
console.log(`[ai-game-creator-shell] ${summary}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function runTauriBuild(
|
||||
args = [],
|
||||
context = resolveReleaseContext(args),
|
||||
{ spawn = spawnSync, stageRuntime = stageNodeRuntime } = {},
|
||||
{
|
||||
spawn = spawnSync,
|
||||
stageRuntime = stageNodeRuntime,
|
||||
stageBundled = stageBundledResources,
|
||||
platform = process.platform,
|
||||
arch = process.arch,
|
||||
} = {},
|
||||
) {
|
||||
if (
|
||||
explicitBuildTarget(args) &&
|
||||
@@ -480,14 +489,35 @@ export function runTauriBuild(
|
||||
) {
|
||||
throw new Error('构建参数与发布上下文目标不一致');
|
||||
}
|
||||
const tauriArguments = buildTauriBuildArguments(args, context.target);
|
||||
const { channel, target } = context;
|
||||
if (!args.includes('--no-bundle')) stageRuntime(target);
|
||||
const configPath = writeChannelConfigFile(
|
||||
channel,
|
||||
target,
|
||||
!args.includes('--no-bundle'),
|
||||
const tauriArguments = buildTauriBuildArguments(
|
||||
args,
|
||||
context.target,
|
||||
platform,
|
||||
);
|
||||
const bundling = !args.includes('--no-bundle');
|
||||
// 无显式 target 的 no-bundle 由 Cargo 构建宿主平台,不使用默认发布目标。
|
||||
const resourceTarget =
|
||||
explicitBuildTarget(args) ||
|
||||
(bundling ? context.target : supportedHostTarget(platform, arch));
|
||||
const features = resolveEditorFeatures({
|
||||
argv: tauriArguments,
|
||||
target: resourceTarget ?? platform,
|
||||
env: {},
|
||||
});
|
||||
const { channel } = context;
|
||||
if (bundling) {
|
||||
stageRuntime(resourceTarget);
|
||||
}
|
||||
if (resourceTarget) {
|
||||
stageBundled(resourceTarget, { features });
|
||||
} else {
|
||||
console.log(
|
||||
'[ai-game-creator-shell] 当前宿主平台不参与客户端随包资源构建,跳过资源准备',
|
||||
);
|
||||
}
|
||||
// Linux smoke 不发布 updater,沿用渠道上下文;桌面宿主必须与实际构建目标一致。
|
||||
const target = resourceTarget ?? context.target;
|
||||
const configPath = writeChannelConfigFile(channel, target, bundling);
|
||||
console.log(
|
||||
`[ai-game-creator-shell] 渠道 ${channel} 端点配置:${configPath}`,
|
||||
);
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
AGC_PRODUCT_NAME,
|
||||
resolveChannelInstallIdentity,
|
||||
} from './channel-identity.mjs';
|
||||
import { supportedHostTarget } from './prepare-bundled-resources.mjs';
|
||||
|
||||
const windowsTarget = 'x86_64-pc-windows-msvc';
|
||||
const universalTarget = 'universal-apple-darwin';
|
||||
@@ -504,6 +505,8 @@ test('packaged renderer receives the same channel as the updater manifest', () =
|
||||
// 必须 stub:真实 staging 会用宿主平台(如 macOS 的 darwin/arm64)去对默认的
|
||||
// Windows 目标做一致性校验,在非 Windows 主机上直接失败——本用例只关心渠道注入。
|
||||
stageRuntime: () => {},
|
||||
// 同上:随包资源准备会读取真实上游包,本用例只关心渠道环境变量。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, _args, options) => {
|
||||
spawnOptions = options;
|
||||
return { status: 0 };
|
||||
@@ -594,6 +597,8 @@ test('explicit macOS target drives version lookup, Tauri endpoint, artifact and
|
||||
seenContexts.push(context);
|
||||
runTauriBuild(args, context, {
|
||||
stageRuntime: () => {},
|
||||
// 必须 stub:随包资源准备会读取真实上游包与仓库插件工作区,本用例只关心参数。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
const configIndex = command.lastIndexOf('--config');
|
||||
const config = JSON.parse(
|
||||
@@ -843,6 +848,7 @@ test('Windows remains the default and explicit Windows overrides macOS environme
|
||||
context,
|
||||
{
|
||||
stageRuntime: () => {},
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
assert.ok(
|
||||
command.includes(
|
||||
@@ -960,6 +966,10 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
assert.equal(target, windowsTarget);
|
||||
events.push('stage');
|
||||
},
|
||||
stageBundled(target) {
|
||||
assert.equal(target, windowsTarget);
|
||||
events.push('bundled');
|
||||
},
|
||||
spawn(_binary, args) {
|
||||
events.push('build');
|
||||
const config = JSON.parse(
|
||||
@@ -975,12 +985,24 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
assert.deepEqual(events, ['stage', 'build']);
|
||||
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(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) {
|
||||
events.push('build');
|
||||
const config = JSON.parse(
|
||||
readFileSync(args[args.lastIndexOf('--config') + 1], 'utf8'),
|
||||
);
|
||||
@@ -993,12 +1015,16 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
assert.deepEqual(events, ['bundled', 'build']);
|
||||
assert.throws(
|
||||
() =>
|
||||
runTauriBuild(['--target', windowsTarget], context, {
|
||||
stageRuntime() {
|
||||
throw new Error('missing runtime');
|
||||
},
|
||||
stageBundled() {
|
||||
assert.fail('invalid runtime must prevent bundled staging');
|
||||
},
|
||||
spawn() {
|
||||
assert.fail('invalid runtime must prevent build');
|
||||
},
|
||||
@@ -1007,6 +1033,297 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
);
|
||||
});
|
||||
|
||||
const windowsEditorFeatures = [
|
||||
'cocos-editor-execute',
|
||||
'unity-editor-execute',
|
||||
'godot-editor-execute',
|
||||
];
|
||||
|
||||
function uniqueSorted(values) {
|
||||
return [...new Set(values ?? [])].sort();
|
||||
}
|
||||
|
||||
function splitFeatureList(value) {
|
||||
return String(value ?? '')
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/// 最终命令行里 Cargo 实际会收到的 feature 集:tauri 自身的 `--features` / `-f`
|
||||
/// 与 `--` 之后的 runner 参数(tauri 会把它们追加到 cargo 命令)都算数,
|
||||
/// 随包资源准备必须拿到同一集合。
|
||||
function cargoFeaturesFromCommand(command) {
|
||||
const argv = command.slice(command.indexOf('--') + 1);
|
||||
const features = [];
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const argument = String(argv[index]);
|
||||
if (argument.startsWith('--features=')) {
|
||||
features.push(...splitFeatureList(argument.slice('--features='.length)));
|
||||
} else if (argument === '--features' || argument === '-f') {
|
||||
features.push(...splitFeatureList(argv[index + 1]));
|
||||
index += 1;
|
||||
} else if (/^-f.+/u.test(argument)) {
|
||||
features.push(...splitFeatureList(argument.slice(2)));
|
||||
}
|
||||
}
|
||||
return uniqueSorted(features);
|
||||
}
|
||||
|
||||
/**
|
||||
* PR520 的 CI 回归:`tauri build --no-bundle` 不带显式 target 时编译的是**宿主**平台,
|
||||
* 而发布上下文默认目标始终是 Windows。随包资源与 feature 集必须跟着宿主走,
|
||||
* 否则 Linux 宿主会拿着 Windows 目标去 staging(CI smoke 直接失败),
|
||||
* 还会出现「cargo 没开 feature、staging 开了」的错配。
|
||||
*/
|
||||
test('no-bundle 在无随包资源声明的宿主(Linux)既不 staging 也不注入 Windows feature', () => {
|
||||
const context = resolveReleaseContext(['--no-bundle'], {});
|
||||
assert.equal(context.target, windowsTarget);
|
||||
let command;
|
||||
withEnv({ AGC_DEV_CARGO_FEATURES: undefined }, () => {
|
||||
runTauriBuild(['--no-bundle'], context, {
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
stageRuntime() {
|
||||
assert.fail('--no-bundle 不得准备 Node 运行时');
|
||||
},
|
||||
stageBundled(target) {
|
||||
assert.fail(`Linux 宿主没有随包资源声明,不得 staging ${target}`);
|
||||
},
|
||||
spawn(_binary, args) {
|
||||
command = args;
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
});
|
||||
assert.ok(command, '宿主不受声明覆盖时仍必须完成本次构建');
|
||||
assert.ok(!command.includes('--target'), '宿主构建不得打显式 target');
|
||||
assert.deepEqual(
|
||||
cargoFeaturesFromCommand(command),
|
||||
[],
|
||||
'Linux 宿主不得注入 Windows 编辑器 feature',
|
||||
);
|
||||
});
|
||||
|
||||
test('no-bundle 在 macOS / Windows 宿主按真实宿主三元组准备随包资源', () => {
|
||||
const context = resolveReleaseContext(['--no-bundle'], {});
|
||||
for (const [platform, arch, hostTarget, expectedFeatures] of [
|
||||
['darwin', 'arm64', 'aarch64-apple-darwin', []],
|
||||
['darwin', 'x64', 'x86_64-apple-darwin', []],
|
||||
['win32', 'x64', windowsTarget, windowsEditorFeatures],
|
||||
]) {
|
||||
const label = `${platform}/${arch}`;
|
||||
let staged;
|
||||
let command;
|
||||
withEnv({ AGC_DEV_CARGO_FEATURES: undefined }, () => {
|
||||
runTauriBuild(['--no-bundle'], context, {
|
||||
platform,
|
||||
arch,
|
||||
stageRuntime() {
|
||||
assert.fail('--no-bundle 不得准备 Node 运行时');
|
||||
},
|
||||
stageBundled(target, options) {
|
||||
staged = { target, features: options?.features };
|
||||
},
|
||||
spawn(_binary, args) {
|
||||
command = args;
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
});
|
||||
assert.equal(
|
||||
staged?.target,
|
||||
hostTarget,
|
||||
`${label} 必须按宿主三元组 staging`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
uniqueSorted(staged?.features),
|
||||
uniqueSorted(expectedFeatures),
|
||||
`${label} staging feature 与宿主不一致`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
cargoFeaturesFromCommand(command),
|
||||
uniqueSorted(expectedFeatures),
|
||||
`${label} Cargo feature 与 staging 不一致`,
|
||||
);
|
||||
assert.ok(
|
||||
!command.includes('--target'),
|
||||
`${label} 宿主构建不得打显式 target`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('no-bundle 的显式 target 优先于宿主:跨平台 / 跨架构仍按参数准备', () => {
|
||||
for (const [platform, arch, target, expectedFeatures] of [
|
||||
['linux', 'x64', windowsTarget, windowsEditorFeatures],
|
||||
['darwin', 'arm64', 'x86_64-apple-darwin', []],
|
||||
]) {
|
||||
const args = ['--no-bundle', '--target', target];
|
||||
const context = resolveReleaseContext(args, {});
|
||||
const label = `${platform}/${arch} → ${target}`;
|
||||
let staged;
|
||||
let command;
|
||||
withEnv({ AGC_DEV_CARGO_FEATURES: undefined }, () => {
|
||||
runTauriBuild(args, context, {
|
||||
platform,
|
||||
arch,
|
||||
stageRuntime() {
|
||||
assert.fail('--no-bundle 不得准备 Node 运行时');
|
||||
},
|
||||
stageBundled(stageTarget, options) {
|
||||
staged = { target: stageTarget, features: options?.features };
|
||||
},
|
||||
spawn(_binary, argv) {
|
||||
command = argv;
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
});
|
||||
assert.equal(staged?.target, target, `${label} 必须按显式 target staging`);
|
||||
assert.ok(
|
||||
command.includes(target),
|
||||
`${label} 必须把显式 target 传给 Tauri`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
uniqueSorted(staged?.features),
|
||||
uniqueSorted(expectedFeatures),
|
||||
`${label} staging feature 与预期不一致`,
|
||||
);
|
||||
assert.deepEqual(
|
||||
cargoFeaturesFromCommand(command),
|
||||
uniqueSorted(expectedFeatures),
|
||||
`${label} Cargo feature 与 staging 不一致`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('打包构建(非 no-bundle)的 staging 目标跟随发布上下文而不是宿主', () => {
|
||||
const crossArch = 'x86_64-apple-darwin';
|
||||
const context = resolveReleaseContext([], { AGC_BUILD_TARGET: crossArch });
|
||||
assert.equal(context.target, crossArch);
|
||||
let runtimeTarget;
|
||||
let staged;
|
||||
let command;
|
||||
withEnv({ AGC_DEV_CARGO_FEATURES: undefined }, () => {
|
||||
runTauriBuild([], context, {
|
||||
platform: 'darwin',
|
||||
arch: 'arm64',
|
||||
stageRuntime(target) {
|
||||
runtimeTarget = target;
|
||||
},
|
||||
stageBundled(target, options) {
|
||||
staged = { target, features: options?.features };
|
||||
},
|
||||
spawn(_binary, args) {
|
||||
command = args;
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
});
|
||||
assert.equal(runtimeTarget, crossArch);
|
||||
assert.equal(
|
||||
staged?.target,
|
||||
crossArch,
|
||||
'打包构建必须按发布上下文目标 staging',
|
||||
);
|
||||
assert.deepEqual(uniqueSorted(staged?.features), []);
|
||||
assert.ok(
|
||||
command.includes(crossArch),
|
||||
'打包构建必须把发布上下文目标传给 Tauri',
|
||||
);
|
||||
assert.deepEqual(cargoFeaturesFromCommand(command), []);
|
||||
});
|
||||
|
||||
test('staging feature 与最终 Cargo feature 一致:-f / --features / -- 之后的 runner 参数', () => {
|
||||
const context = resolveReleaseContext(['--no-bundle'], {});
|
||||
const cases = [
|
||||
{
|
||||
args: ['--no-bundle', '--features', 'custom-editor-feature'],
|
||||
expected: ['custom-editor-feature'],
|
||||
},
|
||||
{
|
||||
args: ['--no-bundle', '--features=custom-a,custom-b'],
|
||||
expected: ['custom-a', 'custom-b'],
|
||||
},
|
||||
{
|
||||
args: ['--no-bundle', '-f', 'custom-editor-feature'],
|
||||
expected: ['custom-editor-feature'],
|
||||
},
|
||||
{
|
||||
args: ['--no-bundle', '-fcustom-editor-feature'],
|
||||
expected: ['custom-editor-feature'],
|
||||
},
|
||||
// `--` 之后的参数是 runner 参数:tauri 会把它们追加到 cargo 命令行
|
||||
// (`tauri build --help`:Command line arguments passed to the runner),
|
||||
// 因此 Cargo 最终拿到的是「注入的默认值 ∪ runner 参数」,staging 必须一致。
|
||||
{
|
||||
args: ['--no-bundle', '--', '--features=app-runner'],
|
||||
expected: [...windowsEditorFeatures, 'app-runner'],
|
||||
},
|
||||
// 环境变量泄漏(Jenkins Job 参数)不得让 staging 与 Cargo 分叉。
|
||||
{
|
||||
args: ['--no-bundle'],
|
||||
env: { AGC_DEV_CARGO_FEATURES: 'cocos-editor-execute' },
|
||||
},
|
||||
];
|
||||
for (const entry of cases) {
|
||||
const { args, expected } = entry;
|
||||
const label = args.join(' ');
|
||||
let staged;
|
||||
let command;
|
||||
withEnv({ AGC_DEV_CARGO_FEATURES: undefined, ...entry.env }, () => {
|
||||
runTauriBuild(args, context, {
|
||||
platform: 'win32',
|
||||
arch: 'x64',
|
||||
stageRuntime() {
|
||||
assert.fail(`${label} 不得准备 Node 运行时`);
|
||||
},
|
||||
stageBundled(target, options) {
|
||||
staged = { target, features: options?.features };
|
||||
},
|
||||
spawn(_binary, argv) {
|
||||
command = argv;
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
});
|
||||
assert.equal(staged?.target, windowsTarget, label);
|
||||
const cargoFeatures = cargoFeaturesFromCommand(command);
|
||||
assert.deepEqual(
|
||||
uniqueSorted(staged?.features),
|
||||
cargoFeatures,
|
||||
`${label}:staging feature 必须等于 Cargo 收到的 feature`,
|
||||
);
|
||||
if (expected) {
|
||||
assert.deepEqual(
|
||||
cargoFeatures,
|
||||
uniqueSorted(expected),
|
||||
`${label}:Cargo feature 与预期不一致`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('未注入 platform / arch 时按真实 process 平台取宿主随包目标', () => {
|
||||
const hostTarget = supportedHostTarget(process.platform, process.arch);
|
||||
const context = resolveReleaseContext(['--no-bundle'], {});
|
||||
const events = [];
|
||||
withEnv({ AGC_DEV_CARGO_FEATURES: undefined }, () => {
|
||||
runTauriBuild(['--no-bundle'], context, {
|
||||
stageRuntime() {
|
||||
assert.fail('--no-bundle 不得准备 Node 运行时');
|
||||
},
|
||||
stageBundled(target) {
|
||||
events.push(target);
|
||||
},
|
||||
spawn() {
|
||||
return { status: 0 };
|
||||
},
|
||||
});
|
||||
});
|
||||
assert.deepEqual(events, hostTarget ? [hostTarget] : []);
|
||||
});
|
||||
|
||||
test('channel manifest carries version, platform keys and signature', () => {
|
||||
withSignedArtifact('陶泥儿_0.1.48_x64-setup.exe', (artifact) => {
|
||||
withEnv({ AGC_UPDATE_RELEASE_NOTES: '修复与改进' }, () => {
|
||||
@@ -1111,6 +1428,8 @@ for (const channel of ['release', 'beta-2']) {
|
||||
);
|
||||
runTauriBuild([`--target=${target}`], context, {
|
||||
stageRuntime: () => {},
|
||||
// 必须 stub:随包资源准备会读取真实上游包与仓库插件工作区,本用例只关心参数。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
const config = JSON.parse(
|
||||
readFileSync(
|
||||
|
||||
@@ -1,24 +1,134 @@
|
||||
/** 默认桌面能力;显式 feature 参数优先,不把应用参数当 Cargo 参数。 */
|
||||
export function withDefaultCargoFeatures(argv, features) {
|
||||
const separator = argv.indexOf('--');
|
||||
const cargoArgs = separator < 0 ? argv : argv.slice(0, separator);
|
||||
if (
|
||||
!features.length ||
|
||||
cargoArgs.some(
|
||||
(value) =>
|
||||
value === '--features' ||
|
||||
value === '-f' ||
|
||||
value.startsWith('--features=') ||
|
||||
/^-f.+/u.test(value),
|
||||
)
|
||||
) {
|
||||
// 只看第一个 `--` 之前的 Tauri 选项:runner 参数区里的 `--features` 会与这里注入的
|
||||
// 默认值在 Cargo 侧合并,不能当作「用户已经指定过了」而跳过注入。
|
||||
if (!features.length || tauriOptions(argv).some(hasExplicitFeatures)) {
|
||||
return argv;
|
||||
}
|
||||
return [`--features=${features.join(',')}`, ...argv];
|
||||
}
|
||||
|
||||
/// 第一个 `--` 之前是 Tauri 自己的选项,之后是 runner(Cargo)参数。
|
||||
function tauriOptions(argv) {
|
||||
const separator = argv.indexOf('--');
|
||||
return separator < 0 ? argv : argv.slice(0, separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* 交给 Tauri 与 Cargo 的参数区间:第一个 `--` 之前是 Tauri 选项,之后到第二个 `--` 是
|
||||
* runner(Cargo)参数;第二个 `--` 之后由 Tauri 转交应用,不属于构建参数。
|
||||
* dev 入口(`buildTauriArguments` 会把启动器的单个 `--` 补成 runner + 应用两段)与发布
|
||||
* 入口都用这一份区间解析,两边的目标与 feature 必须来自同一个口径。
|
||||
*/
|
||||
function cargoArguments(argv) {
|
||||
const separator = argv.indexOf('--');
|
||||
if (separator < 0) return argv;
|
||||
const applicationStart = argv.indexOf('--', separator + 1);
|
||||
return applicationStart < 0 ? argv : argv.slice(0, applicationStart);
|
||||
}
|
||||
|
||||
/// 四种拼写(`--features x` / `--features=x` / `-f x` / `-fx`)都算显式指定。
|
||||
function hasExplicitFeatures(value) {
|
||||
return (
|
||||
value === '--features' ||
|
||||
value === '-f' ||
|
||||
value.startsWith('--features=') ||
|
||||
/^-f.+/u.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
function parseFeatureNames(raw) {
|
||||
return String(raw)
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function defaultEditorFeatures(target) {
|
||||
return target === 'win32' || target.includes('windows')
|
||||
? ['cocos-editor-execute', 'unity-editor-execute', 'godot-editor-execute']
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 显式 Cargo 目标解析:`--target <triple>` / `--target=<triple>` / `-t <triple>`。
|
||||
* 应用参数区(第二个 `--` 之后)不参与解析。未显式指定返回 undefined;取值缺失、
|
||||
* 取到另一个选项或重复指定都失败关闭。
|
||||
* dev 入口与发布入口共用这一份解析:cargo 的构建目标与随包资源 staging 目标
|
||||
* 必须来自同一个显式来源,不能一方读参数、另一方回退宿主默认值。
|
||||
*/
|
||||
export function readCargoTarget(argv = []) {
|
||||
const options = cargoArguments(argv);
|
||||
let target;
|
||||
for (let index = 0; index < options.length; index += 1) {
|
||||
const argument = String(options[index]);
|
||||
let value;
|
||||
if (argument === '--target' || argument === '-t') {
|
||||
value = options[index + 1];
|
||||
index += 1;
|
||||
} else if (argument.startsWith('--target=')) {
|
||||
value = argument.slice('--target='.length);
|
||||
} else if (argument.startsWith('-t') && argument.length > 2) {
|
||||
value = argument.slice(2).replace(/^=/u, '');
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (!value?.trim() || String(value).startsWith('-')) {
|
||||
throw new Error('--target 缺少有效目标');
|
||||
}
|
||||
if (target !== undefined) throw new Error('不能重复指定 --target');
|
||||
target = String(value).trim();
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单一声明式解析:本次构建实际生效的编辑器 feature 集。
|
||||
* 解析顺序:命令行 `--features`(Cargo 真正收到的参数,不能被环境覆盖)
|
||||
* > 环境变量 `AGC_DEV_CARGO_FEATURES`(存在即以它为准,空串即关闭默认 feature)
|
||||
* > 目标平台默认值。
|
||||
* 传入的必须是「最终交给 Tauri 的参数」:dev 入口与发布入口的 cargo 参数与随包资源
|
||||
* 准备步骤都消费这里的返回值,否则会出现「cargo 开了 feature、staging 没开(或反之)」。
|
||||
*/
|
||||
export function resolveEditorFeatures({
|
||||
argv = [],
|
||||
target,
|
||||
env = process.env,
|
||||
} = {}) {
|
||||
const fromArgv = featuresFromArgv(argv);
|
||||
if (fromArgv) {
|
||||
return fromArgv;
|
||||
}
|
||||
// 变量存在本身即声明「本次 feature 集由环境决定」:空串是有意的关闭,不是未设置。
|
||||
if (env.AGC_DEV_CARGO_FEATURES !== undefined) {
|
||||
return parseFeatureNames(env.AGC_DEV_CARGO_FEATURES);
|
||||
}
|
||||
return defaultEditorFeatures(target);
|
||||
}
|
||||
|
||||
/// 汇总 cargo 参数区里出现在任何位置的 `--features` / `-f`(Cargo 会把多个 flag 合并);
|
||||
/// 没有任何 feature flag 时返回 null。显式但为空(如 `--features=`)返回空数组。
|
||||
export function featuresFromArgv(argv = []) {
|
||||
const options = cargoArguments(argv);
|
||||
const features = [];
|
||||
let found = false;
|
||||
for (let index = 0; index < options.length; index += 1) {
|
||||
const value = String(options[index]);
|
||||
if (value.startsWith('--features=')) {
|
||||
found = true;
|
||||
features.push(...parseFeatureNames(value.slice('--features='.length)));
|
||||
} else if (value === '--features' || value === '-f') {
|
||||
found = true;
|
||||
index += 1;
|
||||
const next = options[index];
|
||||
// 变参 flag:后面不是选项时才是它的取值。
|
||||
if (next !== undefined && !String(next).startsWith('-')) {
|
||||
features.push(...parseFeatureNames(next));
|
||||
}
|
||||
} else if (value.startsWith('-f') && value.length > 2) {
|
||||
found = true;
|
||||
features.push(...parseFeatureNames(value.slice(2).replace(/^=/u, '')));
|
||||
}
|
||||
}
|
||||
return found ? features : null;
|
||||
}
|
||||
|
||||
@@ -1358,7 +1358,6 @@ for (const channel of ['release', 'beta-2']) {
|
||||
|
||||
const expectedBundledDesignAgentResources = {
|
||||
'design-agent': 'design-agent',
|
||||
'resources/claude-agent': 'claude-agent',
|
||||
...Object.fromEntries(
|
||||
[
|
||||
'codex-patch-parser',
|
||||
@@ -1387,6 +1386,7 @@ const expectedBundledWindowsResources = {
|
||||
'resources/codex/win-x64/NOTICE.md': 'coding-agent/win-x64/NOTICE.md',
|
||||
'resources/codex/win-x64/manifest.json': 'coding-agent/win-x64/manifest.json',
|
||||
'resources/plugins': 'plugins',
|
||||
'resources/claude-agent': 'claude-agent',
|
||||
};
|
||||
assert.deepEqual(
|
||||
tauriConfig.bundle?.resources,
|
||||
@@ -1399,6 +1399,13 @@ for (const key of Object.keys(tauriConfig.bundle?.resources ?? {})) {
|
||||
'AI game creator shell base Tauri config must not require Windows-only Codex resources',
|
||||
);
|
||||
}
|
||||
// 基线配置同时服务 Linux(只有 CI 会编译壳 crate):随包资源由准备步骤按目标生成,
|
||||
// 基线声明它们会让没有 node_modules 的平台在构建期就因资源缺失失败。
|
||||
if (String(key).startsWith('resources/')) {
|
||||
throw new Error(
|
||||
'AI game creator shell base Tauri config must not require platform-only bundled resources',
|
||||
);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(
|
||||
windowsTauriConfig.bundle?.resources,
|
||||
@@ -1438,6 +1445,7 @@ assert.deepEqual(
|
||||
]),
|
||||
),
|
||||
['resources/plugins', 'plugins'],
|
||||
['resources/claude-agent', 'claude-agent'],
|
||||
]),
|
||||
'macOS must bundle the complete native Codex layout and plugin workspace',
|
||||
);
|
||||
|
||||
+917
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { buildLocalRustProcessEnv } from '../../../scripts/dev.mjs';
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
readCargoTarget,
|
||||
resolveEditorFeatures,
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
@@ -10,6 +12,10 @@ import {
|
||||
resolveAgcDevEndpoint,
|
||||
withAgcDevEndpointEnv,
|
||||
} from './dev-port.mjs';
|
||||
import {
|
||||
prepareBundledResources,
|
||||
supportedHostTarget,
|
||||
} from './prepare-bundled-resources.mjs';
|
||||
import {
|
||||
isAiGameCreatorServer,
|
||||
preflightExistingVite,
|
||||
@@ -51,22 +57,51 @@ 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);
|
||||
// 可用 AGC_DEV_CARGO_FEATURES(逗号分隔)覆盖,传空串即关闭;
|
||||
// 命令行显式 `--features` / `-f` 优先于环境变量(cargo 实际收到的是命令行参数)。
|
||||
// 入参是「最终交给 Tauri 的参数」:默认平台由其中的显式 `--target` 决定,未指定才是宿主平台。
|
||||
function readDevCargoFeatures(argv = []) {
|
||||
return resolveEditorFeatures({
|
||||
argv,
|
||||
target: readCargoTarget(argv) ?? process.platform,
|
||||
});
|
||||
}
|
||||
|
||||
function withDevCargoFeatures(argv, features = readDevCargoFeatures()) {
|
||||
function withDevCargoFeatures(argv, features = readDevCargoFeatures(argv)) {
|
||||
return withDefaultCargoFeatures(argv, features);
|
||||
}
|
||||
|
||||
/// 随包资源必须在 Tauri 之前生成:构建脚本只做只读校验,不再生成资源。
|
||||
/// 命中缓存的重复调用不写任何文件,因此每次 dev 启动都会先跑一次。
|
||||
/// `argv` 是「最终交给 Tauri 的参数」;staging 目标与本次 cargo 构建目标同源:
|
||||
/// 显式 `--target` 优先,未指定时回落宿主目标;宿主平台不受声明覆盖(如 Linux)时保持跳过,
|
||||
/// 不为其新增随包资源准备。`features` 省略时按同一份参数解析,不允许「cargo 一套、staging 另一套」。
|
||||
function prepareBundledResourcesBeforeTauri(
|
||||
features,
|
||||
argv = [],
|
||||
{
|
||||
prepare = prepareBundledResources,
|
||||
log = console.log,
|
||||
hostTarget = supportedHostTarget(),
|
||||
} = {},
|
||||
) {
|
||||
const target = readCargoTarget(argv) ?? hostTarget;
|
||||
if (!target) {
|
||||
log(
|
||||
'[ai-game-creator-shell] 当前平台不受随包资源声明覆盖,跳过随包资源准备',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const summaries = prepare({
|
||||
target,
|
||||
features: new Set(features ?? readDevCargoFeatures(argv)),
|
||||
log: (line) => log(`[ai-game-creator-shell] ${line}`),
|
||||
});
|
||||
for (const summary of summaries) {
|
||||
log(`[ai-game-creator-shell] ${summary}`);
|
||||
}
|
||||
}
|
||||
|
||||
function spawnTauriCli(argv, { env = process.env } = {}) {
|
||||
return spawnChild(process.execPath, [tauriCliPath, ...argv], {
|
||||
cwd: appRoot,
|
||||
@@ -75,6 +110,17 @@ function spawnTauriCli(argv, { env = process.env } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Tauri dev 的 Cargo 直接继承启动器环境,用户级 / 仓库级 Cargo 配置里的
|
||||
/// `rustc-wrapper`(本地常见为 sccache)会在这里生效。本地 sccache daemon 状态
|
||||
/// 一旦损坏,`cargo` 的首次 rustc 探测就会失败并阻断整个 AGC 启动;因此这里复用
|
||||
/// `npm run dev` 的本地 Rust 环境规则,由脚本而不是本机 Cargo 配置决定 wrapper。
|
||||
function buildTauriDevProcessEnv(endpoint, env = process.env) {
|
||||
return buildLocalRustProcessEnv({
|
||||
...withAgcDevEndpointEnv(endpoint, env),
|
||||
[AGC_DESIGN_DEBUG_ENV]: designDebugEnabled,
|
||||
});
|
||||
}
|
||||
|
||||
async function runTauriDev(
|
||||
argv = process.argv.slice(2),
|
||||
{
|
||||
@@ -84,6 +130,7 @@ async function runTauriDev(
|
||||
spawnCli = spawnTauriCli,
|
||||
waitForCli = waitForChildTermination,
|
||||
terminateTree = terminateChildTree,
|
||||
prepareResources = prepareBundledResourcesBeforeTauri,
|
||||
} = {},
|
||||
) {
|
||||
const endpoint = await resolveDevEndpoint();
|
||||
@@ -119,6 +166,12 @@ async function runTauriDev(
|
||||
}
|
||||
|
||||
try {
|
||||
// 目标与 feature 都从「最终交给 Tauri 的参数」解析:启动器自己的 `--` 之后是应用参数,
|
||||
// 不能参与解析;注入默认 feature 也不改变这份解析结果。
|
||||
const devTauriArguments = buildTauriArguments(argv, endpoint.url);
|
||||
const devFeatures = readDevCargoFeatures(devTauriArguments);
|
||||
prepareResources(devFeatures, devTauriArguments);
|
||||
// Windows 下 Vite 会监听资源目录;必须在启动前完成目录替换,避免 rename 被占用。
|
||||
const preparation = prepareFrontend(endpoint, {
|
||||
signal: preparationAbort.signal,
|
||||
onChild(frontend) {
|
||||
@@ -131,14 +184,11 @@ async function runTauriDev(
|
||||
]);
|
||||
if (!prepared || shutdownSignal) return 1;
|
||||
const tauriArguments = buildTauriArguments(
|
||||
withDevCargoFeatures(argv),
|
||||
withDevCargoFeatures(argv, devFeatures),
|
||||
endpoint.url,
|
||||
);
|
||||
child = spawnCli(tauriArguments, {
|
||||
env: {
|
||||
...withAgcDevEndpointEnv(endpoint),
|
||||
[AGC_DESIGN_DEBUG_ENV]: designDebugEnabled,
|
||||
},
|
||||
env: buildTauriDevProcessEnv(endpoint),
|
||||
});
|
||||
const childResult = waitForCli(child);
|
||||
const outcome = await Promise.race([
|
||||
@@ -225,7 +275,9 @@ function isDirectModuleExecution() {
|
||||
|
||||
export {
|
||||
buildTauriArguments,
|
||||
buildTauriDevProcessEnv,
|
||||
isDirectModuleExecution,
|
||||
prepareBundledResourcesBeforeTauri,
|
||||
runTauriDev,
|
||||
spawnTauriCli,
|
||||
withDevCargoFeatures,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# resources/plugins 由 build.rs 从 plugins/ 复制生成,属于构建产物。
|
||||
# 它在 dev 监听范围内,重新生成会让 Tauri dev 误判为源码改动而触发
|
||||
# “构建 -> 监听 -> 再构建”的自触发循环。
|
||||
resources/plugins/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,18 @@
|
||||
//! 构建与运行共用的平台布局;只允许分发锁定原生包里的明确组件。
|
||||
//!
|
||||
//! 布局、组件白名单与版本常量来自唯一声明 `build_support/package-layout.json`
|
||||
//! (Rust 侧经 `build_support/package-layout.generated.rs` 取得编译期常量,
|
||||
//! 由 `scripts/check-package-layout.mjs` 生成并在门禁中校验一致)。
|
||||
//! 本模块只读声明,不写任何随包资源。
|
||||
|
||||
pub const VERSION: &str = "0.155.1";
|
||||
pub const CLI_VERSION: &str = "codex-cli 0.155.1";
|
||||
pub const SCHEMA: &str = "genarrative-codex-sidecar.v2";
|
||||
// 共享声明模块:构建脚本、运行期与测试各自只用到其中一部分,未用到的入口不算缺陷。
|
||||
#[allow(dead_code)]
|
||||
#[path = "package_layout.rs"]
|
||||
pub(crate) mod package_layout;
|
||||
|
||||
pub const VERSION: &str = package_layout::CODEX_VERSION;
|
||||
pub const CLI_VERSION: &str = package_layout::CODEX_CLI_VERSION;
|
||||
pub const SCHEMA: &str = package_layout::CODEX_MANIFEST_SCHEMA;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Layout {
|
||||
@@ -12,46 +22,13 @@ pub struct Layout {
|
||||
pub files: &'static [&'static str],
|
||||
}
|
||||
|
||||
const WINDOWS_FILES: &[&str] = &[
|
||||
"bin/codex.exe",
|
||||
"bin/codex-code-mode-host.exe",
|
||||
"codex-path/rg.exe",
|
||||
"codex-resources/codex-command-runner.exe",
|
||||
"codex-resources/codex-windows-sandbox-setup.exe",
|
||||
"codex-package.json",
|
||||
];
|
||||
const MAC_FILES: &[&str] = &[
|
||||
"bin/codex",
|
||||
"bin/codex-code-mode-host",
|
||||
"codex-path/rg",
|
||||
"codex-resources/zsh/bin/zsh",
|
||||
"codex-package.json",
|
||||
];
|
||||
|
||||
pub fn for_target(target: &str) -> Option<Layout> {
|
||||
match target {
|
||||
"x86_64-pc-windows-msvc" => Some(Layout {
|
||||
platform: "win32-x64",
|
||||
directory: "win-x64",
|
||||
executable: "bin/codex.exe",
|
||||
files: WINDOWS_FILES,
|
||||
}),
|
||||
"aarch64-apple-darwin" | "x86_64-apple-darwin" => Some(Layout {
|
||||
platform: if target.starts_with("aarch64") {
|
||||
"darwin-arm64"
|
||||
} else {
|
||||
"darwin-x64"
|
||||
},
|
||||
directory: if target.starts_with("aarch64") {
|
||||
"mac-native/darwin-arm64"
|
||||
} else {
|
||||
"mac-native/darwin-x64"
|
||||
},
|
||||
executable: "bin/codex",
|
||||
files: MAC_FILES,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
package_layout::codex_target(target).map(|declared| Layout {
|
||||
platform: declared.platform,
|
||||
directory: declared.directory,
|
||||
executable: declared.executable,
|
||||
files: declared.files,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -80,4 +57,11 @@ mod tests {
|
||||
assert!(for_target("aarch64-pc-windows-msvc").is_none());
|
||||
assert!(for_target("x86_64-unknown-linux-gnu").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constants_come_from_the_shared_declaration() {
|
||||
assert_eq!(VERSION, "0.155.1");
|
||||
assert_eq!(CLI_VERSION, format!("codex-cli {VERSION}"));
|
||||
assert_eq!(SCHEMA, "genarrative-codex-sidecar.v2");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//! 随包阶段的原生包元数据校验,不进入运行时生产模块。
|
||||
|
||||
use super::codex_bundle::{Layout, VERSION};
|
||||
|
||||
pub fn validate_package_metadata(
|
||||
metadata: &serde_json::Value,
|
||||
target: &str,
|
||||
layout: Layout,
|
||||
) -> Result<(), String> {
|
||||
if metadata["layoutVersion"] == 1
|
||||
&& metadata["version"] == VERSION
|
||||
&& metadata["target"] == target
|
||||
&& metadata["entrypoint"] == layout.executable
|
||||
&& metadata["resourcesDir"] == "codex-resources"
|
||||
&& metadata["pathDir"] == "codex-path"
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Codex 原生包版本、布局或架构不匹配目标 {target}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::codex_bundle::for_target;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn metadata_rejects_version_architecture_and_layout_drift() {
|
||||
let target = "aarch64-apple-darwin";
|
||||
let layout = for_target(target).unwrap();
|
||||
let valid = serde_json::json!({
|
||||
"layoutVersion": 1,
|
||||
"version": VERSION,
|
||||
"target": target,
|
||||
"entrypoint": "bin/codex",
|
||||
"resourcesDir": "codex-resources",
|
||||
"pathDir": "codex-path",
|
||||
});
|
||||
assert!(validate_package_metadata(&valid, target, layout).is_ok());
|
||||
for (key, value) in [
|
||||
("layoutVersion", serde_json::json!(2)),
|
||||
("version", serde_json::json!("0.0.0")),
|
||||
("target", serde_json::json!("x86_64-apple-darwin")),
|
||||
("entrypoint", serde_json::json!("bin/codex.exe")),
|
||||
("resourcesDir", serde_json::json!("../private")),
|
||||
("pathDir", serde_json::json!(null)),
|
||||
] {
|
||||
let mut invalid = valid.clone();
|
||||
invalid[key] = value;
|
||||
assert!(
|
||||
validate_package_metadata(&invalid, target, layout).is_err(),
|
||||
"{key}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
|
||||
pub const BUNDLE_FILES: [&str; 4] = [
|
||||
"bin/win-x64/agc_godot_editor.dll",
|
||||
"bin/win-x64/metadata.json",
|
||||
"vendor/LICENSE.txt",
|
||||
"vendor/provenance.json",
|
||||
];
|
||||
// 共享随包资源声明:Godot 随包文件清单与构建期校验共用同一份来源。
|
||||
#[allow(dead_code)]
|
||||
#[path = "package_layout.rs"]
|
||||
mod package_layout;
|
||||
|
||||
pub const BUNDLE_FILES: &[&str] = package_layout::GODOT_BUNDLE_FILES;
|
||||
|
||||
fn plain_metadata(path: &Path) -> Result<fs::Metadata, String> {
|
||||
let metadata = fs::symlink_metadata(path)
|
||||
@@ -74,39 +74,6 @@ pub fn validate(root: &Path) -> Result<Vec<(&'static str, Vec<u8>)>, String> {
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
pub fn stage(root: &Path, destination: &Path, target: &str, enabled: bool) -> Result<(), String> {
|
||||
if target != "x86_64-pc-windows-msvc" || !enabled {
|
||||
return Ok(());
|
||||
}
|
||||
for (relative, bytes) in validate(root)? {
|
||||
let path = destination.join(relative);
|
||||
fs::create_dir_all(path.parent().expect("Godot resource parent"))
|
||||
.map_err(|error| format!("创建 Godot 资源目录失败:{error}"))?;
|
||||
fs::write(&path, bytes).map_err(|error| format!("写入 Godot 资源失败:{error}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn source_files(root: &Path) -> Result<Vec<PathBuf>, String> {
|
||||
plain_metadata(root)?;
|
||||
let mut sources = Vec::new();
|
||||
for entry in fs::read_dir(root).map_err(|error| format!("读取 Godot 源码失败:{error}"))?
|
||||
{
|
||||
let entry = entry.map_err(|error| format!("读取 Godot 源码目录项失败:{error}"))?;
|
||||
if matches!(entry.file_name().to_str(), Some("bin" | ".build")) {
|
||||
continue;
|
||||
}
|
||||
let metadata = plain_metadata(&entry.path())?;
|
||||
if metadata.is_dir() {
|
||||
sources.extend(source_files(&entry.path())?);
|
||||
} else if metadata.is_file() {
|
||||
sources.push(entry.path());
|
||||
}
|
||||
}
|
||||
sources.sort();
|
||||
Ok(sources)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -134,82 +101,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stage_only_verified_windows_runtime_and_not_build_inputs() {
|
||||
let source = tempfile::tempdir().unwrap();
|
||||
let destination = tempfile::tempdir().unwrap();
|
||||
fixture(source.path());
|
||||
fs::write(source.path().join("bridge.gd"), "source").unwrap();
|
||||
fs::write(source.path().join("bin/win-x64/extra.dll"), "excluded").unwrap();
|
||||
stage(
|
||||
source.path(),
|
||||
destination.path(),
|
||||
"x86_64-pc-windows-msvc",
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
fn validate_rejects_incomplete_bundle() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let error = validate(temp.path()).expect_err("空目录必须被拒绝");
|
||||
assert!(!error.is_empty(), "失败原因不能为空");
|
||||
|
||||
// 只有文件名、内容不合法的「像样」目录也必须被拒绝。
|
||||
for relative in BUNDLE_FILES {
|
||||
assert_eq!(
|
||||
fs::read(source.path().join(relative)).unwrap(),
|
||||
fs::read(destination.path().join(relative)).unwrap()
|
||||
);
|
||||
let file = temp.path().join(relative);
|
||||
fs::create_dir_all(file.parent().expect("parent")).expect("create dir");
|
||||
fs::write(&file, b"not a real artifact").expect("write file");
|
||||
}
|
||||
assert!(!destination.path().join("bridge.gd").exists());
|
||||
assert!(!destination.path().join("bin/win-x64/extra.dll").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_or_disabled_targets_need_no_native_artifacts() {
|
||||
let destination = tempfile::tempdir().unwrap();
|
||||
for (target, enabled) in [
|
||||
("aarch64-apple-darwin", true),
|
||||
("x86_64-apple-darwin", true),
|
||||
("x86_64-unknown-linux-gnu", true),
|
||||
("aarch64-pc-windows-msvc", true),
|
||||
("x86_64-pc-windows-msvc", false),
|
||||
] {
|
||||
stage(
|
||||
Path::new("missing-godot-native"),
|
||||
destination.path(),
|
||||
target,
|
||||
enabled,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(fs::read_dir(destination.path()).unwrap().count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_or_tampered_bundle_fails_before_copying() {
|
||||
let source = tempfile::tempdir().unwrap();
|
||||
let destination = tempfile::tempdir().unwrap();
|
||||
fixture(source.path());
|
||||
fs::write(source.path().join(BUNDLE_FILES[0]), b"tampered").unwrap();
|
||||
assert!(stage(
|
||||
source.path(),
|
||||
destination.path(),
|
||||
"x86_64-pc-windows-msvc",
|
||||
true
|
||||
)
|
||||
.unwrap_err()
|
||||
.contains("SHA256"));
|
||||
assert_eq!(fs::read_dir(destination.path()).unwrap().count(), 0);
|
||||
fixture(source.path());
|
||||
fs::remove_file(source.path().join("vendor/LICENSE.txt")).unwrap();
|
||||
assert!(validate(source.path()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn source_watch_list_excludes_build_outputs() {
|
||||
let source = tempfile::tempdir().unwrap();
|
||||
fixture(source.path());
|
||||
fs::create_dir(source.path().join(".build")).unwrap();
|
||||
fs::write(source.path().join(".build/bridge.obj"), "generated").unwrap();
|
||||
fs::write(source.path().join("bridge.gd"), "source").unwrap();
|
||||
let sources = source_files(source.path()).unwrap();
|
||||
assert_eq!(sources.len(), 3);
|
||||
assert!(sources.contains(&source.path().join("bridge.gd")));
|
||||
assert!(!sources.iter().any(|path| path
|
||||
.components()
|
||||
.any(|component| component.as_os_str() == "bin" || component.as_os_str() == ".build")));
|
||||
assert!(
|
||||
validate(temp.path()).is_err(),
|
||||
"内容不合法的随包库必须被拒绝",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
|
||||
// 来源:build_support/package-layout.json。不要手工编辑本文件。
|
||||
// 修改随包资源布局请编辑声明文件,然后运行
|
||||
// npm run agc:bundled-resources:sync(在仓库根目录)
|
||||
// 门禁会校验两者一致(npm run agc:typecheck 链内含 check-package-layout.mjs)。
|
||||
|
||||
pub const DECLARATION_SCHEMA: &str = "agc-package-layout.v1";
|
||||
pub const LAYOUT_VERSION: u64 = 2;
|
||||
|
||||
pub const CODEX_VERSION: &str = "0.155.1";
|
||||
pub const CODEX_CLI_VERSION: &str = "codex-cli 0.155.1";
|
||||
pub const CODEX_MANIFEST_SCHEMA: &str = "genarrative-codex-sidecar.v2";
|
||||
|
||||
pub const CLAUDE_AGENT_SDK_VERSION: &str = "0.3.285";
|
||||
|
||||
pub const GODOT_BUNDLE_FILES: &[&str] = &["bin/win-x64/agc_godot_editor.dll", "bin/win-x64/metadata.json", "vendor/LICENSE.txt", "vendor/provenance.json"];
|
||||
|
||||
pub const CODEX: Codex = Codex {
|
||||
package_metadata: PackageMetadata {
|
||||
layout_version: 1,
|
||||
resources_dir: "codex-resources",
|
||||
path_dir: "codex-path",
|
||||
},
|
||||
resource_directory: "resources/codex",
|
||||
manifest_file_name: "manifest.json",
|
||||
package_metadata_file_name: "codex-package.json",
|
||||
notice_file_name: "NOTICE.md",
|
||||
source_roots: &["app", "repo"],
|
||||
source_relative_paths: &["node_modules/@openai/codex-<platform>/vendor/<target>", "node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"],
|
||||
notice_sources: &[
|
||||
NoticeSource {
|
||||
targets: &["aarch64-apple-darwin", "x86_64-apple-darwin"],
|
||||
source: "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
|
||||
preserve: false,
|
||||
},
|
||||
NoticeSource {
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
source: "resources/codex/win-x64/NOTICE.md",
|
||||
preserve: true,
|
||||
}
|
||||
],
|
||||
universal_groups: &[
|
||||
UniversalGroup {
|
||||
name: "mac-native",
|
||||
directory: "mac-native",
|
||||
targets: &["aarch64-apple-darwin", "x86_64-apple-darwin"],
|
||||
}
|
||||
],
|
||||
targets: &[
|
||||
CodexTarget {
|
||||
target: "x86_64-pc-windows-msvc",
|
||||
platform: "win32-x64",
|
||||
directory: "win-x64",
|
||||
executable: "bin/codex.exe",
|
||||
files: &["bin/codex.exe", "bin/codex-code-mode-host.exe", "codex-path/rg.exe", "codex-resources/codex-command-runner.exe", "codex-resources/codex-windows-sandbox-setup.exe", "codex-package.json"],
|
||||
},
|
||||
CodexTarget {
|
||||
target: "aarch64-apple-darwin",
|
||||
platform: "darwin-arm64",
|
||||
directory: "mac-native/darwin-arm64",
|
||||
executable: "bin/codex",
|
||||
files: &["bin/codex", "bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh", "codex-package.json"],
|
||||
},
|
||||
CodexTarget {
|
||||
target: "x86_64-apple-darwin",
|
||||
platform: "darwin-x64",
|
||||
directory: "mac-native/darwin-x64",
|
||||
executable: "bin/codex",
|
||||
files: &["bin/codex", "bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh", "codex-package.json"],
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
pub const CLAUDE_AGENT: ClaudeAgent = ClaudeAgent {
|
||||
version: "0.3.285",
|
||||
resource_directory: "resources/claude-agent",
|
||||
entry_relative_path: "agent-sidecar/src/index.mjs",
|
||||
entry_file_name: "index.mjs",
|
||||
node_modules_directory: "node_modules",
|
||||
sdk_package_name: "@anthropic-ai/claude-agent-sdk",
|
||||
sdk_entry_file_name: "sdk.mjs",
|
||||
sdk_package_metadata_file_name: "package.json",
|
||||
targets: &[
|
||||
ClaudeAgentTarget {
|
||||
target: "x86_64-pc-windows-msvc",
|
||||
runtime_package: "claude-agent-sdk-win32-x64",
|
||||
runtime_file_name: "claude.exe",
|
||||
},
|
||||
ClaudeAgentTarget {
|
||||
target: "aarch64-apple-darwin",
|
||||
runtime_package: "claude-agent-sdk-darwin-arm64",
|
||||
runtime_file_name: "claude",
|
||||
},
|
||||
ClaudeAgentTarget {
|
||||
target: "x86_64-apple-darwin",
|
||||
runtime_package: "claude-agent-sdk-darwin-x64",
|
||||
runtime_file_name: "claude",
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
pub const PLUGINS: Plugins = Plugins {
|
||||
source_directory: "plugins",
|
||||
destination_directory: "resources/plugins",
|
||||
manifest_file_name: "plugin.json",
|
||||
target_contains_any: &["windows", "apple-darwin"],
|
||||
subdirectories: &[
|
||||
Subdirectory {
|
||||
path: "src",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "panels",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "skills",
|
||||
plugin: "",
|
||||
origin: "source",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "native/payload",
|
||||
plugin: "agc-cocos-editor",
|
||||
origin: "prepared",
|
||||
target_contains: &["windows"],
|
||||
targets: &[],
|
||||
features: &["cocos-editor-injection"],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "dotnet/publish/win-x64",
|
||||
plugin: "agc-unity-editor",
|
||||
origin: "prepared",
|
||||
target_contains: &[],
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
features: &["unity-editor-execute"],
|
||||
}
|
||||
],
|
||||
library_staging: &[
|
||||
LibraryStaging {
|
||||
plugin: "agc-godot-editor",
|
||||
source_subdirectory: "native/gdextension",
|
||||
targets: &["x86_64-pc-windows-msvc"],
|
||||
features: &["godot-editor-execute"],
|
||||
layout: "godot-bundle",
|
||||
files: &["bin/win-x64/agc_godot_editor.dll", "bin/win-x64/metadata.json", "vendor/LICENSE.txt", "vendor/provenance.json"],
|
||||
}
|
||||
],
|
||||
skip_directory_names: &["target", "node_modules"],
|
||||
skip_directory_name_prefixes: &["."],
|
||||
skip_file_name_prefixes: &["."],
|
||||
skip_file_name_fragments: &[".test."],
|
||||
};
|
||||
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"schema": "agc-package-layout.v1",
|
||||
"description": "AGC 随包资源布局与复制规则的唯一声明。Rust 侧构建期校验与 Node 侧准备步骤共用本文件,任何一侧都不得再写第二份布局或组件白名单。含 <platform>、<target> 占位符的字段由调用方按目标三元展开。修改布局时同步递增 layoutVersion(准备步骤的缓存 key 组成部分)。",
|
||||
"layoutVersion": 2,
|
||||
"codex": {
|
||||
"version": "0.155.1",
|
||||
"cliVersionPrefix": "codex-cli ",
|
||||
"manifestSchema": "genarrative-codex-sidecar.v2",
|
||||
"packageMetadata": {
|
||||
"layoutVersion": 1,
|
||||
"resourcesDir": "codex-resources",
|
||||
"pathDir": "codex-path"
|
||||
},
|
||||
"resourceDirectory": "resources/codex",
|
||||
"manifestFileName": "manifest.json",
|
||||
"packageMetadataFileName": "codex-package.json",
|
||||
"noticeFileName": "NOTICE.md",
|
||||
"sourceRoots": ["app", "repo"],
|
||||
"sourceRelativePaths": [
|
||||
"node_modules/@openai/codex-<platform>/vendor/<target>",
|
||||
"node_modules/@openai/codex/node_modules/@openai/codex-<platform>/vendor/<target>"
|
||||
],
|
||||
"noticeSources": [
|
||||
{
|
||||
"targets": ["aarch64-apple-darwin", "x86_64-apple-darwin"],
|
||||
"source": "resources/codex/【声明】Mac内置Codex组件-2026-09-18.md",
|
||||
"preserve": false
|
||||
},
|
||||
{
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"source": "resources/codex/win-x64/NOTICE.md",
|
||||
"preserve": true
|
||||
}
|
||||
],
|
||||
"universalGroups": [
|
||||
{
|
||||
"name": "mac-native",
|
||||
"directory": "mac-native",
|
||||
"targets": ["aarch64-apple-darwin", "x86_64-apple-darwin"]
|
||||
}
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"target": "x86_64-pc-windows-msvc",
|
||||
"platform": "win32-x64",
|
||||
"directory": "win-x64",
|
||||
"executable": "bin/codex.exe",
|
||||
"files": [
|
||||
"bin/codex.exe",
|
||||
"bin/codex-code-mode-host.exe",
|
||||
"codex-path/rg.exe",
|
||||
"codex-resources/codex-command-runner.exe",
|
||||
"codex-resources/codex-windows-sandbox-setup.exe",
|
||||
"codex-package.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "aarch64-apple-darwin",
|
||||
"platform": "darwin-arm64",
|
||||
"directory": "mac-native/darwin-arm64",
|
||||
"executable": "bin/codex",
|
||||
"files": [
|
||||
"bin/codex",
|
||||
"bin/codex-code-mode-host",
|
||||
"codex-path/rg",
|
||||
"codex-resources/zsh/bin/zsh",
|
||||
"codex-package.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "x86_64-apple-darwin",
|
||||
"platform": "darwin-x64",
|
||||
"directory": "mac-native/darwin-x64",
|
||||
"executable": "bin/codex",
|
||||
"files": [
|
||||
"bin/codex",
|
||||
"bin/codex-code-mode-host",
|
||||
"codex-path/rg",
|
||||
"codex-resources/zsh/bin/zsh",
|
||||
"codex-package.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"claudeAgent": {
|
||||
"version": "0.3.285",
|
||||
"resourceDirectory": "resources/claude-agent",
|
||||
"entryRelativePath": "agent-sidecar/src/index.mjs",
|
||||
"entryFileName": "index.mjs",
|
||||
"nodeModulesDirectory": "node_modules",
|
||||
"sdkPackageName": "@anthropic-ai/claude-agent-sdk",
|
||||
"sdkEntryFileName": "sdk.mjs",
|
||||
"sdkPackageMetadataFileName": "package.json",
|
||||
"sourceRoots": ["app", "repo"],
|
||||
"skipDirectoryNames": ["target", "node_modules"],
|
||||
"skipDirectoryNamePrefixes": ["."],
|
||||
"skipFileNamePrefixes": ["."],
|
||||
"skipFileNameFragments": [".test."],
|
||||
"targets": [
|
||||
{
|
||||
"target": "x86_64-pc-windows-msvc",
|
||||
"runtimePackage": "claude-agent-sdk-win32-x64",
|
||||
"runtimeFileName": "claude.exe"
|
||||
},
|
||||
{
|
||||
"target": "aarch64-apple-darwin",
|
||||
"runtimePackage": "claude-agent-sdk-darwin-arm64",
|
||||
"runtimeFileName": "claude"
|
||||
},
|
||||
{
|
||||
"target": "x86_64-apple-darwin",
|
||||
"runtimePackage": "claude-agent-sdk-darwin-x64",
|
||||
"runtimeFileName": "claude"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"sourceDirectory": "plugins",
|
||||
"destinationDirectory": "resources/plugins",
|
||||
"manifestFileName": "plugin.json",
|
||||
"targetContainsAny": ["windows", "apple-darwin"],
|
||||
"subdirectories": [
|
||||
{
|
||||
"path": "src",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "panels",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "skills",
|
||||
"origin": "source"
|
||||
},
|
||||
{
|
||||
"path": "native/payload",
|
||||
"origin": "prepared",
|
||||
"targetContains": ["windows"],
|
||||
"plugin": "agc-cocos-editor",
|
||||
"prepare": "cocos-bridge-build",
|
||||
"features": ["cocos-editor-injection"]
|
||||
},
|
||||
{
|
||||
"path": "dotnet/publish/win-x64",
|
||||
"origin": "prepared",
|
||||
"prepare": "unity-helper-publish",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["unity-editor-execute"],
|
||||
"plugin": "agc-unity-editor"
|
||||
}
|
||||
],
|
||||
"libraryStaging": [
|
||||
{
|
||||
"plugin": "agc-godot-editor",
|
||||
"sourceSubdirectory": "native/gdextension",
|
||||
"prepare": "godot-extension-build",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["godot-editor-execute"],
|
||||
"layout": "godot-bundle",
|
||||
"files": [
|
||||
"bin/win-x64/agc_godot_editor.dll",
|
||||
"bin/win-x64/metadata.json",
|
||||
"vendor/LICENSE.txt",
|
||||
"vendor/provenance.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"nativePayloads": [
|
||||
{
|
||||
"plugin": "agc-cocos-editor",
|
||||
"prepare": "cocos-bridge-build",
|
||||
"sourceFileName": "cocos_editor_bridge.dll",
|
||||
"destinationSubdirectory": "native/payload",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["cocos-editor-injection"],
|
||||
"destinationFileName": "cocos-editor-bridge.dll"
|
||||
}
|
||||
],
|
||||
"prepareSteps": [
|
||||
{
|
||||
"name": "unity-helper-publish",
|
||||
"kind": "powershell",
|
||||
"workingDirectory": "plugins/agc-unity-editor/dotnet",
|
||||
"scriptFileName": "build.ps1",
|
||||
"fingerprint": {
|
||||
"roots": ["."],
|
||||
"excludeDirectoryNames": ["bin", "obj", "publish", "native-build"],
|
||||
"stampRelativePath": "publish/win-x64/.agc-source.sha256"
|
||||
},
|
||||
"requiredOutputs": [
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/Agc.Unity.Attach.exe",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/NOTICE",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/THIRD-PARTY-NOTICES.txt",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/DotCraft-Apache-2.0.txt",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/Roslyn-MIT.txt",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/upstream.json",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/dotnet-LICENSE.TXT",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/dotnet-THIRD-PARTY-NOTICES.TXT",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/microsoft.codeanalysis.common-ThirdPartyNotices.rtf",
|
||||
"plugins/agc-unity-editor/dotnet/publish/win-x64/licenses/microsoft.codeanalysis.csharp-ThirdPartyNotices.rtf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "godot-extension-build",
|
||||
"kind": "powershell",
|
||||
"workingDirectory": "plugins/agc-godot-editor/native/gdextension",
|
||||
"scriptFileName": "build.ps1",
|
||||
"removeEnvironment": ["PSModulePath"],
|
||||
"requiredOutputs": [
|
||||
"plugins/agc-godot-editor/native/gdextension/bin/win-x64/agc_godot_editor.dll",
|
||||
"plugins/agc-godot-editor/native/gdextension/bin/win-x64/metadata.json",
|
||||
"plugins/agc-godot-editor/native/gdextension/vendor/LICENSE.txt",
|
||||
"plugins/agc-godot-editor/native/gdextension/vendor/provenance.json"
|
||||
],
|
||||
"fingerprint": {
|
||||
"roots": ["."],
|
||||
"excludeDirectoryNames": ["bin", ".build", "native-build"],
|
||||
"stampRelativePath": "bin/win-x64/.agc-source.sha256"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "cocos-bridge-build",
|
||||
"kind": "cargo",
|
||||
"packageDirectory": "plugins/agc-cocos-editor/native/cocos-editor-bridge",
|
||||
"features": ["windows-injection"],
|
||||
"requiredOutputs": []
|
||||
}
|
||||
],
|
||||
"skipDirectoryNames": ["target", "node_modules"],
|
||||
"skipDirectoryNamePrefixes": ["."],
|
||||
"skipFileNamePrefixes": ["."],
|
||||
"skipFileNameFragments": [".test."]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
*.dll
|
||||
@@ -304,7 +304,8 @@ pub(crate) fn game_creator_claude_code_cli_version_identity() -> Result<String,
|
||||
return Err("Claude Agent SDK sidecar 的 Node runtime 版本检查失败".to_string());
|
||||
}
|
||||
Ok(format!(
|
||||
"claude-agent-sdk-sidecar@0.3.285;node={};entry={}",
|
||||
"claude-agent-sdk-sidecar@{};node={};entry={}",
|
||||
env!("AGC_CLAUDE_AGENT_SDK_VERSION"),
|
||||
String::from_utf8_lossy(&output.stdout).trim(),
|
||||
sidecar.display()
|
||||
))
|
||||
|
||||
@@ -8,11 +8,6 @@ use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
|
||||
#[path = "../../build_support/codex_bundle.rs"]
|
||||
pub(crate) mod codex_bundle;
|
||||
|
||||
// 复用构建端校验的既有单测,生产运行时只编译共享布局。
|
||||
#[cfg(test)]
|
||||
#[path = "../../build_support/codex_package_metadata.rs"]
|
||||
mod codex_package_metadata;
|
||||
|
||||
const GAME_CREATOR_CODEX_CLI_EXECUTABLE: &str = "codex";
|
||||
const GAME_CREATOR_CODEX_CLI_PROMPT_MAX_BYTES: usize = 4 * 1024 * 1024;
|
||||
const GAME_CREATOR_CODEX_CLI_STDOUT_MAX_BYTES: usize = 4 * 1024 * 1024;
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
#[path = "../build_support/godot_bundle.rs"]
|
||||
mod godot_bundle;
|
||||
|
||||
// 复用随包资源声明的既有单测(校验通过/拒绝用例),生产运行时只经 codex_bundle 使用布局。
|
||||
#[cfg(test)]
|
||||
#[path = "../build_support/package_layout.rs"]
|
||||
mod package_layout;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
use std::fs::{File, OpenOptions};
|
||||
|
||||
@@ -39,19 +39,14 @@
|
||||
"vendor/codex-utils-path-uri/LICENSE": "licenses/codex-utils-path-uri/LICENSE",
|
||||
"vendor/codex-utils-path-uri/NOTICE": "licenses/codex-utils-path-uri/NOTICE",
|
||||
"vendor/codex-utils-absolute-path/LICENSE": "licenses/codex-utils-absolute-path/LICENSE",
|
||||
"vendor/codex-utils-absolute-path/NOTICE": "licenses/codex-utils-absolute-path/NOTICE",
|
||||
"resources/claude-agent": "claude-agent"
|
||||
"vendor/codex-utils-absolute-path/NOTICE": "licenses/codex-utils-absolute-path/NOTICE"
|
||||
},
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": [
|
||||
"bubblewrap"
|
||||
]
|
||||
"depends": ["bubblewrap"]
|
||||
},
|
||||
"rpm": {
|
||||
"depends": [
|
||||
"bubblewrap"
|
||||
]
|
||||
"depends": ["bubblewrap"]
|
||||
}
|
||||
},
|
||||
"icon": [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user