修复AI游戏创作壳跨平台启动
Project CI / Frontend tests (pull_request) Successful in 2m28s
Project CI / Repository checks (pull_request) Successful in 3m17s
Project CI / Backend tests (pull_request) Successful in 10m26s
Project CI / Native shell tests (pull_request) Failing after 6m19s

修复 macOS 下 Unix 文件身份比较和临时目录测试兼容
隔离 AI 游戏创作本地数据库与发布身份并阻止旧 schema 降级启动
完善 Tauri 开发栈错误传播和 POSIX 子进程树清理
跳过 macOS 不支持的进程指标回调以消除周期告警
补充开发调度测试、技术方案和团队排障记忆
This commit is contained in:
2026-07-22 15:21:33 +08:00
parent 017e956724
commit 034a7f6651
10 changed files with 150 additions and 22 deletions
@@ -13,6 +13,7 @@ const viteUrl = `http://${viteHost}:${vitePort}/`;
const viteMarkerUrl = `${viteUrl}__agc_dev_server.json`;
const defaultApiTarget = process.env.RUST_SERVER_TARGET || 'http://127.0.0.1:8082';
const backendDatabase = 'genarrative-game-creator-dev';
const backendSpacetimeDataDir = 'server-rs/.spacetimedb/ai-game-creator/data';
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
function readJson(path) {
@@ -146,9 +147,13 @@ async function isExistingVitePairedWithBackend(apiTarget) {
}
function spawnChild(command, args, options) {
const useShell = process.platform === 'win32';
return spawn(command, args, {
...options,
shell: true,
shell: useShell,
// POSIX 下让每个长驻服务拥有独立进程组,退出时可以连同 npm、
// Node、Cargo 及其子进程一起清理,避免残留订阅任务持续刷日志。
detached: !useShell,
stdio: 'inherit',
});
}
@@ -158,9 +163,17 @@ function stopChild(child, signal = 'SIGTERM') {
return;
}
try {
child.kill(signal);
if (process.platform !== 'win32' && Number.isInteger(child.pid)) {
process.kill(-child.pid, signal);
} else {
child.kill(signal);
}
} catch {
// ignore cleanup races
try {
child.kill(signal);
} catch {
// ignore cleanup races
}
}
}
@@ -200,6 +213,8 @@ async function ensureBackend() {
'--',
'--database',
backendDatabase,
'--spacetime-data-dir',
backendSpacetimeDataDir,
'--no-interactive',
],
{ cwd: appRoot },
@@ -336,8 +336,8 @@ fn verify_unix_agent_db_root(root: &Path, opened: &File) -> Result<(), String> {
let metadata = opened
.metadata()
.map_err(|error| format!("复核 Agent DB 项目目录句柄失败:{error}"))?;
if stat.st_dev != metadata.dev()
|| stat.st_ino != metadata.ino()
if stat.st_dev as u64 != metadata.dev()
|| stat.st_ino as u64 != metadata.ino()
|| stat.st_mode & libc::S_IFMT != libc::S_IFDIR
{
return Err("Agent DB 项目目录在安全打开期间发生替换或不是普通目录".to_string());
@@ -380,8 +380,8 @@ fn verify_unix_agent_db_entry(
} else {
libc::S_IFREG
};
if stat.st_dev != metadata.dev()
|| stat.st_ino != metadata.ino()
if stat.st_dev as u64 != metadata.dev()
|| stat.st_ino as u64 != metadata.ino()
|| stat.st_mode & libc::S_IFMT != expected_type
{
return Err(format!("{label}在安全打开期间发生替换"));
@@ -1200,8 +1200,8 @@ fn verify_unix_project_owner_entry(
} else {
libc::S_IFREG
};
if stat.st_dev != opened_metadata.dev()
|| stat.st_ino != opened_metadata.ino()
if stat.st_dev as u64 != opened_metadata.dev()
|| stat.st_ino as u64 != opened_metadata.ino()
|| stat.st_mode & libc::S_IFMT != expected_type
{
return Err(format!("{label} 在安全打开期间发生替换"));
@@ -1286,7 +1286,10 @@ fn unique_project_path() -> PathBuf {
.expect("system clock should be after epoch")
.as_millis();
let counter = TEST_PROJECT_COUNTER.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!(
let temp_root = std::env::temp_dir()
.canonicalize()
.unwrap_or_else(|_| std::env::temp_dir());
temp_root.join(format!(
"genarrative-ai-game-creator-test-{}-{millis}-{counter}",
std::process::id()
))
@@ -1673,8 +1673,8 @@ fn verify_unix_tool_plan_entry(
} else {
libc::S_IFREG
};
if stat.st_dev != opened_metadata.dev()
|| stat.st_ino != opened_metadata.ino()
if stat.st_dev as u64 != opened_metadata.dev()
|| stat.st_ino as u64 != opened_metadata.ino()
|| stat.st_mode & libc::S_IFMT != expected_type
{
return Err(format!("{label} 在安全扫描期间发生替换"));