import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { readAgcDevEndpoint, resolveAgcDevEndpoint, withAgcDevEndpointEnv, } from './dev-port.mjs'; import { preflightExistingVite, 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 gameChat = args[0] === '--game-chat'; if (gameChat) { args.shift(); } return { gameChat, args }; } function buildTauriArguments(argv, devUrl = readAgcDevEndpoint().url) { const { gameChat, args } = parseLauncherArguments(argv); const configOverride = JSON.stringify({ build: { devUrl } }); if (gameChat) { return [ 'dev', '--config', configOverride, '--', '--', '--game-chat', ...args, ]; } const separatorIndex = args.indexOf('--'); if (separatorIndex < 0) { return ['dev', ...args, '--config', configOverride]; } return [ 'dev', ...args.slice(0, separatorIndex), '--config', configOverride, ...args.slice(separatorIndex), ]; } function spawnTauriCli(argv, { env = process.env } = {}) { return spawnChild(process.execPath, [tauriCliPath, ...argv], { cwd: appRoot, env, shell: false, }); } async function runTauriDev( argv = process.argv.slice(2), { resolveDevEndpoint = resolveAgcDevEndpoint, preflight = preflightExistingVite, spawnCli = spawnTauriCli, waitForCli = waitForChildTermination, terminateTree = terminateChildTree, } = {}, ) { const endpoint = await resolveDevEndpoint(); await preflight({ endpoint }); const tauriArguments = buildTauriArguments(argv, endpoint.url); const child = spawnCli(tauriArguments, { env: withAgcDevEndpointEnv(endpoint), }); 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; } }