const fs = require("node:fs"); const path = require("node:path"); const net = require("node:net"); const { spawn, spawnSync } = require("node:child_process"); const projectRoot = path.resolve(__dirname, ".."); const serverUrl = process.env.VITE_DEV_SERVER_URL || "http://127.0.0.1:3354"; const authServerUrl = process.env.AUTH_SERVER_URL || "http://127.0.0.1:3302"; const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm"; const requiredNodeMajor = 20; const checkOnly = process.argv.includes("--check"); function log(message = "") { process.stdout.write(`${message}\n`); } function fail(message) { process.stderr.write(`\n[start-local] ${message}\n`); process.exit(1); } function parseUrl(url, name) { try { return new URL(url); } catch { fail(`${name} 无效:${url}`); } } function checkCommand(command, args, label) { const result = spawnSync(command, args, { cwd: projectRoot, encoding: "utf8", stdio: "pipe", shell: process.platform === "win32", }); if (result.error || result.status !== 0) { fail(`未检测到 ${label},请先安装后再运行。`); } return (result.stdout || result.stderr || "").trim(); } function checkNodeVersion() { const major = Number(process.versions.node.split(".")[0]); if (!Number.isFinite(major) || major < requiredNodeMajor) { fail(`当前 Node.js 为 ${process.version},建议使用 .nvmrc 指定的 Node.js ${requiredNodeMajor}。`); } } function checkDependencies() { const viteBin = path.join(projectRoot, "node_modules", ".bin", process.platform === "win32" ? "vite.cmd" : "vite"); const electronPackage = path.join(projectRoot, "node_modules", "electron", "package.json"); if (!fs.existsSync(viteBin) || !fs.existsSync(electronPackage)) { fail("依赖尚未安装,请先执行:npm install"); } } function assertPortAvailable(host, port, label) { return new Promise((resolve, reject) => { const server = net.createServer(); server.once("error", error => { if (error.code === "EADDRINUSE") { reject(new Error(`${label} 端口 ${port} 已被占用,请先关闭已有进程。`)); return; } reject(error); }); server.once("listening", () => { server.close(resolve); }); server.listen(port, host); }); } function canConnect(host, port) { return new Promise(resolve => { const socket = net.createConnection({ host, port }); const finish = ok => { socket.destroy(); resolve(ok); }; socket.setTimeout(500); socket.once("connect", () => finish(true)); socket.once("timeout", () => finish(false)); socket.once("error", () => finish(false)); }); } async function startLocalAuth(authUrl) { const authHost = authUrl.hostname || "127.0.0.1"; const authPort = Number(authUrl.port || 3302); if (await canConnect(authHost, authPort)) { log(`本地认证服务已在运行:${authServerUrl}`); return null; } const child = spawn(process.execPath, ["scripts/local-auth-server.cjs"], { cwd: projectRoot, stdio: "inherit", env: { ...process.env, LOCAL_AUTH_HOST: authHost, LOCAL_AUTH_PORT: String(authPort), }, shell: false, }); child.on("error", error => { fail(`本地认证服务启动失败:${error.message}`); }); for (let attempt = 0; attempt < 40; attempt++) { if (await canConnect(authHost, authPort)) { return child; } await new Promise(resolve => setTimeout(resolve, 250)); } child.kill(); fail(`本地认证服务未能在 ${authServerUrl} 启动。`); } async function main() { process.chdir(projectRoot); const parsedServerUrl = parseUrl(serverUrl, "VITE_DEV_SERVER_URL"); const parsedAuthUrl = parseUrl(authServerUrl, "AUTH_SERVER_URL"); const viteHost = parsedServerUrl.hostname || "127.0.0.1"; const vitePort = Number(parsedServerUrl.port || 3354); log("========================================"); log(" 本地快速启动:Local Auth + Vite + Electron"); log("========================================"); log(`项目目录:${projectRoot}`); log(`认证服务:${authServerUrl}`); log(`开发地址:${serverUrl}`); log(""); checkNodeVersion(); const npmVersion = checkCommand(npmCommand, ["--version"], "npm"); checkDependencies(); await assertPortAvailable(viteHost, vitePort, "Vite"); log(`Node.js:${process.version}`); log(`npm:${npmVersion}`); log(""); if (checkOnly) { log("自检通过:可以执行 npm run start:local 或双击 start-local.bat 启动。"); return; } const authChild = await startLocalAuth(parsedAuthUrl); log(""); log("正在启动应用,关闭此窗口即可停止开发服务。"); log("默认本地账号:13800138000 / 123456"); log(""); const appChild = spawn(npmCommand, ["run", "dev:electron"], { cwd: projectRoot, stdio: "inherit", env: { ...process.env, VITE_DEV_SERVER_URL: serverUrl, AUTH_SERVER_URL: authServerUrl, }, shell: process.platform === "win32", }); const shutdown = code => { if (authChild && !authChild.killed) { authChild.kill(); } process.exit(code ?? 0); }; process.on("SIGINT", () => shutdown(0)); process.on("SIGTERM", () => shutdown(0)); appChild.on("exit", shutdown); appChild.on("error", error => { fail(`启动失败:${error.message}`); }); } main().catch(error => { fail(error.message); });