chenyc
2026-05-13 472fb5267c1b3fa8ab798ccd15bb1aac70128d41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"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";
}