修复AGC随包资源校验边界
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m28s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m4s
Project CI / Backend tests (pull_request) Successful in 3m43s
Project CI / Frontend tests (pull_request) Successful in 2m7s
Project CI / Native shell tests (pull_request) Successful in 5m38s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m37s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 9m14s
Project CI / AI game creator shell web tests (pull_request) Successful in 1m38s
Project CI / Repository checks (pull_request) Successful in 2m14s

修复变参 feature 解析、Windows Codex 缓存幂等性、插件缓存产物检查和插件根目录链接校验。

补齐 staging/backup 忽略规则与 feature 回归测试。
This commit is contained in:
2026-10-01 11:35:27 +08:00
parent 7e60484633
commit e7fa90ae18
6 changed files with 68 additions and 13 deletions
+4
View File
@@ -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/
@@ -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,
@@ -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;
@@ -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'],
);
});
@@ -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}`);
}
}
}
@@ -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()