2026-01-15 20:54:21 +08:00
|
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成桌面端内置的 Next standalone 产物到 `desktop-electron/desktop-next`。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 目标:
|
|
|
|
|
|
* - 打包时不依赖用户机器上的 pnpm / node 环境即可运行(Electron 自身携带 Node)。
|
|
|
|
|
|
* - 运行期文件读写落到安装目录的 data/(由 Electron 注入 MNOTE_DATA_DIR)。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 注意:需要 wolai-frontend 的 next.config.ts 开启 output: "standalone"。
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
const { spawnSync } = require("child_process");
|
|
|
|
|
|
const fs = require("fs");
|
|
|
|
|
|
const path = require("path");
|
|
|
|
|
|
|
|
|
|
|
|
const rootDir = path.resolve(__dirname, "..");
|
|
|
|
|
|
const frontendDir = path.join(rootDir, "wolai-frontend");
|
|
|
|
|
|
const outDir = path.join(rootDir, "desktop-electron", "desktop-next");
|
|
|
|
|
|
|
|
|
|
|
|
function parseDotenv(filePath) {
|
|
|
|
|
|
if (!fs.existsSync(filePath)) return {};
|
|
|
|
|
|
const raw = fs.readFileSync(filePath, { encoding: "utf8" });
|
|
|
|
|
|
const out = {};
|
|
|
|
|
|
for (const line of raw.split(/\r?\n/)) {
|
|
|
|
|
|
const trimmed = line.trim();
|
|
|
|
|
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
|
|
|
|
const idx = trimmed.indexOf("=");
|
|
|
|
|
|
if (idx === -1) continue;
|
|
|
|
|
|
const key = trimmed.slice(0, idx).trim();
|
|
|
|
|
|
const value = trimmed.slice(idx + 1).trim();
|
|
|
|
|
|
if (!key) continue;
|
|
|
|
|
|
out[key] = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function ensureDir(dir) {
|
|
|
|
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function rmDir(dir) {
|
|
|
|
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function run(command, cwd, env) {
|
|
|
|
|
|
const r = spawnSync(command, {
|
|
|
|
|
|
cwd,
|
|
|
|
|
|
env: { ...process.env, ...env },
|
|
|
|
|
|
stdio: "inherit",
|
|
|
|
|
|
shell: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (r.status !== 0) {
|
|
|
|
|
|
process.exit(r.status ?? 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function assertExists(filePath, hint) {
|
|
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
|
|
|
|
console.error(`\n[prepare-desktop-next] 缺少:${filePath}`);
|
|
|
|
|
|
if (hint) console.error(`[prepare-desktop-next] 提示:${hint}`);
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function copyDir(from, to) {
|
|
|
|
|
|
// 关键:Next standalone 在 pnpm 环境下可能包含大量 symlink/junction。
|
|
|
|
|
|
// electron-builder 在复制 extraResources 时对链接目录处理不稳定,
|
|
|
|
|
|
// 因此这里强制“解引用复制”,把真实文件复制出来,确保运行期能 require('next')。
|
|
|
|
|
|
fs.cpSync(from, to, { recursive: true, force: true, dereference: true });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function main() {
|
|
|
|
|
|
console.log("[prepare-desktop-next] 开始构建 wolai-frontend(standalone)…");
|
|
|
|
|
|
|
2026-02-01 08:47:40 +08:00
|
|
|
|
// 约定:全局仅使用仓库根目录的 .env.all 作为配置来源(生产优先)。
|
|
|
|
|
|
// 桌面端如需覆盖,可通过系统环境变量注入(例如在启动命令前临时设置)。
|
|
|
|
|
|
const envAllPath = path.join(rootDir, ".env.all");
|
|
|
|
|
|
assertExists(envAllPath, "请先在仓库根目录创建 .env.all(全局唯一 env 文件)。");
|
2026-01-15 20:54:21 +08:00
|
|
|
|
const desktopEnv = {
|
2026-02-01 08:47:40 +08:00
|
|
|
|
...parseDotenv(envAllPath),
|
2026-01-15 20:54:21 +08:00
|
|
|
|
};
|
|
|
|
|
|
console.log("[prepare-desktop-next] 桌面端 env 摘要:", {
|
|
|
|
|
|
NEXT_PUBLIC_BACKEND_URL: desktopEnv.NEXT_PUBLIC_BACKEND_URL,
|
|
|
|
|
|
NEXT_PUBLIC_ONLYOFFICE_BASE_URL: desktopEnv.NEXT_PUBLIC_ONLYOFFICE_BASE_URL,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
run("pnpm -C wolai-frontend build", rootDir, {
|
|
|
|
|
|
NODE_ENV: "production",
|
|
|
|
|
|
NEXT_TELEMETRY_DISABLED: "1",
|
|
|
|
|
|
...desktopEnv,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const standaloneDir = path.join(frontendDir, ".next", "standalone");
|
|
|
|
|
|
const staticDir = path.join(frontendDir, ".next", "static");
|
|
|
|
|
|
const publicDir = path.join(frontendDir, "public");
|
|
|
|
|
|
|
|
|
|
|
|
assertExists(
|
|
|
|
|
|
path.join(standaloneDir, "server.js"),
|
|
|
|
|
|
'请在 wolai-frontend/next.config.ts 增加 `output: "standalone"` 后重试。',
|
|
|
|
|
|
);
|
|
|
|
|
|
assertExists(staticDir, "Next build 未生成 .next/static(可能构建失败或目录异常)。");
|
|
|
|
|
|
assertExists(publicDir, "wolai-frontend/public 不存在。");
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`[prepare-desktop-next] 清理旧产物:${outDir}`);
|
|
|
|
|
|
rmDir(outDir);
|
|
|
|
|
|
ensureDir(outDir);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("[prepare-desktop-next] 复制 standalone …");
|
|
|
|
|
|
copyDir(standaloneDir, outDir);
|
|
|
|
|
|
|
|
|
|
|
|
console.log("[prepare-desktop-next] 补齐 .next/static …");
|
|
|
|
|
|
ensureDir(path.join(outDir, ".next"));
|
|
|
|
|
|
copyDir(staticDir, path.join(outDir, ".next", "static"));
|
|
|
|
|
|
|
|
|
|
|
|
console.log("[prepare-desktop-next] 补齐 public …");
|
|
|
|
|
|
copyDir(publicDir, path.join(outDir, "public"));
|
|
|
|
|
|
|
|
|
|
|
|
console.log("[prepare-desktop-next] 完成。");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
main();
|