From 694b545062fb500ee05012a4275db06e79e0d7b5 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Wed, 7 Oct 2026 17:11:38 +0800 Subject: [PATCH] =?UTF-8?q?fix(agc):=20=E4=BF=AE=20Linux-only=20=E6=82=AC?= =?UTF-8?q?=E7=A9=BA=E5=BC=95=E7=94=A8=20D1=EF=BC=8C=E5=B9=B6=E8=A1=A5?= =?UTF-8?q?=E5=90=8C=E7=B1=BB=E9=9D=99=E6=80=81=E6=8E=92=E6=9F=A5=EF=BC=88?= =?UTF-8?q?=E9=9D=9E=20Windows=20cfg=20=E5=9D=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D1(由 1a86675f4 引入):该提交删掉了 master 定义的 `resolve_project_bootstrap_spec_at` (原 command_exec.rs:645 + 生产包装 :2500),但同文件 `#[cfg(test)] mod tests` 里 `#[cfg(target_os = "linux")] #[test] fn npm_command_targets_node_plus_npm_cli_on_linux` 仍在调用它(command_exec.rs:2641)⇒ Linux 测试目标必然 E0425;Windows 生产口径看不见(cfg 盲区)。 修法(取"按真实使用面收窄"这一支):按 master 原文恢复该函数,并加 `#[cfg(all(target_os = "linux", test))]`: - 比同文件先例 `project_command_actual_target` 的 `#[cfg(target_os = "linux")]` 更窄——那个函数 Linux 生产仍在调用(沙箱启动路径),这个的唯一生产调用方 project/export.rs 已被 master 删除, 只剩本文件用例调用,故按实际使用面写成 all(target_os="linux", test),避免在 Linux 生产里留下一枚死函数。 - 引用侧有效 cfg = `#[cfg(test)] mod tests` + `#[cfg(target_os = "linux")]` 用例 = all(test, target_os=linux), 与定义侧一致 ⇒ 「引用 1 处 / 定义 1 处(同 cfg)」。 - 函数体与 master 逐字相同;未删用例、未删断言、未用 #[allow]。 同类排查(1a86675f4 里删掉/收窄的每一项符号 × 引用 × 定义 × cfg): 1. GAME_PACKAGE_UPLOAD_PROGRESS_EVENT(常量及其再导出)→ 删除;tip 引用 0 ⇒ 无悬空。 2. run_project_bootstrap_command_at → 删除;引用 0 ⇒ 无悬空。 3. project/bootstrap.rs 整模块(BOOTSTRAP_PACKAGE_MAX_BYTES / ProjectBootstrapResult / read_bootstrap_file / run_project_bootstrap_at)+ project.rs 的 `mod bootstrap;` 与 `pub(crate) use bootstrap::*;` → 删除;`bootstrap::` 与 `run_project_bootstrap_at` 引用均为 0 ⇒ 无悬空。 4. run_project_verification_with_commit_at → 删除;引用 0 ⇒ 无悬空(下游 resolve_project_verification_spec_with_cwd_at 另有 taonier/build.rs:82 与 xhs_minitool/build.rs:46 调用,未受影响)。 5. TaonierArtifactPackage 再导出 → 移除;3 处引用全在 build.rs(定义/返回类型/构造)⇒ 无悬空。 6. read_form → 删除;引用 0 ⇒ 无悬空。 7. ensure_staging_path_in_dir → 收窄为 `#[cfg(test)]`;引用 3 处 = 定义 + 同文件 `#[cfg(test)] mod tests` 两处 ⇒ 兼容(test ⊆ test)。 8. NpmScript.package_json_relative 字段 → 收窄为 `#[cfg(test)]`;读取处两处用例断言(cfg(test))+ 构造处 亦已 cfg(test) ⇒ 兼容;同名自由函数仍属生产并被 script.rs:76 使用。 9. resolve_project_bootstrap_spec_at → 本次按 D1 处理(见上),引用/定义各 1 处且 cfg 一致。 静态复查(方法自定):对 AGC 壳全部 321 个 .rs 建「定义名 → cfg 链」与「use 绑定名」表,按 `#[cfg]` 属性 + 花括号深度重建每行的有效 cfg 链,筛出非 Windows 块(target_os=linux/macos/…、unix、 not(windows))共 7315 行,再统计三类危险计数: - ① 悬空(master 有定义、tip 无定义的名字在非 Windows 块仍有引用):3 名 9 处命中,逐条核对全为假阳 (`"git"`/`"running"` 是字符串字面量;`process_identity` 是模块路径的另一符号)⇒ **0**。 - ② windows-only 定义被非 Windows 块引用:3 名 23 处,全为假阳(`is_empty` 是 std 方法调用; `platform_art_windows_directory_open_policy`、`resume_suspended_tokio` 位于嵌套的 `#[cfg(windows)] { … }` 块内,是启发式未下钻)⇒ **0**。 - ③ not(test) 越用:3 名 4 处,全为假阳(`main`/`startup` 是字符串;`set_external_agent_runner_config_dir` 的 cfg 是 `any(not(test), unix)`,unix 测试构建中存在,且命中行是该定义行本身)⇒ **0**。 说明:`cargo check --target x86_64-unknown-linux-gnu` 在本机跑不通(GTK/webkit 的 -sys 与 ring 需要 Linux 工具链,pkg-config 无法交叉配置),故 Linux 侧证据以静态法给出;master 侧的非 Windows 改动由 master 自己的 Linux CI 兜底。 验证(Windows + 编辑器 feature;取数前 `cargo clean -p genarrative-ai-game-creator-shell`): AGC bin EXIT 0 / 0 warning(18 条 serde 宏提示非 lint);AGC `--all-targets` EXIT 0(harness 实例 382 条); `npm run check:rustfmt` / `npm run check:encoding`(5549 文件)/ `git diff --check` 全 EXIT 0; 六组测试 6 / 17 / 74 / 1 / 65 / 4,全部 0 failed。 --- .../src-tauri/src/command_exec.rs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs b/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs index f5210841d..d88613688 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/command_exec.rs @@ -638,6 +638,52 @@ pub(crate) fn resolve_project_command_spec_at( .map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error)) } +/// Resolves the one privileged npm operation used to hydrate a DirectProject. +/// It intentionally bypasses the general command.exec npm allow-list: callers +/// must use the dedicated `project.bootstrap` action, which only accepts the +/// literal `npm install` in the project's `game` directory. +/// +/// 生产调用方(`project/export.rs`)已随 master 的 exports 退役删除,现存唯一引用是本文件的 +/// `npm_command_targets_node_plus_npm_cli_on_linux` 用例;因此按**真实使用面**收窄到 +/// `all(target_os = "linux", test)`——与同文件 `project_command_actual_target` 的 +/// `cfg(target_os = "linux")` 相比更窄,因为那个函数 Linux 生产仍在调用,这个已不在。 +/// 引用侧的有效 cfg(`#[cfg(test)] mod tests` + `#[cfg(target_os = "linux")]` 用例)与定义侧一致。 +#[cfg(all(target_os = "linux", test))] +pub(crate) fn resolve_project_bootstrap_spec_at( + root: &Path, + timeout_seconds: u64, +) -> Result { + validate_project_root(root) + .map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?; + let cwd_relative = "game".to_string(); + let cwd = resolve_local_project_path(root, &cwd_relative) + .map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?; + validate_project_command_cwd_components(root, &cwd_relative) + .map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?; + if !(PROJECT_COMMAND_MIN_TIMEOUT_SECONDS..=PROJECT_COMMAND_MAX_TIMEOUT_SECONDS) + .contains(&timeout_seconds) + { + return Err(ProjectCommandError::new( + ProjectCommandErrorStage::Validation, + format!("project.bootstrap timeoutSeconds 必须在 {PROJECT_COMMAND_MIN_TIMEOUT_SECONDS}-{PROJECT_COMMAND_MAX_TIMEOUT_SECONDS} 之间"), + )); + } + let program = "npm".to_string(); + let resolved = resolve_project_command_executable(root, &program) + .map_err(|error| ProjectCommandError::new(ProjectCommandErrorStage::Validation, error))?; + Ok(ProjectCommandSpec { + program, + executable: resolved.executable, + safe_path: resolved.safe_path, + arguments: vec!["install".to_string()], + cwd_relative, + cwd, + timeout_seconds, + verification_eligible: false, + node_launcher: resolved.node_launcher, + }) +} + fn resolve_project_command_spec_inner( root: &Path, program: &str,