From e7fa90ae18323a3d4824952b1ad5f4694825e9be Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 1 Oct 2026 11:35:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAGC=E9=9A=8F=E5=8C=85?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=A0=A1=E9=AA=8C=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复变参 feature 解析、Windows Codex 缓存幂等性、插件缓存产物检查和插件根目录链接校验。 补齐 staging/backup 忽略规则与 feature 回归测试。 --- .gitignore | 4 +++ .../scripts/build-release.mjs | 9 +++++++ .../scripts/cargo-features.mjs | 13 +++++----- .../scripts/cargo-features.test.mjs | 18 ++++++++++++- .../scripts/prepare-bundled-resources.mjs | 26 ++++++++++++++++--- .../src-tauri/build_support/package_layout.rs | 11 ++++++-- 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index e4b2363a4..652820e39 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,10 @@ temp*build*/ /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/src-tauri/resources/plugins-backup-*/ +/apps/ai-game-creator-shell/src-tauri/resources/codex/*-backup-*/ +/apps/ai-game-creator-shell/src-tauri/resources/claude-agent-staging-*/ +/apps/ai-game-creator-shell/src-tauri/resources/claude-agent-backup-*/ /apps/ai-game-creator-shell/.llm-drafts/ /apps/ai-game-creator-shell/game-creator.config.local.json /apps/mobile-shell/.expo/ diff --git a/apps/ai-game-creator-shell/scripts/build-release.mjs b/apps/ai-game-creator-shell/scripts/build-release.mjs index f007e8bd4..bfb958806 100644 --- a/apps/ai-game-creator-shell/scripts/build-release.mjs +++ b/apps/ai-game-creator-shell/scripts/build-release.mjs @@ -499,6 +499,15 @@ export function runTauriBuild( const resourceTarget = explicitBuildTarget(args) || (bundling ? context.target : supportedHostTarget(platform, arch)); + if ( + !bundling && + !resourceTarget && + defaultEditorFeatures(platform).length > 0 + ) { + throw new Error( + `当前宿主 ${platform}/${arch} 不在随包资源声明覆盖内;请用 --target 指定受支持的构建目标`, + ); + } const features = resolveEditorFeatures({ argv: tauriArguments, target: resourceTarget ?? platform, diff --git a/apps/ai-game-creator-shell/scripts/cargo-features.mjs b/apps/ai-game-creator-shell/scripts/cargo-features.mjs index 289b0c515..588912ac1 100644 --- a/apps/ai-game-creator-shell/scripts/cargo-features.mjs +++ b/apps/ai-game-creator-shell/scripts/cargo-features.mjs @@ -39,7 +39,7 @@ function hasExplicitFeatures(value) { function parseFeatureNames(raw) { return String(raw) - .split(',') + .split(/[\s,]+/u) .map((name) => name.trim()) .filter(Boolean); } @@ -119,11 +119,12 @@ export function featuresFromArgv(argv = []) { 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)); + while ( + index + 1 < options.length && + !String(options[index + 1]).startsWith('-') + ) { + index += 1; + features.push(...parseFeatureNames(options[index])); } } else if (value.startsWith('-f') && value.length > 2) { found = true; diff --git a/apps/ai-game-creator-shell/scripts/cargo-features.test.mjs b/apps/ai-game-creator-shell/scripts/cargo-features.test.mjs index 3358524b9..baf462966 100644 --- a/apps/ai-game-creator-shell/scripts/cargo-features.test.mjs +++ b/apps/ai-game-creator-shell/scripts/cargo-features.test.mjs @@ -2,7 +2,10 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { buildTauriBuildArguments } from './build-release.mjs'; -import { withDefaultCargoFeatures } from './cargo-features.mjs'; +import { + featuresFromArgv, + withDefaultCargoFeatures, +} from './cargo-features.mjs'; test('Windows release includes the same editor feature as development', () => { assert.deepEqual( @@ -47,3 +50,16 @@ test('explicit Cargo features override defaults in every supported spelling', () ['--features=cocos-editor-execute', '--', '--features=app'], ); }); + +test('features 变参会完整消费空格与逗号分隔的值', () => { + assert.deepEqual( + featuresFromArgv([ + '-f', + 'cocos-editor-execute,unity-editor-execute', + 'godot-editor-execute', + '--target', + 'x86_64-pc-windows-msvc', + ]), + ['cocos-editor-execute', 'unity-editor-execute', 'godot-editor-execute'], + ); +}); diff --git a/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs b/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs index f22e34c00..1b25f2585 100755 --- a/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs +++ b/apps/ai-game-creator-shell/scripts/prepare-bundled-resources.mjs @@ -374,7 +374,11 @@ function unitMatchesExisting(unitPath, declaration, unit, desired) { if (info.size !== file.size || sha256File(filePath) !== file.sha256) { return false; } - if (relative === member.layout.executable && (info.mode & 0o111) === 0) { + if ( + process.platform !== 'win32' && + relative === member.layout.executable && + (info.mode & 0o111) === 0 + ) { return false; } } @@ -1084,6 +1088,10 @@ function pluginTreeMatches( sourceRoot, '插件资源', )) { + const staged = path.join(stagedDirectory, relative); + if (!existsSync(staged)) { + return false; + } allowedFiles.add(`${relativePrefix}${relative}`); } continue; @@ -1117,6 +1125,14 @@ function pluginTreeMatches( libraryStagingEnabled(staging, target, features) ) { for (const relative of staging.files) { + const staged = path.join( + pluginDestination, + staging.sourceSubdirectory, + relative, + ); + if (!existsSync(staged)) { + return false; + } allowedFiles.add( `${plugin.name}/${staging.sourceSubdirectory}/${relative}`, ); @@ -1128,9 +1144,11 @@ function pluginTreeMatches( plugin.name === payload.plugin && nativePayloadEnabled(payload, target, features) ) { - allowedFiles.add( - `${plugin.name}/${payload.destinationSubdirectory}/${payload.destinationFileName ?? payload.sourceFileName}`, - ); + const relative = `${payload.destinationSubdirectory}/${payload.destinationFileName ?? payload.sourceFileName}`; + if (!existsSync(path.join(pluginDestination, relative))) { + return false; + } + allowedFiles.add(`${plugin.name}/${relative}`); } } } diff --git a/apps/ai-game-creator-shell/src-tauri/build_support/package_layout.rs b/apps/ai-game-creator-shell/src-tauri/build_support/package_layout.rs index b334fe265..ddd54ee55 100644 --- a/apps/ai-game-creator-shell/src-tauri/build_support/package_layout.rs +++ b/apps/ai-game-creator-shell/src-tauri/build_support/package_layout.rs @@ -308,12 +308,19 @@ pub fn validate_staged_plugins( if !plugin_staging_applies(target) { return Ok(()); } - if !destination_root.is_dir() { + let metadata = std::fs::symlink_metadata(destination_root).map_err(|error| { + format!( + "插件随包资源目录不可读 {}:{error}", + destination_root.display() + ) + })?; + if metadata.file_type().is_symlink() || !metadata.is_dir() { return Err(format!( - "插件随包资源目录缺失:{}", + "插件随包资源目录缺失或不允许符号链接:{}", destination_root.display() )); } + let declared_plugins = plugin_directories(source_root, plugins().manifest_file_name)?; let declared_names = declared_plugins .iter()