#!/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)…"); // 约定:全局仅使用仓库根目录的 .env.all 作为配置来源(生产优先)。 // 桌面端如需覆盖,可通过系统环境变量注入(例如在启动命令前临时设置)。 const envAllPath = path.join(rootDir, ".env.all"); assertExists(envAllPath, "请先在仓库根目录创建 .env.all(全局唯一 env 文件)。"); const desktopEnv = { ...parseDotenv(envAllPath), }; 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();