162 lines
No EOL
4.4 KiB
TypeScript
162 lines
No EOL
4.4 KiB
TypeScript
import { spawn } from "child_process";
|
|
import { localhost, logsDir, userDataDir } from "./constants";
|
|
import http from "http";
|
|
import fs from "fs";
|
|
|
|
// @ts-ignore
|
|
import handler from "serve-handler";
|
|
import path from "path";
|
|
|
|
export async function startFastApiServer(
|
|
directory: string,
|
|
port: number,
|
|
env: FastApiEnv,
|
|
isDev: boolean,
|
|
) {
|
|
// Start FastAPI server
|
|
let command: string;
|
|
let args: string[];
|
|
|
|
if (isDev) {
|
|
command = "uv";
|
|
args = ["run", "python", "server.py", "--port", port.toString(), "--reload", "true"];
|
|
} else {
|
|
const binary = process.platform === "win32" ? "fastapi.exe" : "fastapi";
|
|
command = path.join(directory, binary);
|
|
args = ["--port", port.toString()];
|
|
}
|
|
|
|
const safeLog = (data: Buffer | string, logPath: string) => {
|
|
try {
|
|
fs.appendFileSync(logPath, data);
|
|
} catch {
|
|
/* ignore if logs dir not writable */
|
|
}
|
|
};
|
|
const fastapiLogPath = path.join(logsDir, "fastapi-server.log");
|
|
|
|
const fastApiProcess = spawn(
|
|
command,
|
|
args,
|
|
{
|
|
cwd: directory,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
env: { ...process.env, ...env },
|
|
windowsHide: process.platform === "win32" && !isDev,
|
|
}
|
|
);
|
|
fastApiProcess.stdout.on("data", (data: any) => {
|
|
safeLog(data, fastapiLogPath);
|
|
console.log(`FastAPI: ${data}`);
|
|
});
|
|
fastApiProcess.stderr.on("data", (data: any) => {
|
|
safeLog(data, fastapiLogPath);
|
|
console.error(`FastAPI: ${data}`);
|
|
});
|
|
fastApiProcess.on("error", (err) => {
|
|
safeLog(`Spawn error: ${err.message}\n`, fastapiLogPath);
|
|
});
|
|
return {
|
|
process: fastApiProcess,
|
|
ready: waitForServer(`${localhost}:${port}/docs`),
|
|
};
|
|
}
|
|
|
|
export async function startNextJsServer(
|
|
directory: string,
|
|
port: number,
|
|
env: NextJsEnv,
|
|
isDev: boolean,
|
|
) {
|
|
let nextjsProcess;
|
|
|
|
if (isDev) {
|
|
// Windows: npm is npm.cmd; spawn() needs a shell or ENOENT.
|
|
nextjsProcess = spawn(
|
|
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
["run", "dev", "--", "-p", port.toString()],
|
|
{
|
|
cwd: directory,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
env: { ...process.env, ...env },
|
|
shell: process.platform === "win32",
|
|
}
|
|
);
|
|
const nextjsLogPath = path.join(logsDir, "nextjs-server.log");
|
|
const safeNextLog = (d: Buffer | string) => {
|
|
try {
|
|
fs.appendFileSync(nextjsLogPath, d);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
};
|
|
nextjsProcess.stdout.on("data", (data: any) => {
|
|
safeNextLog(data);
|
|
console.log(`NextJS: ${data}`);
|
|
});
|
|
nextjsProcess.stderr.on("data", (data: any) => {
|
|
safeNextLog(data);
|
|
console.error(`NextJS: ${data}`);
|
|
});
|
|
nextjsProcess.on("error", (err: Error) => {
|
|
safeNextLog(`Spawn error: ${err.message}\n`);
|
|
console.error(`NextJS spawn error: ${err.message}`);
|
|
});
|
|
nextjsProcess.on("exit", (code: number | null, signal: string | null) => {
|
|
console.error(`NextJS process exited unexpectedly: code=${code}, signal=${signal}`);
|
|
});
|
|
} else {
|
|
// Start NextJS build server
|
|
nextjsProcess = await startNextjsBuildServer(directory, port);
|
|
}
|
|
|
|
return {
|
|
process: nextjsProcess,
|
|
ready: waitForServer(`${localhost}:${port}`),
|
|
};
|
|
}
|
|
|
|
function startNextjsBuildServer(directory: string, port: number): Promise<http.Server> {
|
|
return new Promise((resolve, reject) => {
|
|
const server = http.createServer((req, res) => {
|
|
return handler(req, res, {
|
|
public: directory,
|
|
cleanUrls: true,
|
|
});
|
|
});
|
|
server.on("error", reject);
|
|
server.listen(port, () => {
|
|
server.off("error", reject);
|
|
resolve(server);
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
async function waitForServer(url: string, timeout = 120000): Promise<void> {
|
|
const startTime = Date.now();
|
|
|
|
while (Date.now() - startTime < timeout) {
|
|
try {
|
|
await new Promise<void>((resolve, reject) => {
|
|
const req = http.get(url, (res) => {
|
|
res.resume();
|
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 500) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
|
}
|
|
});
|
|
req.on('error', reject);
|
|
req.setTimeout(5000, () => {
|
|
req.destroy();
|
|
reject(new Error('Request timed out'));
|
|
});
|
|
});
|
|
return;
|
|
} catch (error) {
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
}
|
|
throw new Error(`Server did not start within ${timeout}ms`);
|
|
} |