引入 AGC 随包资源的单一声明与生成门禁
- 新增 build_support/package-layout.json:Codex 三元表与组件白名单、上游候选路径、第三方声明来源、插件随包子目录与跳过规则、平台与 feature 门槛的唯一人工声明 - 新增 scripts/check-package-layout.mjs:由声明生成 build_support/package-layout.generated.rs,并校验目标唯一、可执行组件属于白名单、每个目标恰有一条第三方声明来源、codex.version 与应用锁定的 @openai/codex 一致 - 新增 build_support/package_layout.rs:声明类型、插件与平台门槛判定、逐块 sha256、随包 Codex 目录只读校验及其单测 - codex_bundle.rs 的布局与版本常量改由声明提供,公开接口与取值不变;src/main.rs 在测试期引入该模块以复用既有单测 - package.json 新增 bundled-resources:check/sync 并接进 typecheck 链,根 package.json 提供 agc:* 别名
This commit is contained in:
@@ -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",
|
||||
|
||||
+477
@@ -0,0 +1,477 @@
|
||||
#!/usr/bin/env node
|
||||
// 随包资源声明门禁:把 build_support/package-layout.json(唯一人工声明)渲染成
|
||||
// build_support/package-layout.generated.rs(Rust 编译期常量),并校验声明结构与不变量。
|
||||
//
|
||||
// 用法:
|
||||
// node scripts/check-package-layout.mjs 校验生成结果是否与声明一致(不一致 exit 1)
|
||||
// node scripts/check-package-layout.mjs --write 重新生成
|
||||
//
|
||||
// 设计约束:Rust 侧不解析 JSON(避免运行期解析与生命周期妥协),只使用本脚本产出的常量;
|
||||
// Node 侧准备步骤直接读同一份 JSON。因此本门禁是“单一声明”的机械保障。
|
||||
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
const APP_ROOT = path.resolve(SCRIPT_DIR, '..');
|
||||
const SRC_TAURI = path.join(APP_ROOT, 'src-tauri');
|
||||
const DECLARATION_PATH = path.join(
|
||||
SRC_TAURI,
|
||||
'build_support/package-layout.json',
|
||||
);
|
||||
const GENERATED_PATH = path.join(
|
||||
SRC_TAURI,
|
||||
'build_support/package-layout.generated.rs',
|
||||
);
|
||||
|
||||
const EXPECTED_SCHEMA = 'agc-package-layout.v1';
|
||||
|
||||
class DeclarationError extends Error {}
|
||||
|
||||
function fail(message) {
|
||||
throw new DeclarationError(message);
|
||||
}
|
||||
|
||||
function expectObject(value, at) {
|
||||
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
||||
fail(`${at} 必须是对象`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function expectArray(value, at) {
|
||||
if (!Array.isArray(value)) {
|
||||
fail(`${at} 必须是数组`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function expectString(value, at) {
|
||||
if (typeof value !== 'string' || value.length === 0) {
|
||||
fail(`${at} 必须是非空字符串`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function expectBoolean(value, at) {
|
||||
if (typeof value !== 'boolean') {
|
||||
fail(`${at} 必须是布尔值`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function expectStringArray(value, at) {
|
||||
return expectArray(value, at).map((item, index) =>
|
||||
expectString(item, `${at}[${index}]`),
|
||||
);
|
||||
}
|
||||
|
||||
function optionalStringArray(source, key, at) {
|
||||
if (source[key] === undefined) {
|
||||
return [];
|
||||
}
|
||||
return expectStringArray(source[key], `${at}.${key}`);
|
||||
}
|
||||
|
||||
function expectInteger(value, at) {
|
||||
if (!Number.isInteger(value) || value < 0) {
|
||||
fail(`${at} 必须是非负整数`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// JSON 字符串字面量与 Rust 字符串字面量几乎一致,唯一差异是控制字符的 \uXXXX 与 \u{XX}。
|
||||
function rustString(value) {
|
||||
return JSON.stringify(value).replace(/\\u([0-9a-fA-F]{4})/g, '\\u{$1}');
|
||||
}
|
||||
|
||||
function rustStrings(values) {
|
||||
return `&[${values.map(rustString).join(', ')}]`;
|
||||
}
|
||||
|
||||
function indent(level) {
|
||||
return ' '.repeat(level);
|
||||
}
|
||||
|
||||
function renderStruct(name, fields, level = 0) {
|
||||
const body = fields
|
||||
.map(([key, rendered]) => `${indent(level + 1)}${key}: ${rendered},`)
|
||||
.join('\n');
|
||||
return `${name} {\n${body}\n${indent(level)}}`;
|
||||
}
|
||||
|
||||
function parseDeclaration(raw) {
|
||||
const root = expectObject(JSON.parse(raw), 'root');
|
||||
if (root.schema !== EXPECTED_SCHEMA) {
|
||||
fail(
|
||||
`不支持的声明 schema:${String(root.schema)}(期望 ${EXPECTED_SCHEMA})`,
|
||||
);
|
||||
}
|
||||
const layoutVersion = expectInteger(root.layoutVersion, 'layoutVersion');
|
||||
|
||||
const codex = expectObject(root.codex, 'codex');
|
||||
const targets = expectArray(codex.targets, 'codex.targets').map(
|
||||
(entry, index) => {
|
||||
const at = `codex.targets[${index}]`;
|
||||
const parsed = expectObject(entry, at);
|
||||
const files = expectStringArray(parsed.files, `${at}.files`);
|
||||
if (files.length === 0) {
|
||||
fail(`${at}.files 不能为空`);
|
||||
}
|
||||
const executable = expectString(parsed.executable, `${at}.executable`);
|
||||
if (!files.includes(executable)) {
|
||||
fail(`${at}.executable 必须属于组件白名单:${executable}`);
|
||||
}
|
||||
return {
|
||||
target: expectString(parsed.target, `${at}.target`),
|
||||
platform: expectString(parsed.platform, `${at}.platform`),
|
||||
directory: expectString(parsed.directory, `${at}.directory`),
|
||||
executable,
|
||||
files,
|
||||
};
|
||||
},
|
||||
);
|
||||
const seenTargets = new Set();
|
||||
for (const entry of targets) {
|
||||
if (seenTargets.has(entry.target)) {
|
||||
fail(`codex.targets 重复声明目标 ${entry.target}`);
|
||||
}
|
||||
seenTargets.add(entry.target);
|
||||
}
|
||||
|
||||
const noticeSources = expectArray(
|
||||
codex.noticeSources,
|
||||
'codex.noticeSources',
|
||||
).map((entry, index) => {
|
||||
const at = `codex.noticeSources[${index}]`;
|
||||
const parsed = expectObject(entry, at);
|
||||
return {
|
||||
targets: expectStringArray(parsed.targets, `${at}.targets`),
|
||||
source: expectString(parsed.source, `${at}.source`),
|
||||
preserve: expectBoolean(parsed.preserve, `${at}.preserve`),
|
||||
};
|
||||
});
|
||||
for (const entry of targets) {
|
||||
const covered = noticeSources.filter((notice) =>
|
||||
notice.targets.includes(entry.target),
|
||||
);
|
||||
if (covered.length !== 1) {
|
||||
fail(
|
||||
`目标 ${entry.target} 必须且只能有一条第三方声明来源(实际 ${covered.length} 条)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const universalGroups = expectArray(
|
||||
codex.universalGroups,
|
||||
'codex.universalGroups',
|
||||
).map((entry, index) => {
|
||||
const at = `codex.universalGroups[${index}]`;
|
||||
const parsed = expectObject(entry, at);
|
||||
const groupTargets = expectStringArray(parsed.targets, `${at}.targets`);
|
||||
for (const target of groupTargets) {
|
||||
if (!seenTargets.has(target)) {
|
||||
fail(`${at}.targets 含未声明目标 ${target}`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: expectString(parsed.name, `${at}.name`),
|
||||
directory: expectString(parsed.directory, `${at}.directory`),
|
||||
targets: groupTargets,
|
||||
};
|
||||
});
|
||||
|
||||
const plugins = expectObject(root.plugins, 'plugins');
|
||||
const subdirectories = expectArray(
|
||||
plugins.subdirectories,
|
||||
'plugins.subdirectories',
|
||||
).map((entry, index) => {
|
||||
const at = `plugins.subdirectories[${index}]`;
|
||||
const parsed = expectObject(entry, at);
|
||||
return {
|
||||
path: expectString(parsed.path, `${at}.path`),
|
||||
targetContains: optionalStringArray(parsed, 'targetContains', at),
|
||||
targets: optionalStringArray(parsed, 'targets', at),
|
||||
features: optionalStringArray(parsed, 'features', at),
|
||||
};
|
||||
});
|
||||
const libraryStaging = expectArray(
|
||||
plugins.libraryStaging,
|
||||
'plugins.libraryStaging',
|
||||
).map((entry, index) => {
|
||||
const at = `plugins.libraryStaging[${index}]`;
|
||||
const parsed = expectObject(entry, at);
|
||||
return {
|
||||
plugin: expectString(parsed.plugin, `${at}.plugin`),
|
||||
sourceSubdirectory: expectString(
|
||||
parsed.sourceSubdirectory,
|
||||
`${at}.sourceSubdirectory`,
|
||||
),
|
||||
targets: expectStringArray(parsed.targets, `${at}.targets`),
|
||||
features: expectStringArray(parsed.features, `${at}.features`),
|
||||
layout: expectString(parsed.layout, `${at}.layout`),
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
layoutVersion,
|
||||
codex: {
|
||||
version: expectString(codex.version, 'codex.version'),
|
||||
cliVersionPrefix: expectString(
|
||||
codex.cliVersionPrefix,
|
||||
'codex.cliVersionPrefix',
|
||||
),
|
||||
manifestSchema: expectString(
|
||||
codex.manifestSchema,
|
||||
'codex.manifestSchema',
|
||||
),
|
||||
resourceDirectory: expectString(
|
||||
codex.resourceDirectory,
|
||||
'codex.resourceDirectory',
|
||||
),
|
||||
manifestFileName: expectString(
|
||||
codex.manifestFileName,
|
||||
'codex.manifestFileName',
|
||||
),
|
||||
packageMetadataFileName: expectString(
|
||||
codex.packageMetadataFileName,
|
||||
'codex.packageMetadataFileName',
|
||||
),
|
||||
noticeFileName: expectString(
|
||||
codex.noticeFileName,
|
||||
'codex.noticeFileName',
|
||||
),
|
||||
sourceRoots: expectStringArray(codex.sourceRoots, 'codex.sourceRoots'),
|
||||
sourceRelativePaths: expectStringArray(
|
||||
codex.sourceRelativePaths,
|
||||
'codex.sourceRelativePaths',
|
||||
),
|
||||
noticeSources,
|
||||
universalGroups,
|
||||
targets,
|
||||
},
|
||||
plugins: {
|
||||
sourceDirectory: expectString(
|
||||
plugins.sourceDirectory,
|
||||
'plugins.sourceDirectory',
|
||||
),
|
||||
destinationDirectory: expectString(
|
||||
plugins.destinationDirectory,
|
||||
'plugins.destinationDirectory',
|
||||
),
|
||||
manifestFileName: expectString(
|
||||
plugins.manifestFileName,
|
||||
'plugins.manifestFileName',
|
||||
),
|
||||
targetContainsAny: expectStringArray(
|
||||
plugins.targetContainsAny,
|
||||
'plugins.targetContainsAny',
|
||||
),
|
||||
subdirectories,
|
||||
libraryStaging,
|
||||
skipDirectoryNames: expectStringArray(
|
||||
plugins.skipDirectoryNames,
|
||||
'plugins.skipDirectoryNames',
|
||||
),
|
||||
skipDirectoryNamePrefixes: expectStringArray(
|
||||
plugins.skipDirectoryNamePrefixes,
|
||||
'plugins.skipDirectoryNamePrefixes',
|
||||
),
|
||||
skipFileNamePrefixes: expectStringArray(
|
||||
plugins.skipFileNamePrefixes,
|
||||
'plugins.skipFileNamePrefixes',
|
||||
),
|
||||
skipFileNameFragments: expectStringArray(
|
||||
plugins.skipFileNameFragments,
|
||||
'plugins.skipFileNameFragments',
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function renderCodexTarget(entry) {
|
||||
return renderStruct(
|
||||
'CodexTarget',
|
||||
[
|
||||
['target', rustString(entry.target)],
|
||||
['platform', rustString(entry.platform)],
|
||||
['directory', rustString(entry.directory)],
|
||||
['executable', rustString(entry.executable)],
|
||||
['files', rustStrings(entry.files)],
|
||||
],
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function renderNoticeSource(entry) {
|
||||
return renderStruct(
|
||||
'NoticeSource',
|
||||
[
|
||||
['targets', rustStrings(entry.targets)],
|
||||
['source', rustString(entry.source)],
|
||||
['preserve', entry.preserve ? 'true' : 'false'],
|
||||
],
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function renderUniversalGroup(entry) {
|
||||
return renderStruct(
|
||||
'UniversalGroup',
|
||||
[
|
||||
['name', rustString(entry.name)],
|
||||
['directory', rustString(entry.directory)],
|
||||
['targets', rustStrings(entry.targets)],
|
||||
],
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function renderSubdirectory(entry) {
|
||||
return renderStruct(
|
||||
'Subdirectory',
|
||||
[
|
||||
['path', rustString(entry.path)],
|
||||
['target_contains', rustStrings(entry.targetContains)],
|
||||
['targets', rustStrings(entry.targets)],
|
||||
['features', rustStrings(entry.features)],
|
||||
],
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function renderLibraryStaging(entry) {
|
||||
return renderStruct(
|
||||
'LibraryStaging',
|
||||
[
|
||||
['plugin', rustString(entry.plugin)],
|
||||
['source_subdirectory', rustString(entry.sourceSubdirectory)],
|
||||
['targets', rustStrings(entry.targets)],
|
||||
['features', rustStrings(entry.features)],
|
||||
['layout', rustString(entry.layout)],
|
||||
],
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function renderGenerated(declaration) {
|
||||
const codex = declaration.codex;
|
||||
const plugins = declaration.plugins;
|
||||
const codexStruct = renderStruct('Codex', [
|
||||
['resource_directory', rustString(codex.resourceDirectory)],
|
||||
['manifest_file_name', rustString(codex.manifestFileName)],
|
||||
['package_metadata_file_name', rustString(codex.packageMetadataFileName)],
|
||||
['notice_file_name', rustString(codex.noticeFileName)],
|
||||
['source_roots', rustStrings(codex.sourceRoots)],
|
||||
['source_relative_paths', rustStrings(codex.sourceRelativePaths)],
|
||||
[
|
||||
'notice_sources',
|
||||
`&[\n${codex.noticeSources.map(renderNoticeSource).join(',\n')}\n]`,
|
||||
],
|
||||
[
|
||||
'universal_groups',
|
||||
`&[\n${codex.universalGroups.map(renderUniversalGroup).join(',\n')}\n]`,
|
||||
],
|
||||
['targets', `&[\n${codex.targets.map(renderCodexTarget).join(',\n')}\n]`],
|
||||
]);
|
||||
const pluginsStruct = renderStruct('Plugins', [
|
||||
['source_directory', rustString(plugins.sourceDirectory)],
|
||||
['destination_directory', rustString(plugins.destinationDirectory)],
|
||||
['manifest_file_name', rustString(plugins.manifestFileName)],
|
||||
['target_contains_any', rustStrings(plugins.targetContainsAny)],
|
||||
[
|
||||
'subdirectories',
|
||||
`&[\n${plugins.subdirectories.map(renderSubdirectory).join(',\n')}\n]`,
|
||||
],
|
||||
[
|
||||
'library_staging',
|
||||
`&[\n${plugins.libraryStaging.map(renderLibraryStaging).join(',\n')}\n]`,
|
||||
],
|
||||
['skip_directory_names', rustStrings(plugins.skipDirectoryNames)],
|
||||
[
|
||||
'skip_directory_name_prefixes',
|
||||
rustStrings(plugins.skipDirectoryNamePrefixes),
|
||||
],
|
||||
['skip_file_name_prefixes', rustStrings(plugins.skipFileNamePrefixes)],
|
||||
['skip_file_name_fragments', rustStrings(plugins.skipFileNameFragments)],
|
||||
]);
|
||||
|
||||
return `// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
|
||||
// 来源:build_support/package-layout.json。不要手工编辑本文件。
|
||||
// 修改随包资源布局请编辑声明文件,然后运行
|
||||
// npm run agc:package-layout:sync(在仓库根目录)
|
||||
// 门禁会校验两者一致(npm run agc:typecheck 链内含 check-package-layout.mjs)。
|
||||
|
||||
pub const DECLARATION_SCHEMA: &str = ${rustString(EXPECTED_SCHEMA)};
|
||||
pub const LAYOUT_VERSION: u64 = ${declaration.layoutVersion};
|
||||
|
||||
pub const CODEX_VERSION: &str = ${rustString(declaration.codex.version)};
|
||||
pub const CODEX_CLI_VERSION: &str = ${rustString(declaration.codex.cliVersionPrefix + declaration.codex.version)};
|
||||
pub const CODEX_MANIFEST_SCHEMA: &str = ${rustString(declaration.codex.manifestSchema)};
|
||||
|
||||
pub const CODEX: Codex = ${codexStruct};
|
||||
|
||||
pub const PLUGINS: Plugins = ${pluginsStruct};
|
||||
`;
|
||||
}
|
||||
|
||||
function appLockedCodexVersion() {
|
||||
const appPackage = expectObject(
|
||||
JSON.parse(readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')),
|
||||
'apps/ai-game-creator-shell/package.json',
|
||||
);
|
||||
const declared =
|
||||
appPackage.dependencies?.['@openai/codex'] ??
|
||||
appPackage.devDependencies?.['@openai/codex'];
|
||||
if (typeof declared !== 'string' || declared.length === 0) {
|
||||
fail(
|
||||
'应用 package.json 未声明 @openai/codex,无法校验声明的 codex.version',
|
||||
);
|
||||
}
|
||||
return declared.replace(/^[\^~]/, '');
|
||||
}
|
||||
|
||||
function main() {
|
||||
const declaration = parseDeclaration(readFileSync(DECLARATION_PATH, 'utf8'));
|
||||
const lockedVersion = appLockedCodexVersion();
|
||||
if (lockedVersion !== declaration.codex.version) {
|
||||
fail(
|
||||
`声明 codex.version(${declaration.codex.version})与应用锁定的 @openai/codex(${lockedVersion})不一致;请同步更新 build_support/package-layout.json`,
|
||||
);
|
||||
}
|
||||
const rendered = renderGenerated(declaration);
|
||||
const write = process.argv.includes('--write');
|
||||
if (write) {
|
||||
writeFileSync(GENERATED_PATH, rendered);
|
||||
console.log(
|
||||
`随包资源声明已生成:${path.relative(process.cwd(), GENERATED_PATH)}(layoutVersion ${declaration.layoutVersion})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const current = readFileSync(GENERATED_PATH, 'utf8');
|
||||
if (current !== rendered) {
|
||||
console.error(
|
||||
[
|
||||
`随包资源声明与 Rust 常量不一致:`,
|
||||
` 声明:${path.relative(process.cwd(), DECLARATION_PATH)}`,
|
||||
` 生成:${path.relative(process.cwd(), GENERATED_PATH)}`,
|
||||
'请运行:npm run agc:package-layout:sync',
|
||||
].join('\n'),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(
|
||||
`随包资源声明一致(layoutVersion ${declaration.layoutVersion},codex ${declaration.codex.version},目标 ${declaration.codex.targets.length},插件子目录 ${declaration.plugins.subdirectories.length})`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
if (error instanceof DeclarationError) {
|
||||
console.error(`随包资源声明无效:${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// @generated by apps/ai-game-creator-shell/scripts/check-package-layout.mjs
|
||||
// 来源:build_support/package-layout.json。不要手工编辑本文件。
|
||||
// 修改随包资源布局请编辑声明文件,然后运行
|
||||
// npm run agc:package-layout:sync(在仓库根目录)
|
||||
// 门禁会校验两者一致(npm run agc: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 CODEX: Codex = Codex {
|
||||
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",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "panels",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "skills",
|
||||
target_contains: &[],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "native/payload",
|
||||
target_contains: &["windows"],
|
||||
targets: &[],
|
||||
features: &[],
|
||||
},
|
||||
Subdirectory {
|
||||
path: "dotnet/publish/win-x64",
|
||||
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,110 @@
|
||||
{
|
||||
"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",
|
||||
"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" },
|
||||
{ "path": "panels" },
|
||||
{ "path": "skills" },
|
||||
{ "path": "native/payload", "targetContains": ["windows"] },
|
||||
{
|
||||
"path": "dotnet/publish/win-x64",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["unity-editor-execute"]
|
||||
}
|
||||
],
|
||||
"libraryStaging": [
|
||||
{
|
||||
"plugin": "agc-godot-editor",
|
||||
"sourceSubdirectory": "native/gdextension",
|
||||
"targets": ["x86_64-pc-windows-msvc"],
|
||||
"features": ["godot-editor-execute"],
|
||||
"layout": "godot-bundle"
|
||||
}
|
||||
],
|
||||
"skipDirectoryNames": ["target", "node_modules"],
|
||||
"skipDirectoryNamePrefixes": ["."],
|
||||
"skipFileNamePrefixes": ["."],
|
||||
"skipFileNameFragments": [".test."]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user