diff --git a/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/build.rs b/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/build.rs index 1f9226ef5..be8d993ba 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/build.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/export/draft/xhs_minitool/build.rs @@ -203,4 +203,51 @@ mod tests { .expect("产物在就应当成功"); assert!(result.output_tail.contains("3 个条目")); } + + /// 真起进程跑一遍宿主链路:解析 npm 包、走 `command.exec` 边界执行项目脚本、再按产物判据收尾。 + /// + /// 默认 `#[ignore]`:它要本机 `node` / `npm` 与可用的命令沙箱,慢且依赖宿主环境,不进常规单测。 + /// 手动跑:`cargo test --bin genarrative-ai-game-creator-shell -- --ignored executes_the_project_script`。 + #[test] + #[ignore = "需要本机 node/npm 与可用命令沙箱,手动用 --ignored 运行"] + fn executes_the_project_script_and_requires_a_non_empty_artifact_each_time() { + const MARKER: &str = "打包完成:3 个条目"; + let root = fixture_root("real-run"); + std::fs::write( + root.join("package.json"), + format!( + "{{\"name\":\"xhs-real-run-fixture\",\"private\":true,\"scripts\":{{\"{SCRIPT_NAME}\":\"node make-artifact.mjs\"}}}}" + ), + ) + .unwrap(); + std::fs::write( + root.join("make-artifact.mjs"), + format!( + "import {{ mkdirSync, writeFileSync }} from 'node:fs';\n\ + mkdirSync('.export', {{ recursive: true }});\n\ + writeFileSync('.export/xhs-minitool.zip', Buffer.from('PK\\u0003\\u0004fixture'));\n\ + console.log('{MARKER}');\n" + ), + ) + .unwrap(); + + let first = tauri::async_runtime::block_on(run(&root)).expect("真实脚本应当跑通"); + assert!( + first.output_tail.contains(MARKER), + "输出尾部应当带回脚本的 stdout:{}", + first.output_tail + ); + + // 每一次导出都重新构建:删掉产物再点一次导出,必须是脚本重新产出的那一份。 + std::fs::remove_file(artifact_path(&root)).expect("删掉第一次的产物"); + tauri::async_runtime::block_on(run(&root)).expect("第二次导出也应当重新产出"); + assert!(artifact_path(&root).is_file(), "产物必须被重新写出来"); + + // 脚本"成功"却没产出:改成什么都不写,必须报 artifactMissing 而不是假成功。 + std::fs::write(root.join("make-artifact.mjs"), "console.log('nothing');\n").unwrap(); + std::fs::remove_file(artifact_path(&root)).unwrap(); + let error = + tauri::async_runtime::block_on(run(&root)).expect_err("脚本没产出 zip 时必须失败"); + assert!(matches!(error, XHSMiniToolExportError::ArtifactMissing(_))); + } } diff --git a/docs/project-memory/plans/【实施计划】导出产物面板与小红书小工具导出-2026-10-05.md b/docs/project-memory/plans/【实施计划】导出产物面板与小红书小工具导出-2026-10-05.md index ec631b3d9..fccc850f6 100644 --- a/docs/project-memory/plans/【实施计划】导出产物面板与小红书小工具导出-2026-10-05.md +++ b/docs/project-memory/plans/【实施计划】导出产物面板与小红书小工具导出-2026-10-05.md @@ -30,6 +30,7 @@ ## 验证命令与操作 - `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell export::` +- 真实起进程跑宿主链路(默认 `#[ignore]`,要本机 node/npm 与可用命令沙箱):`cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml --bin genarrative-ai-game-creator-shell -- --ignored executes_the_project_script` - `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml export_bindings`,随后 `npm run check:generated-bindings` - `npx vitest run apps/ai-game-creator-shell/tests/artifactsPanel.test.tsx apps/ai-game-creator-shell/tests/xhsMinitoolExport.test.tsx` - `npm run ai-game-creator-shell:typecheck` @@ -53,10 +54,10 @@ - `91741f9de` 共享注册表与 export 模块骨架;`2a70926a3` 小红书 DTO / 错误 / 布局 / 校验与 ts-rs 绑定;`b6bef5b10` 生成目录加入 prettier 忽略;`3a1999e11` 三个命令 + 构建 + desktop 接线。 - `d694c183d` 前端行为层(命令入口、指令正文、失败说法、字段元数据、下载、状态 hook);`9a8d77dd9` 面板左半(表单 / 冲突 / 失败卡片与三颗按钮);`f8517abaa` 前端契约测试(10 例)。 - `95323ce54` skill 的 `--zip-out` 与 AGC 接入契约(manifest `2026-08-26.41`);`3369f76b1` `.export` 快照同步排除;`faa93929b` 文档回写。 -- `5e2426c8a` 导出收尾判据抽成 `conclude(...)` 并补 5 例确定性用例(非零退出 / 超时 / 产物缺失 / 空产物 / 成功),同时去掉 `XHSMiniToolExportRunResult.exitCode` 这个恒为 0 的死字段;`463341c4b` 导出成功后把脚本输出尾部报给用户。 +- `5e2426c8a` 导出收尾判据抽成 `conclude(...)` 并补 5 例确定性用例(非零退出 / 超时 / 产物缺失 / 空产物 / 成功),同时去掉 `XHSMiniToolExportRunResult.exitCode` 这个恒为 0 的死字段;`463341c4b` 导出成功后把脚本输出尾部报给用户;`b3be3ee71` 证据口径修正。 已验证:Rust `export::` 49 例(`export::draft::xhs_minitool` 33 例)、`export_bindings` 115 例、`project_snapshot` 22 例、`check:generated-bindings`(117 个文件)、前端 vitest 15 例(新增 11 + 既有 4)、`ai-game-creator-shell:typecheck`、`agc:skill-pack:check`、`check:doc-index`、`check:encoding`、`git diff --check`。 -未验证(唯一开口项):真实 vite 项目上的首轮适配闭环——agent 跑通 `build:xhs-minitool`、产出可上传 zip、二次导出零 agent 调用;以及 `.export/` 换机后重新适配的体感。 +未验证(唯一开口项):真实 vite 项目上的首轮适配闭环——agent 跑通 `build:xhs-minitool`、产出可上传 zip、二次导出零 agent 调用;以及 `.export/` 换机后重新适配的体感。宿主侧这一段已尽力自动化:`build.rs` 的 `executes_the_project_script_and_requires_a_non_empty_artifact_each_time`(`#[ignore]`)会真起进程执行项目脚本,覆盖「第一次产出 → 删掉产物再跑一次重新产出 → 脚本成功但没产出报 artifactMissing」。本机尝试运行被容器环境挡下:沙箱里解析到的 `npm` 是 fnm 包装脚本、却由另一个 node 版本执行(`Cannot find module '../lib/cli.js'`,`Node.js v26.10.0`),同一脚本在普通 shell 里 `npm run build:xhs-minitool` 正常产出 zip。该用例留给有健康沙箱与 node 环境的机器一键复跑。 已知环境噪声:本机全量 `cargo test --bin` 有 12 例与本变更无关的失败(`process_session` / `command_sandbox_trampoline` / `runner` 的 pty 与 GUI 锁、`resource_editor` 的外部 HTTP 连接被拒),失败用例均不经过本次改动的路径。 diff --git a/docs/project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md b/docs/project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md index a68d80106..713088e75 100644 --- a/docs/project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md +++ b/docs/project-memory/plans/【里程碑】导出产物面板与小红书小工具导出-2026-10-05.md @@ -54,5 +54,5 @@ - 前端 `npx vitest run tests/xhsMinitoolExport.test.tsx`(11 例)与既有 `tests/artifactsPanel.test.tsx`(4 例)通过;`npm run ai-game-creator-shell:typecheck` 通过。 - `.export/` 快照排除有定向用例:`project_snapshot::tests::project_snapshot_sync_policy_keeps_agent_state_and_still_blocks_credentials` 同时钉住「同步排除」与「通用口径不排除(Agent 仍可写)」。 - `npm run agc:skill-pack:sync` → `agc:skill-pack:check` 通过(`manifest.json` 版本 `2026-08-26.41`,文件数不变,仍 36 条)。 -- 运行时(**未验证**,本里程碑唯一开口项):在真实 vite 项目上由 agent 完成首轮适配、产出可人工上传的 zip、二次导出零 agent 调用;`.export/` 换机后需重新适配的体感也需一并确认。 +- 运行时(**未验证**,本里程碑唯一开口项):在真实 vite 项目上由 agent 完成首轮适配、产出可人工上传的 zip、二次导出零 agent 调用;`.export/` 换机后需重新适配的体感也需一并确认。宿主侧这一段已有默认 `#[ignore]` 的真起进程用例(`executes_the_project_script_and_requires_a_non_empty_artifact_each_time`:第一次产出 → 删掉产物重跑必须重新产出 → 脚本成功但没产出报 artifactMissing),一键复跑命令见实施计划;本次尝试被容器环境的 npm/沙箱解析挡下,不是代码问题。 - 边界(已覆盖):注册表损坏、脚本缺失、脚本非零退出、产物缺失、icon 越界与符号链接、目录遍历拒绝。