Files
WYF-koubo/scripts/start-local.cjs
T
2026-06-20 10:13:00 +08:00

197 lines
5.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});