import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { preflightExistingVite, resolveDevStackProfile, spawnChild, stopChild, terminateChildTree, waitForChildTermination, } from './start-dev-stack.mjs'; const appRoot = fileURLToPath(new URL('..', import.meta.url)); const repoRoot = resolve(appRoot, '../..'); const tauriCliPath = resolve(repoRoot, 'node_modules/@tauri-apps/cli/tauri.js'); function parseLauncherArguments(argv) { const args = [...argv]; const appRun = args[0] === '--app-run'; if (appRun) { args.shift(); } const gameChat = args[0] === '--game-chat'; if (gameChat) { args.shift(); } if (appRun && gameChat) { throw new Error('app-run profile 不能与 game-chat 入口同时使用'); } return { appRun, gameChat, args }; } function buildTauriArguments(argv) { const { appRun, gameChat, args } = parseLauncherArguments(argv); if (appRun) { return [ 'dev', '--config', 'src-tauri/tauri.app-run-dev.conf.json', ...args, ]; } if (gameChat) { return ['dev', '--', '--', '--game-chat', ...args]; } return ['dev', ...args]; } function spawnTauriCli(argv, { profileName = 'default' } = {}) { return spawnChild(process.execPath, [tauriCliPath, ...argv], { cwd: appRoot, env: { ...process.env, GENARRATIVE_AGC_DEV_PROFILE: profileName, }, shell: false, }); } async function runTauriDev( argv = process.argv.slice(2), { preflight = preflightExistingVite, spawnCli = spawnTauriCli, waitForCli = waitForChildTermination, terminateTree = terminateChildTree, } = {}, ) { const { appRun } = parseLauncherArguments(argv); const profile = resolveDevStackProfile(appRun ? 'app-run' : 'default'); await preflight({ profile }); const tauriArguments = buildTauriArguments(argv); const child = spawnCli(tauriArguments, { profileName: profile.name }); let resolveShutdown; let shutdownSignal = ''; let repeatedSignal = false; const shutdownRequested = new Promise((resolveRequest) => { resolveShutdown = resolveRequest; }); const signalHandlers = new Map(); for (const signal of ['SIGINT', 'SIGTERM']) { const handler = () => { if (!shutdownSignal) { shutdownSignal = signal; stopChild(child, 'SIGTERM'); resolveShutdown(signal); return; } repeatedSignal = true; stopChild(child, 'SIGKILL'); }; signalHandlers.set(signal, handler); process.on(signal, handler); } try { const childResult = waitForCli(child); const outcome = await Promise.race([ childResult.then((failure) => ({ type: 'exit', failure })), shutdownRequested.then((signal) => ({ type: 'signal', signal })), ]); const cleanup = await terminateTree(child, { gracefulTimeoutMs: repeatedSignal ? 0 : 2500, }); if (!cleanup.stopped) { console.error( '[ai-game-creator-shell] Tauri dev exited, but its process tree could not be fully stopped.', ); return 1; } if (outcome.type === 'signal') { return 1; } const { failure } = outcome; return failure.type === 'error' || failure.signal ? 1 : (failure.code ?? 0); } finally { for (const [signal, handler] of signalHandlers) { process.off(signal, handler); } } } function isDirectModuleExecution() { return Boolean( process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url), ); } export { buildTauriArguments, isDirectModuleExecution, parseLauncherArguments, runTauriDev, spawnTauriCli, }; if (isDirectModuleExecution()) { try { process.exitCode = await runTauriDev(); } catch (error) { console.error( `[ai-game-creator-shell] ${error instanceof Error ? error.message : String(error)}`, ); process.exitCode = 1; } }