mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-06-18 15:54:08 +08:00
fix(dev-runtime): 统一开发端口并优化页面初始化
- 新增 dev 启动脚本,自动分配前后端端口并等待 Nuxt 就绪后再启动 Electron - 开发模式忽略持久化 API 覆盖,统一 Nuxt 与桌面端端口配置 - API 健康检查改为挂载后执行,聊天页预取改为 lazy,并隐藏搜索区账号切换
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
const http = require("http");
|
||||
const net = require("net");
|
||||
const path = require("path");
|
||||
const { spawn, spawnSync } = require("child_process");
|
||||
|
||||
const repoRoot = path.resolve(__dirname, "..", "..");
|
||||
const frontendDir = path.join(repoRoot, "frontend");
|
||||
const desktopDir = path.join(repoRoot, "desktop");
|
||||
|
||||
function parsePort(value) {
|
||||
const n = Number.parseInt(String(value || "").trim(), 10);
|
||||
return Number.isInteger(n) && n >= 1 && n <= 65535 ? n : null;
|
||||
}
|
||||
|
||||
function log(message) {
|
||||
process.stdout.write(`[dev] ${message}\n`);
|
||||
}
|
||||
|
||||
function prefixPipe(stream, prefix) {
|
||||
if (!stream) return;
|
||||
let pending = "";
|
||||
stream.setEncoding("utf8");
|
||||
stream.on("data", (chunk) => {
|
||||
pending += chunk;
|
||||
const lines = pending.split(/\r?\n/);
|
||||
pending = lines.pop() || "";
|
||||
for (const line of lines) {
|
||||
process.stdout.write(`${prefix} ${line}\n`);
|
||||
}
|
||||
});
|
||||
stream.on("end", () => {
|
||||
const tail = pending.trim();
|
||||
if (tail) process.stdout.write(`${prefix} ${tail}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
function isPortAvailable(port, host) {
|
||||
return new Promise((resolve) => {
|
||||
const server = net.createServer();
|
||||
const done = (ok) => {
|
||||
try {
|
||||
server.close();
|
||||
} catch {}
|
||||
resolve(ok);
|
||||
};
|
||||
server.once("error", () => done(false));
|
||||
server.once("listening", () => done(true));
|
||||
server.listen(port, host);
|
||||
});
|
||||
}
|
||||
|
||||
async function choosePort({ label, envName, preferredPort, host, searchLimit = 20 }) {
|
||||
if (preferredPort != null) {
|
||||
const ok = await isPortAvailable(preferredPort, host);
|
||||
if (!ok) throw new Error(`${label}端口 ${preferredPort} 已被占用,请修改环境变量 ${envName}`);
|
||||
return preferredPort;
|
||||
}
|
||||
|
||||
const startPort = envName === "NUXT_PORT" ? 3000 : 10392;
|
||||
for (let port = startPort; port <= startPort + searchLimit; port += 1) {
|
||||
if (await isPortAvailable(port, host)) return port;
|
||||
}
|
||||
throw new Error(`未找到可用的${label}端口(起始 ${startPort})`);
|
||||
}
|
||||
|
||||
function httpReady(url) {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.get(url, (res) => {
|
||||
res.resume();
|
||||
resolve(true);
|
||||
});
|
||||
req.on("error", () => resolve(false));
|
||||
req.setTimeout(1000, () => {
|
||||
req.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForUrl(url, child, timeoutMs) {
|
||||
const startedAt = Date.now();
|
||||
while (Date.now() - startedAt < timeoutMs) {
|
||||
if (child.exitCode != null) {
|
||||
throw new Error(`前端进程提前退出,exitCode=${child.exitCode}`);
|
||||
}
|
||||
if (await httpReady(url)) return;
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
}
|
||||
throw new Error(`等待前端启动超时:${url}`);
|
||||
}
|
||||
|
||||
function killChild(child) {
|
||||
if (!child || child.killed || child.exitCode != null) return;
|
||||
if (process.platform === "win32") {
|
||||
spawnSync("taskkill", ["/pid", String(child.pid), "/t", "/f"], { stdio: "ignore" });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
child.kill("SIGTERM");
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function spawnLogged(command, args, options, prefix) {
|
||||
const child = spawn(command, args, {
|
||||
...options,
|
||||
shell: process.platform === "win32",
|
||||
stdio: ["inherit", "pipe", "pipe"],
|
||||
});
|
||||
prefixPipe(child.stdout, `${prefix}`);
|
||||
prefixPipe(child.stderr, `${prefix}`);
|
||||
return child;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const frontendHost = String(process.env.NUXT_HOST || "127.0.0.1").trim() || "127.0.0.1";
|
||||
const requestedFrontendPort = parsePort(process.env.NUXT_PORT);
|
||||
const requestedBackendPort = parsePort(process.env.WECHAT_TOOL_PORT);
|
||||
const frontendPort = await choosePort({
|
||||
label: "前端",
|
||||
envName: "NUXT_PORT",
|
||||
preferredPort: requestedFrontendPort,
|
||||
host: frontendHost,
|
||||
});
|
||||
const backendPort = await choosePort({
|
||||
label: "后端",
|
||||
envName: "WECHAT_TOOL_PORT",
|
||||
preferredPort: requestedBackendPort,
|
||||
host: "127.0.0.1",
|
||||
});
|
||||
const startUrl = `http://${frontendHost}:${frontendPort}`;
|
||||
|
||||
log(`frontend=${startUrl}`);
|
||||
log(`backend=http://127.0.0.1:${backendPort}/api`);
|
||||
|
||||
const sharedEnv = {
|
||||
...process.env,
|
||||
NUXT_HOST: frontendHost,
|
||||
NUXT_PORT: String(frontendPort),
|
||||
WECHAT_TOOL_PORT: String(backendPort),
|
||||
ELECTRON_START_URL: startUrl,
|
||||
};
|
||||
|
||||
const npmCommand = "npm";
|
||||
const electronCommand = "electron";
|
||||
const children = new Set();
|
||||
let shuttingDown = false;
|
||||
|
||||
const shutdown = (exitCode) => {
|
||||
if (shuttingDown) return;
|
||||
shuttingDown = true;
|
||||
for (const child of children) killChild(child);
|
||||
process.exitCode = exitCode;
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => shutdown(130));
|
||||
process.on("SIGTERM", () => shutdown(143));
|
||||
|
||||
const frontend = spawnLogged(npmCommand, ["run", "dev"], { cwd: frontendDir, env: sharedEnv }, "[frontend]");
|
||||
children.add(frontend);
|
||||
frontend.once("exit", (code, signal) => {
|
||||
log(`frontend exited code=${code} signal=${signal}`);
|
||||
shutdown(code == null ? 1 : code);
|
||||
});
|
||||
|
||||
await waitForUrl(startUrl, frontend, 60_000);
|
||||
log("frontend is ready, starting Electron");
|
||||
|
||||
const electron = spawnLogged(electronCommand, ["."], { cwd: desktopDir, env: sharedEnv }, "[electron]");
|
||||
children.add(electron);
|
||||
electron.once("exit", (code, signal) => {
|
||||
log(`electron exited code=${code} signal=${signal}`);
|
||||
shutdown(code == null ? 0 : code);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
process.stderr.write(`[dev] ${err?.stack || err}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user