"use strict"; const { execSync } = require("child_process"); const fs = require("fs"); const path = require("path"); const ROOT = path.resolve(__dirname, ".."); const DIST = path.join(ROOT, "dist"); const PKG_CACHE = path.join(ROOT, ".pkg-cache"); const TARGETS = { win: { id: "node18-win-x64", ext: ".exe", os: "windows" }, linux:{ id: "node18-linux-x64", ext: "", os: "linux" }, }; const args = process.argv.slice(2); const winOnly = args.includes("--win"); const linuxOnly = args.includes("--linux"); const targets = winOnly ? ["win"] : linuxOnly ? ["linux"] : ["win", "linux"]; // ── 1. 清理 ── console.log("[1/5] 清理输出目录..."); fs.mkdirSync(DIST, { recursive: true }); for (const key of targets) { const t = TARGETS[key]; const outDir = path.join(DIST, `jms-connection-service-${t.os}`); try { fs.rmSync(outDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 500 }); } catch { console.log(` ⚠ ${t.os} 目录清理失败(可能被占用),尝试增量覆盖...`); } } // ── 2. 检查 pkg ── console.log("[2/5] 检查 pkg..."); const pkgBin = path.join(ROOT, "node_modules", ".bin", "pkg"); if (!fs.existsSync(pkgBin) && !fs.existsSync(pkgBin + ".cmd")) { console.log(" pkg 未安装,正在安装..."); execSync("npm install --save-dev pkg", { cwd: ROOT, stdio: "inherit" }); } // ── 3. pkg 编译 ── console.log("[3/5] 编译可执行文件..."); const builtFiles = []; for (const key of targets) { const t = TARGETS[key]; const baseName = `jms-connection-service-${t.os}`; const outDir = path.join(DIST, `jms-connection-service-${t.os}`); fs.mkdirSync(outDir, { recursive: true }); const outPath = path.join(outDir, baseName); console.log(` 编译 ${t.id} → ${path.relative(ROOT, outPath)}${t.ext}`); try { execSync( `npx pkg . --targets ${t.id} --output "${outPath}"`, { cwd: ROOT, stdio: "inherit", env: { ...process.env, PKG_CACHE_PATH: PKG_CACHE } } ); builtFiles.push({ key, outDir, baseName, ext: t.ext, os: t.os }); } catch (err) { console.error(` ✗ ${t.id} 编译失败: ${err.message}`); process.exit(1); } } // ── 4. 组装发布包 ── console.log("[4/5] 组装发布包..."); for (const item of builtFiles) { const { outDir } = item; // 复制配置模板 const configSrc = path.join(ROOT, "config.json"); const configDst = path.join(outDir, "config.json"); fs.copyFileSync(configSrc, configDst); // 创建 logs 目录 fs.mkdirSync(path.join(outDir, "logs"), { recursive: true }); // 写入使用说明 const readme = [ "JMS 透析机 TCP 联机服务", "========================", "", "快速开始:", " 1. 编辑 config.json,配置设备 IP、端口、序列号", " 2. 命令行启动:", ` Windows: .\\jms-connection-service-windows.exe`, ` Linux: ./jms-connection-service-linux`, " 3. 浏览器访问 http://localhost:3100 查看监控大屏", "", "目录结构:", ` jms-connection-service-${item.os}/`, ` ├── jms-connection-service-${item.os}${item.ext} # 主程序`, " ├── config.json # 配置文件(可编辑)", " └── logs/ # 日志目录(自动创建)", "", "注册为系统服务(推荐生产环境):", ` Windows: sc create JMSConnection binPath= "完整路径\\jms-connection-service-windows.exe" start= auto`, " Linux: 参考 DEPLOY.md 中的 PM2 / systemd 章节", "", "详细文档见 DEPLOY.md", ].join("\n"); fs.writeFileSync(path.join(outDir, "README.txt"), readme, "utf-8"); console.log(` ${item.os}: ${path.relative(ROOT, outDir)}/ 组装完成`); } // ── 5. 打包归档 ── console.log("[5/5] 打包归档..."); for (const item of builtFiles) { const { outDir, baseName, os } = item; const dirName = path.basename(outDir); if (os === "windows") { const zipPath = path.join(DIST, `${baseName}.zip`); try { // 清除旧 zip try { fs.unlinkSync(zipPath); } catch {} execSync( `powershell -Command "Compress-Archive -Path '${outDir}' -DestinationPath '${zipPath}' -Force"`, { stdio: "ignore" } ); console.log(` ${path.basename(zipPath)} (${formatSize(fs.statSync(zipPath).size)})`); } catch { console.log(` ⚠ PowerShell 不可用,跳过 zip (文件在 ${path.relative(ROOT, outDir)}/)`); } } else { const tgzPath = path.join(DIST, `${baseName}.tar.gz`); try { try { fs.unlinkSync(tgzPath); } catch {} // 用相对路径避免 Windows 盘符问题 execSync( `tar -czf "${tgzPath}" -C "${DIST}" "${dirName}"`, { stdio: "ignore", cwd: DIST } ); console.log(` ${path.basename(tgzPath)} (${formatSize(fs.statSync(tgzPath).size)})`); } catch { console.log(` ⚠ tar 不可用,跳过归档 (文件在 ${path.relative(ROOT, outDir)}/)`); } } } // ── 汇总 ── console.log("\n======== 打包完成 ========"); console.log(`输出目录: ${path.relative(ROOT, DIST)}/`); const distFiles = fs.readdirSync(DIST); for (const f of distFiles) { const full = path.join(DIST, f); const stat = fs.statSync(full); if (stat.isDirectory()) { const contents = fs.readdirSync(full); const exe = contents.find(x => x.endsWith(".exe") || x.startsWith("jms-connection-service-")); console.log(` ${f}/ (${exe ? exe + "、" : ""}config.json、logs/、README.txt)`); } else { console.log(` ${f} (${formatSize(stat.size)})`); } } console.log("\n下一步: 进入 dist/ 对应目录,编辑 config.json 后启动程序验证。"); // ── helpers ── function formatSize(bytes) { if (bytes >= 1048576) return (bytes / 1048576).toFixed(1) + " MB"; if (bytes >= 1024) return (bytes / 1024).toFixed(1) + " KB"; return bytes + " B"; }