5ea453d5d1
取消开发态自动打开 Agent 聊天窗口 统一客户端可见标题为陶泥儿 同步启动守卫、测试与技术文档
126 lines
3.0 KiB
JavaScript
126 lines
3.0 KiB
JavaScript
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('<title>陶泥儿</title>') &&
|
|
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);
|
|
});
|