Files
mnote/scripts/desktop-local.js
T

160 lines
4.6 KiB
JavaScript
Raw Normal View History

2026-01-15 20:54:21 +08:00
#!/usr/bin/env node
/**
* 一键启动:wolai-backend + 桌面端(Electron + 内置 Next standalone
*
* 目的:
* - 本地联调无需反复安装 NSIS 安装包
* - Electron 走内置 Next(更接近安装包运行方式)
*
* 可选环境变量:
* - SKIP_DESKTOP_NEXT_BUILD=1:跳过 build:desktop:next(仅启动)
* - PYTHON_BIN:默认 python
* - BACKEND_CMD:覆盖后端启动命令
* - ELECTRON_CMD:覆盖 Electron 启动命令
* - MNOTE_DATA_DIR:覆盖桌面端 data 目录(默认 <repo>/data-desktop-test
*/
const { spawnSync, spawn } = require("child_process");
const fs = require("fs");
const net = require("net");
const path = require("path");
const rootDir = path.resolve(__dirname, "..");
const backendDir = path.join(rootDir, "wolai-backend");
const pythonBin = process.env.PYTHON_BIN || "python";
const dataDir = process.env.MNOTE_DATA_DIR || path.join(rootDir, "data-desktop-test");
const backendCmd =
process.env.BACKEND_CMD || `${pythonBin} -m uvicorn app.main:app --reload --host 127.0.0.1 --port 8000`;
const electronCmd = process.env.ELECTRON_CMD || "electron desktop-electron/main.js";
function loadEnvFile(filePath) {
if (!fs.existsSync(filePath)) return {};
const content = fs.readFileSync(filePath, "utf8");
return content
.split(/\r?\n/)
.filter((line) => line.trim() && !line.trim().startsWith("#"))
.reduce((acc, line) => {
const idx = line.indexOf("=");
if (idx === -1) return acc;
const key = line.slice(0, idx).trim();
const value = line.slice(idx + 1).trim();
acc[key] = value;
return acc;
}, {});
}
function runOrExit(command, cwd, env) {
const r = spawnSync(command, {
cwd,
env,
stdio: "inherit",
shell: true,
});
if (r.status !== 0) process.exit(r.status ?? 1);
}
async function waitForPort(host, port, timeoutMs = 20_000) {
const start = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
const ok = await new Promise((resolve) => {
const socket = net.createConnection({ host, port });
const timer = setTimeout(() => {
socket.destroy();
resolve(false);
}, 500);
socket.once("connect", () => {
clearTimeout(timer);
socket.end();
resolve(true);
});
socket.once("error", () => {
clearTimeout(timer);
resolve(false);
});
});
if (ok) return;
if (Date.now() - start > timeoutMs) {
throw new Error(`等待端口超时:${host}:${port}`);
}
// eslint-disable-next-line no-await-in-loop
await new Promise((r) => setTimeout(r, 120));
}
}
async function main() {
const envMerged = {
...process.env,
...loadEnvFile(path.join(rootDir, ".env.local")),
...loadEnvFile(path.join(rootDir, ".env")),
...loadEnvFile(path.join(backendDir, ".env")),
PYTHONUTF8: "1",
MNOTE_DATA_DIR: dataDir,
};
const skipBuild =
(process.env.SKIP_DESKTOP_NEXT_BUILD || "").toLowerCase() === "1" ||
(process.env.SKIP_DESKTOP_NEXT_BUILD || "").toLowerCase() === "true";
if (!skipBuild) {
console.log("[desktop-local] 构建桌面端内置 Nextstandalone)…");
runOrExit("pnpm run build:desktop:next", rootDir, envMerged);
} else {
console.log("[desktop-local] 已跳过 build:desktop:nextSKIP_DESKTOP_NEXT_BUILD=1");
}
console.log(`[desktop-local] data 目录:${dataDir}`);
console.log(`[desktop-local] 后端命令:${backendCmd}`);
console.log(`[desktop-local] Electron 命令:${electronCmd}`);
const children = [];
let shuttingDown = false;
const shutdown = () => {
if (shuttingDown) return;
shuttingDown = true;
console.log("[desktop-local] 正在关闭子进程…");
for (const child of children) {
try {
if (!child.killed) child.kill("SIGINT");
} catch {}
}
setTimeout(() => process.exit(0), 200);
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
const backend = spawn(backendCmd, {
cwd: backendDir,
env: envMerged,
stdio: "inherit",
shell: true,
});
children.push(backend);
backend.on("exit", () => shutdown());
await waitForPort("127.0.0.1", 8000).catch(() => {
console.log("[desktop-local] 提示:未检测到后端 8000 端口就绪(可能仍在启动或启动失败)。");
});
const electron = spawn(electronCmd, {
cwd: rootDir,
env: envMerged,
stdio: "inherit",
shell: true,
});
children.push(electron);
electron.on("exit", () => shutdown());
}
main().catch((err) => {
console.error(err instanceof Error ? err.stack : String(err));
process.exit(1);
});