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,