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 6393efc00..060af0d67 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 @@ -95,8 +95,13 @@ fn conclude( let path = artifact_path(root); let produced = matches!( - std::fs::metadata(&path), - Ok(metadata) if metadata.is_file() && metadata.len() > 0 + std::fs::symlink_metadata(&path), + // `metadata` 会跟随符号链接:指向项目外某个非空文件的链接会被当成「产出成功」, + // 而下游下载路径又按 fail closed 拒收链接。这里用 symlink_metadata 与之一致。 + Ok(metadata) + if metadata.is_file() + && !metadata.file_type().is_symlink() + && metadata.len() > 0 ); if !produced { // 脚本"成功"却没产出:多半是 agent 把 zip 写到了别处,或 pack.mjs 的参数没对齐。 @@ -232,6 +237,25 @@ mod tests { )); } + #[cfg(unix)] + #[test] + fn a_symlinked_artifact_does_not_count() { + let root = fixture_root("symlink-artifact"); + let path = artifact_path(&root); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + let target = root.join("outside.zip"); + std::fs::write(&target, b"PK\x03\x04zip").unwrap(); + std::os::unix::fs::symlink(&target, &path).unwrap(); + + assert!( + matches!( + conclude(&root, Some(0), false, "构建完成"), + Err(XHSMiniToolExportBuildError::ArtifactMissing(_)) + ), + "指向项目外非空文件的链接不能算产出成功" + ); + } + #[test] fn a_successful_script_with_a_non_empty_artifact_returns_the_output_tail() { let root = fixture_root("ok");