import { spawn } from 'node:child_process'; import http from 'node:http'; import net from 'node:net'; import { fileURLToPath } from 'node:url'; import { resolveAgcDevEndpoint, withAgcDevEndpointEnv } from './dev-port.mjs'; const appRoot = fileURLToPath(new URL('..', import.meta.url)); const endpoint = await resolveAgcDevEndpoint(); const { host, port, url: devUrl } = endpoint; function readExistingServer() { return new Promise((resolve) => { const request = http.get( { host, port, path: '/', timeout: 1000, }, (response) => { let body = ''; response.setEncoding('utf8'); response.on('data', (chunk) => { if (body.length < 4096) { body += chunk; } }); response.on('end', () => { resolve({ statusCode: response.statusCode ?? 0, body, }); }); }, ); request.on('timeout', () => { request.destroy(); resolve(null); }); request.on('error', () => resolve(null)); }); } function isAiGameCreatorServer(response) { return ( response && response.statusCode >= 200 && response.statusCode < 500 && response.body.includes('陶泥儿') && response.body.includes('/src/main.tsx') ); } function isPortListening() { return new Promise((resolve) => { const socket = net.connect({ host, port }); socket.once('connect', () => { socket.destroy(); resolve(true); }); socket.once('error', () => resolve(false)); socket.setTimeout(1000, () => { socket.destroy(); resolve(true); }); }); } const existing = await readExistingServer(); if (existing) { if (isAiGameCreatorServer(existing)) { console.error( `[ai-game-creator-shell] ${devUrl} is already running but cannot be safely reused. Stop that process before starting the dev server.`, ); process.exit(1); } console.error( `[ai-game-creator-shell] ${devUrl} is already in use by another server. Stop that process before starting Tauri dev.`, ); process.exit(1); } if (await isPortListening()) { console.error( `[ai-game-creator-shell] ${devUrl} is already in use by a non-HTTP or unrecognized server. Stop that process before starting Tauri dev.`, ); process.exit(1); } const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const child = spawn( npm, [ '--prefix', '../..', 'exec', 'vite', '--', '--config', 'vite.config.ts', '--port', String(endpoint.port), ], { cwd: appRoot, env: withAgcDevEndpointEnv(endpoint), stdio: 'inherit', // Node 18.20+/20+/24 on Windows rejects spawning .cmd (npm.cmd) without a shell (EINVAL). shell: true, }, ); for (const signal of ['SIGINT', 'SIGTERM']) { process.on(signal, () => { child.kill(signal); }); } child.on('exit', (code, signal) => { if (signal) { process.exit(1); } process.exit(code ?? 0); });