Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ceca4a7669 | |||
| 55b881de49 | |||
| acf021de2a | |||
| 3fba76fd6e | |||
| 3b4f9b2e10 | |||
| 547fb0dbce | |||
| 06ffa7b6ac | |||
| e4634928c6 | |||
| faa510e999 | |||
| 0b19df467e | |||
| 96b44d8660 | |||
| 33308a93e8 | |||
| f6dad1950f | |||
| a4b103a1b0 | |||
| 05d455d4ef | |||
| d8b37e0da9 | |||
| 21c3bd7a2b | |||
| 756297d2df | |||
| e02811588c | |||
| e1780b21b0 |
@@ -57,6 +57,8 @@ temp*build*/
|
||||
/apps/ai-game-creator-shell/logs/
|
||||
/apps/ai-game-creator-shell/src-tauri/resources/node-runtime/
|
||||
/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": {
|
||||
"@cubone/react-file-manager": "^1.35.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from './agc-global-version.mjs';
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
resolveEditorFeatures,
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
resolveReleaseChannel,
|
||||
} from './channel-identity.mjs';
|
||||
import { prepareNsisToolsetForRelease } from './nsis-toolset.mjs';
|
||||
import { prepareBundledResources } from './prepare-bundled-resources.mjs';
|
||||
import { stageNodeRuntime } from './stage-node-runtime.mjs';
|
||||
|
||||
const appRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||
@@ -431,10 +433,30 @@ 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,
|
||||
} = {},
|
||||
) {
|
||||
if (
|
||||
explicitBuildTarget(args) &&
|
||||
@@ -444,7 +466,12 @@ export function runTauriBuild(
|
||||
}
|
||||
const tauriArguments = buildTauriBuildArguments(args, context.target);
|
||||
const { channel, target } = context;
|
||||
if (!args.includes('--no-bundle')) stageRuntime(target);
|
||||
const features = resolveEditorFeatures({ argv: args, target });
|
||||
if (!args.includes('--no-bundle')) {
|
||||
stageRuntime(target);
|
||||
}
|
||||
// app 构建本身就需要随包资源,因此 --no-bundle 也要 staging;只有总号发布是打包专属。
|
||||
stageBundled(target, { features });
|
||||
const configPath = writeChannelConfigFile(
|
||||
channel,
|
||||
target,
|
||||
|
||||
@@ -363,6 +363,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 };
|
||||
@@ -456,6 +458,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(
|
||||
@@ -705,6 +709,7 @@ test('Windows remains the default and explicit Windows overrides macOS environme
|
||||
context,
|
||||
{
|
||||
stageRuntime: () => {},
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
assert.ok(
|
||||
command.includes(
|
||||
@@ -818,6 +823,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(
|
||||
@@ -829,10 +838,21 @@ 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) {
|
||||
const config = JSON.parse(
|
||||
@@ -848,6 +868,9 @@ test('release stages Node before Tauri and injects its resource mapping only for
|
||||
stageRuntime() {
|
||||
throw new Error('missing runtime');
|
||||
},
|
||||
stageBundled() {
|
||||
assert.fail('invalid runtime must prevent bundled staging');
|
||||
},
|
||||
spawn() {
|
||||
assert.fail('invalid runtime must prevent build');
|
||||
},
|
||||
@@ -960,6 +983,8 @@ for (const channel of ['release', 'beta-2']) {
|
||||
);
|
||||
runTauriBuild([`--target=${target}`], context, {
|
||||
stageRuntime: () => {},
|
||||
// 必须 stub:随包资源准备会读取真实上游包与仓库插件工作区,本用例只关心参数。
|
||||
stageBundled: () => {},
|
||||
spawn: (_binary, command) => {
|
||||
const config = JSON.parse(
|
||||
readFileSync(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+750
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,9 @@
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { buildLocalRustProcessEnv } from '../../../scripts/dev.mjs';
|
||||
import {
|
||||
defaultEditorFeatures,
|
||||
resolveEditorFeatures,
|
||||
withDefaultCargoFeatures,
|
||||
} from './cargo-features.mjs';
|
||||
import {
|
||||
@@ -10,6 +11,10 @@ import {
|
||||
resolveAgcDevEndpoint,
|
||||
withAgcDevEndpointEnv,
|
||||
} from './dev-port.mjs';
|
||||
import {
|
||||
prepareBundledResources,
|
||||
supportedHostTarget,
|
||||
} from './prepare-bundled-resources.mjs';
|
||||
import {
|
||||
isAiGameCreatorServer,
|
||||
preflightExistingVite,
|
||||
@@ -52,21 +57,37 @@ 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);
|
||||
}
|
||||
|
||||
/// 随包资源必须在 Tauri 之前生成:构建脚本只做只读校验,不再生成资源。
|
||||
/// 命中缓存的重复调用不写任何文件,因此每次 dev 启动都会先跑一次。
|
||||
function prepareBundledResourcesBeforeTauri(
|
||||
features = readDevCargoFeatures(),
|
||||
{ prepare = prepareBundledResources, log = console.log } = {},
|
||||
) {
|
||||
const target = supportedHostTarget();
|
||||
if (!target) {
|
||||
log(
|
||||
'[ai-game-creator-shell] 当前平台不受随包资源声明覆盖,跳过随包资源准备',
|
||||
);
|
||||
return;
|
||||
}
|
||||
const summaries = prepare({
|
||||
target,
|
||||
features: new Set(features),
|
||||
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 +96,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 +116,7 @@ async function runTauriDev(
|
||||
spawnCli = spawnTauriCli,
|
||||
waitForCli = waitForChildTermination,
|
||||
terminateTree = terminateChildTree,
|
||||
prepareResources = prepareBundledResourcesBeforeTauri,
|
||||
} = {},
|
||||
) {
|
||||
const endpoint = await resolveDevEndpoint();
|
||||
@@ -130,15 +163,14 @@ async function runTauriDev(
|
||||
shutdownRequested.then(() => false),
|
||||
]);
|
||||
if (!prepared || shutdownSignal) return 1;
|
||||
const devFeatures = readDevCargoFeatures();
|
||||
prepareResources(devFeatures);
|
||||
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 +257,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,132 @@
|
||||
// @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 = 1;
|
||||
|
||||
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 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 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",
|
||||
}
|
||||
],
|
||||
skip_directory_names: &["target", "node_modules"],
|
||||
skip_directory_name_prefixes: &["."],
|
||||
skip_file_name_prefixes: &["."],
|
||||
skip_file_name_fragments: &[".test."],
|
||||
};
|
||||
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"schema": "agc-package-layout.v1",
|
||||
"layoutVersion": 1,
|
||||
"description": "AGC 随包资源布局与复制规则的唯一声明。Rust 侧构建期校验与 Node 侧准备步骤共用本文件,任何一侧都不得再写第二份布局或组件白名单。含 <platform>、<target> 占位符的字段由调用方按目标三元展开。修改布局时同步递增 layoutVersion(准备步骤的缓存 key 组成部分)。",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"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
|
||||
@@ -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};
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from '../scripts/start-dev-stack.mjs';
|
||||
import {
|
||||
buildTauriArguments,
|
||||
buildTauriDevProcessEnv,
|
||||
runTauriDev as runTauriDevImpl,
|
||||
withDevCargoFeatures,
|
||||
} from '../scripts/start-tauri-dev.mjs';
|
||||
@@ -27,7 +28,13 @@ const resolveTestEndpoint = async () => testEndpoint;
|
||||
const runTauriDev = (
|
||||
argv: string[],
|
||||
options: Parameters<typeof runTauriDevImpl>[1],
|
||||
) => runTauriDevImpl(argv, { prepareFrontend: async () => {}, ...options });
|
||||
) =>
|
||||
runTauriDevImpl(argv, {
|
||||
prepareFrontend: async () => {},
|
||||
// 随包资源准备会读取真实上游包与仓库插件工作区;需要断言的用例自行注入。
|
||||
prepareResources: () => {},
|
||||
...options,
|
||||
});
|
||||
|
||||
async function waitForFile(path: string, timeoutMs = 5000) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
@@ -152,6 +159,10 @@ describe('AI 游戏创作 Tauri dev 生命周期', () => {
|
||||
prepareFrontend: async () => {
|
||||
order.push('frontend-ready');
|
||||
},
|
||||
prepareResources: (features) => {
|
||||
expect(Array.isArray(features)).toBe(true);
|
||||
order.push('resources');
|
||||
},
|
||||
spawnCli: () => {
|
||||
order.push('spawn');
|
||||
return child;
|
||||
@@ -171,6 +182,7 @@ describe('AI 游戏创作 Tauri dev 生命周期', () => {
|
||||
expect(order).toEqual([
|
||||
'preflight',
|
||||
'frontend-ready',
|
||||
'resources',
|
||||
'spawn',
|
||||
'exit',
|
||||
'cleanup',
|
||||
@@ -300,3 +312,54 @@ describe('AI 游戏创作 Tauri dev 生命周期', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('AI 游戏创作 Tauri dev 进程环境', () => {
|
||||
const posixTest = process.platform === 'win32' ? test.skip : test;
|
||||
|
||||
// 本机 `~/.cargo/config.toml` 或仓库级 Cargo 配置里的 sccache wrapper 只有在环境变量
|
||||
// 非空时才会被覆盖;这里必须显式写入要交给 Tauri Cargo 的 wrapper 决策结果。
|
||||
posixTest('本地 dev 不把 sccache 交给 Tauri Cargo', () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
try {
|
||||
const env = buildTauriDevProcessEnv(testEndpoint, {
|
||||
RUSTC_WRAPPER: 'sccache',
|
||||
CARGO_BUILD_RUSTC_WRAPPER: 'sccache',
|
||||
});
|
||||
|
||||
expect(env.RUSTC_WRAPPER).toBe('/usr/bin/env');
|
||||
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('/usr/bin/env');
|
||||
} finally {
|
||||
warn.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
posixTest('未显式配置 wrapper 时清空两个变量', () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
try {
|
||||
const env = buildTauriDevProcessEnv(testEndpoint, {
|
||||
CARGO_TERM_COLOR: 'never',
|
||||
});
|
||||
|
||||
expect(env.RUSTC_WRAPPER).toBe('');
|
||||
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('');
|
||||
expect(env.CARGO_TERM_COLOR).toBe('never');
|
||||
} finally {
|
||||
warn.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
posixTest('保留显式自定义 wrapper 且不改写调用方 env', () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
try {
|
||||
const input = { RUSTC_WRAPPER: '/opt/custom/rustc-wrapper' };
|
||||
const env = buildTauriDevProcessEnv(testEndpoint, input);
|
||||
|
||||
expect(env.RUSTC_WRAPPER).toBe('/opt/custom/rustc-wrapper');
|
||||
expect(env.CARGO_BUILD_RUSTC_WRAPPER).toBe('/opt/custom/rustc-wrapper');
|
||||
expect(env.GENARRATIVE_AGC_VITE_PORT).toBe(String(testEndpoint.port));
|
||||
expect(input).toEqual({ RUSTC_WRAPPER: '/opt/custom/rustc-wrapper' });
|
||||
} finally {
|
||||
warn.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
- [AI 游戏创作智能体 App 实施计划](./technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md):当前 DirectProject、受控语义工具、UI workflow、资源和运行时合同。
|
||||
- [AGC 后端框架整理与演进路线](./technical/【技术方案】AGC后端框架整理与演进路线-2026-09-18.md):共享 Runtime、本地执行宿主、云端控制面、领域/平台适配器及分阶段收口边界。
|
||||
- [AGC 随包资源 staging 归位](./technical/【技术方案】AGC随包资源staging归位-2026-09-26.md):随包资源改由准备步骤在 `tauri dev|build` 之前一次性生成、`build.rs` 退化为校验者;含缓存与原子性合同、入口接线、验收判据与里程碑拆分。
|
||||
- [AGC 异步操作可恢复闭环](./【技术方案】AGC异步操作可恢复闭环-2026-09-14.md):认证响应体、最近项目检查和首页自动创建的超时、逐项恢复与跨页防重合同。
|
||||
- [AGC 客户端稳定版生命周期大切换](./【技术方案】AGC客户端稳定版生命周期大切换-2026-09-14.md):统一 operation、认证/Runner、项目入口、本地恢复和 dev-stack 身份边界。
|
||||
- [策划会话 Runtime V2 接入与旧链路退役方案](./technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md):历史方案,仅用于追溯 V2 的实现与退役过程,不作为当前实现依据。
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# 【实施计划】AGC 随包资源改由校验器读入
|
||||
|
||||
| 字段 | 值 |
|
||||
| --------- | --------------------------------------------------------------- |
|
||||
| Milestone | `docs/project-memory/plans/【里程碑】AGC随包资源改由校验器读入-2026-09-26.md` |
|
||||
| Status | ready(待里程碑规范评审通过后开工) |
|
||||
| Owner | suzmii / Agent |
|
||||
|
||||
## 修改边界
|
||||
|
||||
允许修改:
|
||||
|
||||
- `apps/ai-game-creator-shell/src-tauri/build.rs`:新增只读校验调用点;本里程碑内保持现有写入分支不变(不改变既有构建行为)。
|
||||
- `apps/ai-game-creator-shell/src-tauri/build_support/**`:把平台布局、组件白名单、摘要校验整理为可被构建脚本之外的独立工具复用的一处声明。
|
||||
- 新增随包资源准备工具及其测试(位置见「待确认决策」)。
|
||||
- 需要时扩展 `apps/ai-game-creator-shell/scripts/check-config.mjs` 的断言。
|
||||
- 文档:主规范未决问题收口、开发运维文档对应段落。
|
||||
|
||||
明确不修改:
|
||||
|
||||
- 三份 tauri 配置的 `resources` 映射、包内路径与安装包形态。
|
||||
- 运行时资源解析与完整性校验(`codex_cli.rs`、`plugin_host.rs`、`editor_adapters.rs`、`environment_check.rs`)。
|
||||
- 发布脚本流程、版本号机制、签名与上传。
|
||||
- dev 启动器与发布入口的接线(下一里程碑)。
|
||||
- 编辑器分支产物(Unity/Godot/Cocos)的生成方式(最后一个里程碑)。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
1. **共用能力可复用**:确认 `build_support` 内的平台布局与组件白名单能被独立工具引用(现状先例:`src/agent/codex_cli.rs` 与 `main.rs` 已通过 `#[path]` 复用同一模块),把「布局 + 白名单 + 摘要校验」收敛为单一入口,避免准备工具另写一份清单。
|
||||
2. **准备工具骨架**:目标目录与清单写出、缓存 key(上游 lockfile 的 `resolved` + `integrity` + 布局版本 + 目标三元)、临时目录 + 原子替换、所有权与符号链接校验、并发串行化、单行汇总日志。先实现纯复制两条路径(随包组件、插件工作区),编辑器分支产物本轮仍由构建脚本生成。
|
||||
3. **幂等与失败关闭**:重复执行不改变内容与时间戳;上游缺失、摘要不匹配、目录被非本工具占用、目标平台不支持四类场景各自失败并给出可定位原因。
|
||||
4. **校验路径上线**:构建脚本在既有产物上执行只读校验(默认不影响现有写入行为),校验失败以明确原因中止。
|
||||
5. **测试与证据**:按里程碑「证据要求」补齐用例与运行记录。
|
||||
|
||||
## 验证命令
|
||||
|
||||
1. 声明唯一性与门禁:`npm run agc:bundled-resources:check`(已进 `agc:typecheck` 链),不一致时用 `npm run agc:bundled-resources:sync` 重新生成。
|
||||
2. 准备工具用例(含幂等与失败关闭):`npm run agc:bundled-resources:test`。
|
||||
3. 校验路径独立运行(跳过写入分支):`AGC_SKIP_RESOURCE_STAGING=1 cargo check --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml`。
|
||||
4. Rust 用例:`cargo test --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml package_layout` 与 `cargo test --no-default-features --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml codex_bundle`。
|
||||
5. 幂等(真实工作区):连续两次 `node apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs`,第二次必须全部「命中缓存」,且两次之后的目录快照(相对路径、大小、mtime、sha256)完全一致。
|
||||
6. 并存一致:准备步骤产物与构建脚本产物逐文件比对(相对路径、大小、sha256)一致。
|
||||
7. 行为不回归:`cargo build --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --no-default-features`(本里程碑不承诺构建变快,仅确认行为与改造前一致,并记录当前构建耗时作为后续里程碑基线)。
|
||||
8. 门禁:`node apps/ai-game-creator-shell/scripts/check-config.mjs`、`npm run check:encoding`、`npm run check:doc-index`、`git diff --check`,以及改动范围内相关 vitest/Rust 测试。
|
||||
|
||||
## 风险与回滚点
|
||||
|
||||
| 风险 | 影响 | 处理 |
|
||||
| --- | --- | --- |
|
||||
| 校验器误判把构建卡死 | 影响所有本机构建 | 只读校验先以「不影响写入行为」的方式接入;出现误判可先关闭校验调用点回滚 |
|
||||
| 准备工具与构建脚本并存产生双写 | 两处结果漂移、时间戳变化 | 并存期以「准备工具产物 == 构建脚本产物」逐文件比对作为过渡判据;不一致视为失败 |
|
||||
| 缓存 key 漏掉上游变化 | 静默用旧组件 | key 含 lockfile `resolved` + `integrity` + 布局版本 + 三元;清单校验作为第二道闸 |
|
||||
| 准备工具实现形态选错 | 返工 | 见「待确认决策」,评审时一次定清 |
|
||||
|
||||
回滚点:本里程碑不改变既有构建行为,回滚只需移除校验调用点与准备工具,不影响产物与发布流程。
|
||||
|
||||
## 已定决策
|
||||
|
||||
准备工具的实现形态(主规范未决问题 1)**已定为混合**(2026-09-27,机制见主规范 §4.8):
|
||||
|
||||
- 上游获取、`integrity` 校验、归档安全与原子替换复用 Node 侧既有范式(`scripts/stage-node-runtime.mjs`、`scripts/prepare-macos-codex.mjs`);
|
||||
- 平台布局、组件白名单与逐文件摘要校验复用 Rust 侧既有声明(`build_support/codex_bundle.rs`、`build_support/godot_bundle.rs`),由准备工具与校验路径共用同一份声明文件承载,不再各写一份清单。(上游原生包元数据的期望值后来并入同一份声明;`build_support/codex_package_metadata.rs` 已在 M2 因失去调用方删除。)
|
||||
|
||||
理由:避免出现第二份组件白名单,同时不必重写 registry 下载、`integrity` 与 tar 安全校验;缺点是声明需要经过一次生成步骤才能在 Rust 侧使用,由 `check-package-layout.mjs` 门禁保证两者一致。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user