pi-web-ui 0.48.3 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/index.js +1 -3
- package/package.json +5 -15
- package/electron/main.mjs +0 -427
package/dist/server/index.js
CHANGED
|
@@ -179,9 +179,7 @@ const here = dirname(fileURLToPath(import.meta.url)); // <pkg>/dist/server or <p
|
|
|
179
179
|
// Resolve the package root robustly: dev runs from <repo>/server (tsx), prod
|
|
180
180
|
// from <pkg>/dist/server — the ancestor that actually has package.json wins.
|
|
181
181
|
function resolvePkgRoot() {
|
|
182
|
-
//
|
|
183
|
-
//(process.resourcesPath)加载 web/dist 和 themes。通过这个 env var 告诉
|
|
184
|
-
// server 去哪里找 pkgRoot,避免 resolvePkgRoot 的候选路径找不到 package.json。
|
|
182
|
+
// 可选:显式指定 pkgRoot(如部署在自定义目录时),否则按候选路径探测。
|
|
185
183
|
if (process.env.PI_WEB_PKG_ROOT)
|
|
186
184
|
return process.env.PI_WEB_PKG_ROOT;
|
|
187
185
|
const candidates = [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"pi-web-ui": "bin/pi-web-ui.mjs"
|
|
13
13
|
},
|
|
14
|
-
"main": "
|
|
14
|
+
"main": "bin/pi-web-ui.mjs",
|
|
15
15
|
"files": [
|
|
16
16
|
"bin/",
|
|
17
17
|
"dist/",
|
|
@@ -59,20 +59,13 @@
|
|
|
59
59
|
"test:smoke": "node tests/run-smoke.mjs",
|
|
60
60
|
"check:protocol": "node scripts/check-protocol-sync.mjs",
|
|
61
61
|
"test:freeze": "node tests/freeze-test.mjs",
|
|
62
|
-
"start": "node dist/server/index.js"
|
|
63
|
-
"start:electron": "electron .",
|
|
64
|
-
"build:electron": "electron-builder build",
|
|
65
|
-
"build:electron:mac": "electron-builder build --mac",
|
|
66
|
-
"build:electron:win": "electron-builder build --win",
|
|
67
|
-
"build:electron:linux": "electron-builder build --linux",
|
|
68
|
-
"publish:electron": "electron-builder build --publish always"
|
|
62
|
+
"start": "node dist/server/index.js"
|
|
69
63
|
},
|
|
70
64
|
"dependencies": {
|
|
71
65
|
"@earendil-works/pi-coding-agent": "^0.84.4",
|
|
72
66
|
"@xterm/addon-fit": "^0.11.0",
|
|
73
67
|
"@xterm/xterm": "^6.0.0",
|
|
74
68
|
"compression": "^1.8.1",
|
|
75
|
-
"electron-updater": "^6.0.0",
|
|
76
69
|
"express": "^4.21.2",
|
|
77
70
|
"highlight.js": "^11.10.0",
|
|
78
71
|
"node-pty": "^1.1.0",
|
|
@@ -99,12 +92,9 @@
|
|
|
99
92
|
"tsx": "^4.19.2",
|
|
100
93
|
"typescript": "^5.7.2",
|
|
101
94
|
"vite": "^6.0.3",
|
|
102
|
-
"vitest": "^4.1.11"
|
|
103
|
-
"electron": "^41.0.0",
|
|
104
|
-
"electron-builder": "^25.0.0"
|
|
95
|
+
"vitest": "^4.1.11"
|
|
105
96
|
},
|
|
106
97
|
"allowScripts": {
|
|
107
|
-
"node-pty@1.1.0": true
|
|
108
|
-
"electron@41.10.7": true
|
|
98
|
+
"node-pty@1.1.0": true
|
|
109
99
|
}
|
|
110
100
|
}
|
package/electron/main.mjs
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pi-web-ui Electron 桌面版主进程。
|
|
3
|
-
*
|
|
4
|
-
* 架构:
|
|
5
|
-
* - 主进程 fork 一个隐藏子进程跑 server(ELECTRON_RUN_AS_NODE=1),
|
|
6
|
-
* server 使用 Electron 内置的 Node 运行时,node-pty 等原生模块
|
|
7
|
-
* 在 electron-builder 打包时自动 rebuild 为 Electron ABI。
|
|
8
|
-
* - BrowserWindow 加载 http://127.0.0.1:{PORT}(server 就绪后)。
|
|
9
|
-
* - 托盘:关闭窗口 → 最小化到托盘;Quit → 真正退出(杀 server 子进程)。
|
|
10
|
-
* - 自动更新:electron-updater + GitHub Releases。
|
|
11
|
-
*
|
|
12
|
-
* 开发模式(npm run dev:electron):
|
|
13
|
-
* 先构建 web + server(npm run build),然后 electron . 即可。
|
|
14
|
-
* 如有 Vite dev server (:5173),优先加载它获取 HMR。
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { app, BrowserWindow, Tray, Menu, nativeImage, dialog, Notification } from "electron";
|
|
18
|
-
import { fork } from "node:child_process";
|
|
19
|
-
import { createServer } from "node:net";
|
|
20
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
21
|
-
import { join, dirname, resolve } from "node:path";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
23
|
-
import { autoUpdater } from "electron-updater";
|
|
24
|
-
|
|
25
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
const ROOT = resolve(__dirname, "..");
|
|
27
|
-
const isDev = !app.isPackaged || !!process.env.DEV;
|
|
28
|
-
|
|
29
|
-
// ── 状态 ──
|
|
30
|
-
/** @type {BrowserWindow | null} */
|
|
31
|
-
let mainWindow = null;
|
|
32
|
-
/** @type {Tray | null} */
|
|
33
|
-
let tray = null;
|
|
34
|
-
/** @type {import("node:child_process").ChildProcess | null} */
|
|
35
|
-
let serverProcess = null;
|
|
36
|
-
let isQuitting = false;
|
|
37
|
-
let serverPort = 0;
|
|
38
|
-
|
|
39
|
-
// ── 路径 ──
|
|
40
|
-
|
|
41
|
-
/** 打包后 server 在 resources/dist/server/index.js */
|
|
42
|
-
function getServerPath() {
|
|
43
|
-
if (isDev) return join(ROOT, "dist", "server", "index.js");
|
|
44
|
-
return join(process.resourcesPath, "dist", "server", "index.js");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** 打包后 themes 在 resources/themes/ */
|
|
48
|
-
function getPkgRoot() {
|
|
49
|
-
if (isDev) return ROOT;
|
|
50
|
-
return process.resourcesPath;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── 端口 ──
|
|
54
|
-
|
|
55
|
-
/** 找一个随机空闲端口(防止端口冲突) */
|
|
56
|
-
async function findFreePort() {
|
|
57
|
-
return new Promise((resolve, reject) => {
|
|
58
|
-
const srv = createServer();
|
|
59
|
-
srv.listen(0, "127.0.0.1", () => {
|
|
60
|
-
const port = /** @type {import("node:net").AddressInfo} */ (srv.address()).port;
|
|
61
|
-
srv.close(() => resolve(port));
|
|
62
|
-
});
|
|
63
|
-
srv.on("error", reject);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ── 启动 Server 子进程 ──
|
|
68
|
-
|
|
69
|
-
async function startServer() {
|
|
70
|
-
serverPort = await findFreePort();
|
|
71
|
-
const serverPath = getServerPath();
|
|
72
|
-
|
|
73
|
-
if (!existsSync(serverPath)) {
|
|
74
|
-
dialog.showErrorBox(
|
|
75
|
-
"pi-web-ui 启动失败",
|
|
76
|
-
`找不到 server 入口:${serverPath}\n\n请先执行 npm run build,然后重试。`,
|
|
77
|
-
);
|
|
78
|
-
app.quit();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 确保 ~/.pi-web 目录存在(server 需要)
|
|
83
|
-
const dataDir = join(process.env.HOME || process.env.USERPROFILE || "~", ".pi-web");
|
|
84
|
-
mkdirSync(dataDir, { recursive: true });
|
|
85
|
-
|
|
86
|
-
serverProcess = fork(serverPath, [], {
|
|
87
|
-
env: {
|
|
88
|
-
...process.env,
|
|
89
|
-
PORT: String(serverPort),
|
|
90
|
-
PI_WEB_HOST: "127.0.0.1",
|
|
91
|
-
PI_WEB_NO_BROWSER: "1", // 不要自动打开浏览器
|
|
92
|
-
PI_WEB_PKG_ROOT: getPkgRoot(), // 告诉 server 去哪找 web/dist / themes
|
|
93
|
-
ELECTRON_RUN_AS_NODE: "1", // 以 Node.js 模式运行(非 Electron)
|
|
94
|
-
},
|
|
95
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
96
|
-
serialization: "json",
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// 收集 stdout/stderr 用于调试
|
|
100
|
-
let serverOut = "";
|
|
101
|
-
serverProcess.stdout?.on("data", (chunk) => {
|
|
102
|
-
const text = chunk.toString();
|
|
103
|
-
serverOut += text;
|
|
104
|
-
process.stdout.write(`[server] ${text}`);
|
|
105
|
-
});
|
|
106
|
-
serverProcess.stderr?.on("data", (chunk) => {
|
|
107
|
-
const text = chunk.toString();
|
|
108
|
-
serverOut += text;
|
|
109
|
-
process.stderr.write(`[server:err] ${text}`);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// 等待 server 就绪:监听 stdout 中的 "⚡ pi-web-ui" 标记
|
|
113
|
-
await new Promise((resolve, reject) => {
|
|
114
|
-
const timeout = setTimeout(() => {
|
|
115
|
-
reject(
|
|
116
|
-
new Error(
|
|
117
|
-
`Server 启动超时(30s)。最后输出:\n${serverOut.slice(-500)}`,
|
|
118
|
-
),
|
|
119
|
-
);
|
|
120
|
-
}, 30_000);
|
|
121
|
-
|
|
122
|
-
const checkOutput = (chunk) => {
|
|
123
|
-
const text = chunk.toString();
|
|
124
|
-
if (text.includes("⚡ pi-web-ui") || text.includes("http://localhost")) {
|
|
125
|
-
clearTimeout(timeout);
|
|
126
|
-
resolve();
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
serverProcess.stdout?.on("data", checkOutput);
|
|
131
|
-
serverProcess.on("error", (err) => {
|
|
132
|
-
clearTimeout(timeout);
|
|
133
|
-
reject(err);
|
|
134
|
-
});
|
|
135
|
-
serverProcess.on("exit", (code) => {
|
|
136
|
-
clearTimeout(timeout);
|
|
137
|
-
if (code !== 0) {
|
|
138
|
-
reject(new Error(`Server 退出了 (exit code=${code})`));
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
console.log(`[electron] server 就绪,端口 ${serverPort}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ── 窗口 ──
|
|
147
|
-
|
|
148
|
-
function createWindow() {
|
|
149
|
-
mainWindow = new BrowserWindow({
|
|
150
|
-
width: 1200,
|
|
151
|
-
height: 800,
|
|
152
|
-
minWidth: 800,
|
|
153
|
-
minHeight: 600,
|
|
154
|
-
title: "pi-web-ui",
|
|
155
|
-
show: false,
|
|
156
|
-
webPreferences: {
|
|
157
|
-
preload: join(__dirname, "preload.mjs"),
|
|
158
|
-
nodeIntegration: false,
|
|
159
|
-
contextIsolation: true,
|
|
160
|
-
},
|
|
161
|
-
icon: join(__dirname, "icon.png"),
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// 加载 localhost 上的 server
|
|
165
|
-
const url = `http://127.0.0.1:${serverPort}`;
|
|
166
|
-
mainWindow.loadURL(url);
|
|
167
|
-
|
|
168
|
-
mainWindow.once("ready-to-show", () => {
|
|
169
|
-
mainWindow.show();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// 关闭窗口 → 隐藏到托盘(不退出)
|
|
173
|
-
mainWindow.on("close", (event) => {
|
|
174
|
-
if (!isQuitting) {
|
|
175
|
-
event.preventDefault();
|
|
176
|
-
mainWindow.hide();
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
mainWindow.on("closed", () => {
|
|
182
|
-
mainWindow = null;
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// 开发模式打开 DevTools
|
|
186
|
-
if (isDev) {
|
|
187
|
-
mainWindow.webContents.openDevTools({ mode: "detach" });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── 托盘 ──
|
|
192
|
-
|
|
193
|
-
function createTray() {
|
|
194
|
-
// 用 16x16 的图标,从 nativeImage 创建
|
|
195
|
-
// 如果没有图标,用 nativeImage.createEmpty()
|
|
196
|
-
const icon = nativeImage.createEmpty();
|
|
197
|
-
tray = new Tray(icon);
|
|
198
|
-
tray.setToolTip("pi-web-ui");
|
|
199
|
-
|
|
200
|
-
const contextMenu = Menu.buildFromTemplate([
|
|
201
|
-
{
|
|
202
|
-
label: "显示窗口",
|
|
203
|
-
click: () => {
|
|
204
|
-
if (mainWindow) {
|
|
205
|
-
mainWindow.show();
|
|
206
|
-
mainWindow.focus();
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
{ type: "separator" },
|
|
211
|
-
{
|
|
212
|
-
label: "关于 pi-web-ui",
|
|
213
|
-
click: () => {
|
|
214
|
-
dialog.showMessageBox({
|
|
215
|
-
type: "info",
|
|
216
|
-
title: "关于 pi-web-ui",
|
|
217
|
-
message: `pi-web-ui v${app.getVersion()}`,
|
|
218
|
-
detail: "Web chat interface for the pi coding agent.",
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
{ type: "separator" },
|
|
223
|
-
{
|
|
224
|
-
label: "退出",
|
|
225
|
-
click: () => {
|
|
226
|
-
isQuitting = true;
|
|
227
|
-
app.quit();
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
tray.setContextMenu(contextMenu);
|
|
233
|
-
|
|
234
|
-
// 点击托盘图标 → 显示/隐藏窗口
|
|
235
|
-
tray.on("click", () => {
|
|
236
|
-
if (mainWindow) {
|
|
237
|
-
if (mainWindow.isVisible()) {
|
|
238
|
-
mainWindow.hide();
|
|
239
|
-
} else {
|
|
240
|
-
mainWindow.show();
|
|
241
|
-
mainWindow.focus();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ── 菜单 ──
|
|
248
|
-
|
|
249
|
-
function createAppMenu() {
|
|
250
|
-
const template = [
|
|
251
|
-
{
|
|
252
|
-
label: "pi-web-ui",
|
|
253
|
-
submenu: [
|
|
254
|
-
{
|
|
255
|
-
label: "关于 pi-web-ui",
|
|
256
|
-
click: () => {
|
|
257
|
-
dialog.showMessageBox({
|
|
258
|
-
type: "info",
|
|
259
|
-
title: "关于 pi-web-ui",
|
|
260
|
-
message: `pi-web-ui v${app.getVersion()}`,
|
|
261
|
-
detail: "Web chat interface for the pi coding agent.",
|
|
262
|
-
});
|
|
263
|
-
},
|
|
264
|
-
},
|
|
265
|
-
{ type: "separator" },
|
|
266
|
-
{ role: "hide" },
|
|
267
|
-
{ role: "hideOthers" },
|
|
268
|
-
{ role: "unhide" },
|
|
269
|
-
{ type: "separator" },
|
|
270
|
-
{
|
|
271
|
-
label: "退出",
|
|
272
|
-
accelerator: "CmdOrCtrl+Q",
|
|
273
|
-
click: () => {
|
|
274
|
-
isQuitting = true;
|
|
275
|
-
app.quit();
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
],
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
label: "编辑",
|
|
282
|
-
submenu: [
|
|
283
|
-
{ role: "undo" },
|
|
284
|
-
{ role: "redo" },
|
|
285
|
-
{ type: "separator" },
|
|
286
|
-
{ role: "cut" },
|
|
287
|
-
{ role: "copy" },
|
|
288
|
-
{ role: "paste" },
|
|
289
|
-
{ role: "selectAll" },
|
|
290
|
-
],
|
|
291
|
-
},
|
|
292
|
-
{
|
|
293
|
-
label: "视图",
|
|
294
|
-
submenu: [
|
|
295
|
-
{ role: "reload" },
|
|
296
|
-
{ role: "forceReload" },
|
|
297
|
-
{ role: "toggleDevTools" },
|
|
298
|
-
{ type: "separator" },
|
|
299
|
-
{ role: "resetZoom" },
|
|
300
|
-
{ role: "zoomIn" },
|
|
301
|
-
{ role: "zoomOut" },
|
|
302
|
-
{ type: "separator" },
|
|
303
|
-
{ role: "togglefullscreen" },
|
|
304
|
-
],
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
label: "窗口",
|
|
308
|
-
submenu: [
|
|
309
|
-
{ role: "minimize" },
|
|
310
|
-
{ role: "close" },
|
|
311
|
-
],
|
|
312
|
-
},
|
|
313
|
-
];
|
|
314
|
-
|
|
315
|
-
// macOS 需要第一个菜单项是应用名
|
|
316
|
-
const menu = Menu.buildFromTemplate(template);
|
|
317
|
-
Menu.setApplicationMenu(menu);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// ── 自动更新 ──
|
|
321
|
-
|
|
322
|
-
function setupAutoUpdater() {
|
|
323
|
-
if (isDev) return; // 开发模式不检查更新
|
|
324
|
-
|
|
325
|
-
autoUpdater.autoDownload = false;
|
|
326
|
-
autoUpdater.autoInstallOnAppQuit = true;
|
|
327
|
-
|
|
328
|
-
// 检查更新(启动后延迟 5s)
|
|
329
|
-
setTimeout(() => {
|
|
330
|
-
autoUpdater.checkForUpdates().catch(() => {
|
|
331
|
-
// 静默失败(无网络 / 超时等)
|
|
332
|
-
});
|
|
333
|
-
}, 5000);
|
|
334
|
-
|
|
335
|
-
autoUpdater.on("update-available", (info) => {
|
|
336
|
-
const notification = new Notification({
|
|
337
|
-
title: "pi-web-ui 更新可用",
|
|
338
|
-
body: `版本 ${info.version} 可下载(当前 ${app.getVersion()})`,
|
|
339
|
-
});
|
|
340
|
-
notification.on("click", () => {
|
|
341
|
-
autoUpdater.downloadUpdate();
|
|
342
|
-
});
|
|
343
|
-
notification.show();
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
autoUpdater.on("update-downloaded", () => {
|
|
347
|
-
const result = dialog.showMessageBoxSync({
|
|
348
|
-
type: "info",
|
|
349
|
-
title: "更新已下载",
|
|
350
|
-
message: "新版本已下载完成,是否立即重启以安装更新?",
|
|
351
|
-
buttons: ["立即重启", "稍后"],
|
|
352
|
-
});
|
|
353
|
-
if (result === 0) {
|
|
354
|
-
autoUpdater.quitAndInstall();
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ── 应用生命周期 ──
|
|
360
|
-
|
|
361
|
-
app.setAppUserModelId("com.xingshuyin.pi-web-ui");
|
|
362
|
-
|
|
363
|
-
// 确保只有一个实例
|
|
364
|
-
const gotLock = app.requestSingleInstanceLock();
|
|
365
|
-
if (!gotLock) {
|
|
366
|
-
app.quit();
|
|
367
|
-
} else {
|
|
368
|
-
app.on("second-instance", () => {
|
|
369
|
-
if (mainWindow) {
|
|
370
|
-
if (mainWindow.isMinimized()) mainWindow.restore();
|
|
371
|
-
mainWindow.show();
|
|
372
|
-
mainWindow.focus();
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
app.whenReady().then(async () => {
|
|
378
|
-
try {
|
|
379
|
-
await startServer();
|
|
380
|
-
} catch (err) {
|
|
381
|
-
dialog.showErrorBox("pi-web-ui 启动失败", err.message);
|
|
382
|
-
app.quit();
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
createAppMenu();
|
|
387
|
-
createWindow();
|
|
388
|
-
createTray();
|
|
389
|
-
setupAutoUpdater();
|
|
390
|
-
|
|
391
|
-
app.on("activate", () => {
|
|
392
|
-
if (!mainWindow) createWindow();
|
|
393
|
-
if (mainWindow) {
|
|
394
|
-
mainWindow.show();
|
|
395
|
-
mainWindow.focus();
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
app.on("window-all-closed", () => {
|
|
401
|
-
// macOS 不退出(dock 图标还在)
|
|
402
|
-
// 其他平台退出
|
|
403
|
-
if (process.platform !== "darwin") {
|
|
404
|
-
isQuitting = true;
|
|
405
|
-
app.quit();
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
app.on("before-quit", () => {
|
|
410
|
-
isQuitting = true;
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
app.on("will-quit", () => {
|
|
414
|
-
// 杀 server 子进程
|
|
415
|
-
if (serverProcess) {
|
|
416
|
-
try {
|
|
417
|
-
serverProcess.kill("SIGTERM");
|
|
418
|
-
} catch {
|
|
419
|
-
// 可能已经退出
|
|
420
|
-
}
|
|
421
|
-
serverProcess = null;
|
|
422
|
-
}
|
|
423
|
-
if (tray) {
|
|
424
|
-
tray.destroy();
|
|
425
|
-
tray = null;
|
|
426
|
-
}
|
|
427
|
-
});
|