@hangox/mg-cli 1.0.0 → 1.0.2

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.
Files changed (42) hide show
  1. package/dist/cli.js +1580 -0
  2. package/dist/cli.js.map +1 -0
  3. package/dist/daemon-runner.js +794 -0
  4. package/dist/daemon-runner.js.map +1 -0
  5. package/dist/index-DNrszrq9.d.ts +568 -0
  6. package/dist/index.d.ts +129 -0
  7. package/dist/index.js +950 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/server.d.ts +2 -0
  10. package/dist/server.js +689 -0
  11. package/dist/server.js.map +1 -0
  12. package/package.json +5 -1
  13. package/.eslintrc.cjs +0 -26
  14. package/CLAUDE.md +0 -43
  15. package/src/cli/client.ts +0 -266
  16. package/src/cli/commands/execute-code.ts +0 -59
  17. package/src/cli/commands/export-image.ts +0 -193
  18. package/src/cli/commands/get-all-nodes.ts +0 -81
  19. package/src/cli/commands/get-all-pages.ts +0 -118
  20. package/src/cli/commands/get-node-by-id.ts +0 -83
  21. package/src/cli/commands/get-node-by-link.ts +0 -105
  22. package/src/cli/commands/server.ts +0 -130
  23. package/src/cli/index.ts +0 -33
  24. package/src/index.ts +0 -9
  25. package/src/server/connection-manager.ts +0 -211
  26. package/src/server/daemon-runner.ts +0 -22
  27. package/src/server/daemon.ts +0 -211
  28. package/src/server/index.ts +0 -8
  29. package/src/server/logger.ts +0 -117
  30. package/src/server/request-handler.ts +0 -192
  31. package/src/server/websocket-server.ts +0 -297
  32. package/src/shared/constants.ts +0 -90
  33. package/src/shared/errors.ts +0 -131
  34. package/src/shared/index.ts +0 -8
  35. package/src/shared/types.ts +0 -227
  36. package/src/shared/utils.ts +0 -352
  37. package/tests/unit/shared/constants.test.ts +0 -66
  38. package/tests/unit/shared/errors.test.ts +0 -82
  39. package/tests/unit/shared/utils.test.ts +0 -208
  40. package/tsconfig.json +0 -22
  41. package/tsup.config.ts +0 -33
  42. package/vitest.config.ts +0 -22
@@ -0,0 +1,794 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/server/daemon.ts
4
+ import { spawn } from "child_process";
5
+ import { fileURLToPath } from "url";
6
+ import { dirname as dirname3, join as join2 } from "path";
7
+
8
+ // src/shared/utils.ts
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
10
+ import { dirname, resolve, isAbsolute } from "path";
11
+
12
+ // src/shared/constants.ts
13
+ import { homedir } from "os";
14
+ import { join } from "path";
15
+ var DEFAULT_PORT = 9527;
16
+ var PORT_RANGE_START = 9527;
17
+ var PORT_RANGE_END = 9536;
18
+ var CONFIG_DIR = join(homedir(), ".mg-plugin");
19
+ var SERVER_INFO_FILE = join(CONFIG_DIR, "server.json");
20
+ var LOG_DIR = join(CONFIG_DIR, "logs");
21
+ var SERVER_LOG_FILE = join(LOG_DIR, "server.log");
22
+ var HEARTBEAT_INTERVAL = 3e4;
23
+ var HEARTBEAT_TIMEOUT = 9e4;
24
+ var REQUEST_TIMEOUT = 3e4;
25
+
26
+ // src/shared/utils.ts
27
+ function ensureDir(dir) {
28
+ if (!existsSync(dir)) {
29
+ mkdirSync(dir, { recursive: true });
30
+ }
31
+ }
32
+ function ensureConfigDir() {
33
+ ensureDir(CONFIG_DIR);
34
+ ensureDir(LOG_DIR);
35
+ }
36
+ function readServerInfo() {
37
+ try {
38
+ if (!existsSync(SERVER_INFO_FILE)) {
39
+ return null;
40
+ }
41
+ const content = readFileSync(SERVER_INFO_FILE, "utf-8");
42
+ return JSON.parse(content);
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function writeServerInfo(info) {
48
+ ensureConfigDir();
49
+ writeFileSync(SERVER_INFO_FILE, JSON.stringify(info, null, 2), "utf-8");
50
+ }
51
+ function deleteServerInfo() {
52
+ try {
53
+ if (existsSync(SERVER_INFO_FILE)) {
54
+ unlinkSync(SERVER_INFO_FILE);
55
+ }
56
+ } catch {
57
+ }
58
+ }
59
+ function isProcessRunning(pid) {
60
+ try {
61
+ process.kill(pid, 0);
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+ function generateId() {
68
+ return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
69
+ }
70
+ function getCurrentISOTime() {
71
+ return (/* @__PURE__ */ new Date()).toISOString();
72
+ }
73
+ function formatLogTime(date = /* @__PURE__ */ new Date()) {
74
+ const year = date.getFullYear();
75
+ const month = String(date.getMonth() + 1).padStart(2, "0");
76
+ const day = String(date.getDate()).padStart(2, "0");
77
+ const hours = String(date.getHours()).padStart(2, "0");
78
+ const minutes = String(date.getMinutes()).padStart(2, "0");
79
+ const seconds = String(date.getSeconds()).padStart(2, "0");
80
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
81
+ }
82
+
83
+ // src/shared/errors.ts
84
+ var ErrorNames = {
85
+ ["E001" /* CONNECTION_FAILED */]: "CONNECTION_FAILED",
86
+ ["E002" /* CONNECTION_TIMEOUT */]: "CONNECTION_TIMEOUT",
87
+ ["E003" /* NO_PAGE_CONNECTED */]: "NO_PAGE_CONNECTED",
88
+ ["E004" /* PAGE_NOT_FOUND */]: "PAGE_NOT_FOUND",
89
+ ["E005" /* NODE_NOT_FOUND */]: "NODE_NOT_FOUND",
90
+ ["E006" /* NO_SELECTION */]: "NO_SELECTION",
91
+ ["E007" /* MG_UNAVAILABLE */]: "MG_UNAVAILABLE",
92
+ ["E008" /* EXPORT_FAILED */]: "EXPORT_FAILED",
93
+ ["E009" /* FILE_WRITE_FAILED */]: "FILE_WRITE_FAILED",
94
+ ["E010" /* INVALID_LINK */]: "INVALID_LINK",
95
+ ["E011" /* INVALID_PARAMS */]: "INVALID_PARAMS",
96
+ ["E012" /* REQUEST_TIMEOUT */]: "REQUEST_TIMEOUT",
97
+ ["E013" /* PORT_EXHAUSTED */]: "PORT_EXHAUSTED",
98
+ ["E014" /* SERVER_DISCOVERY_FAILED */]: "SERVER_DISCOVERY_FAILED",
99
+ ["E015" /* SERVER_START_FAILED */]: "SERVER_START_FAILED",
100
+ ["E016" /* SERVER_ALREADY_RUNNING */]: "SERVER_ALREADY_RUNNING",
101
+ ["E017" /* CONNECTION_LOST */]: "CONNECTION_LOST",
102
+ ["E099" /* UNKNOWN_ERROR */]: "UNKNOWN_ERROR"
103
+ };
104
+ var ErrorMessages = {
105
+ ["E001" /* CONNECTION_FAILED */]: "\u65E0\u6CD5\u8FDE\u63A5\u5230 MG Server",
106
+ ["E002" /* CONNECTION_TIMEOUT */]: "\u8FDE\u63A5\u8D85\u65F6",
107
+ ["E003" /* NO_PAGE_CONNECTED */]: "\u6CA1\u6709 MasterGo \u9875\u9762\u8FDE\u63A5\u5230 Server",
108
+ ["E004" /* PAGE_NOT_FOUND */]: "\u672A\u627E\u5230\u5339\u914D\u7684\u9875\u9762",
109
+ ["E005" /* NODE_NOT_FOUND */]: "\u8282\u70B9\u4E0D\u5B58\u5728",
110
+ ["E006" /* NO_SELECTION */]: "\u6CA1\u6709\u9009\u4E2D\u4EFB\u4F55\u8282\u70B9",
111
+ ["E007" /* MG_UNAVAILABLE */]: "mg \u5BF9\u8C61\u4E0D\u53EF\u7528",
112
+ ["E008" /* EXPORT_FAILED */]: "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25",
113
+ ["E009" /* FILE_WRITE_FAILED */]: "\u6587\u4EF6\u5199\u5165\u5931\u8D25",
114
+ ["E010" /* INVALID_LINK */]: "\u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F",
115
+ ["E011" /* INVALID_PARAMS */]: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
116
+ ["E012" /* REQUEST_TIMEOUT */]: "\u8BF7\u6C42\u8D85\u65F6",
117
+ ["E013" /* PORT_EXHAUSTED */]: "\u6240\u6709\u5907\u9009\u7AEF\u53E3\u5747\u88AB\u5360\u7528",
118
+ ["E014" /* SERVER_DISCOVERY_FAILED */]: "\u65E0\u6CD5\u53D1\u73B0 Server (\u7AEF\u53E3\u626B\u63CF\u5931\u8D25)",
119
+ ["E015" /* SERVER_START_FAILED */]: "\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25",
120
+ ["E016" /* SERVER_ALREADY_RUNNING */]: "Server \u5DF2\u5728\u8FD0\u884C\u4E2D",
121
+ ["E017" /* CONNECTION_LOST */]: "\u8FDE\u63A5\u65AD\u5F00",
122
+ ["E099" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF"
123
+ };
124
+ var MGError = class extends Error {
125
+ /** 错误码 */
126
+ code;
127
+ /** 错误名称 */
128
+ errorName;
129
+ /** 额外详情 */
130
+ details;
131
+ constructor(code, message, details) {
132
+ super(message || ErrorMessages[code]);
133
+ this.name = "MGError";
134
+ this.code = code;
135
+ this.errorName = ErrorNames[code];
136
+ this.details = details;
137
+ }
138
+ /** 转换为 JSON 格式 */
139
+ toJSON() {
140
+ return {
141
+ code: this.code,
142
+ name: this.errorName,
143
+ message: this.message,
144
+ details: this.details
145
+ };
146
+ }
147
+ /** 格式化输出 */
148
+ toString() {
149
+ return `\u9519\u8BEF [${this.code}]: ${this.message}`;
150
+ }
151
+ };
152
+
153
+ // src/server/websocket-server.ts
154
+ import { WebSocketServer } from "ws";
155
+
156
+ // src/server/logger.ts
157
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
158
+ import { dirname as dirname2 } from "path";
159
+ var levelPriority = {
160
+ ["DEBUG" /* DEBUG */]: 0,
161
+ ["INFO" /* INFO */]: 1,
162
+ ["WARN" /* WARN */]: 2,
163
+ ["ERROR" /* ERROR */]: 3
164
+ };
165
+ var Logger = class {
166
+ options;
167
+ constructor(options = {}) {
168
+ this.options = {
169
+ console: options.console ?? true,
170
+ file: options.file ?? false,
171
+ filePath: options.filePath ?? SERVER_LOG_FILE,
172
+ minLevel: options.minLevel ?? "INFO" /* INFO */
173
+ };
174
+ if (this.options.file) {
175
+ const dir = dirname2(this.options.filePath);
176
+ if (!existsSync2(dir)) {
177
+ mkdirSync2(dir, { recursive: true });
178
+ }
179
+ }
180
+ }
181
+ /**
182
+ * 记录日志
183
+ */
184
+ log(level, message, ...args2) {
185
+ if (levelPriority[level] < levelPriority[this.options.minLevel]) {
186
+ return;
187
+ }
188
+ const timestamp = formatLogTime();
189
+ const formattedMessage = `[${timestamp}] [${level}] ${message}`;
190
+ if (this.options.console) {
191
+ const consoleMethod = level === "ERROR" /* ERROR */ ? console.error : console.log;
192
+ if (args2.length > 0) {
193
+ consoleMethod(formattedMessage, ...args2);
194
+ } else {
195
+ consoleMethod(formattedMessage);
196
+ }
197
+ }
198
+ if (this.options.file) {
199
+ try {
200
+ const fileMessage = args2.length > 0 ? `${formattedMessage} ${JSON.stringify(args2)}
201
+ ` : `${formattedMessage}
202
+ `;
203
+ appendFileSync(this.options.filePath, fileMessage);
204
+ } catch (error) {
205
+ if (this.options.console) {
206
+ console.error("\u65E5\u5FD7\u6587\u4EF6\u5199\u5165\u5931\u8D25:", error);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ debug(message, ...args2) {
212
+ this.log("DEBUG" /* DEBUG */, message, ...args2);
213
+ }
214
+ info(message, ...args2) {
215
+ this.log("INFO" /* INFO */, message, ...args2);
216
+ }
217
+ warn(message, ...args2) {
218
+ this.log("WARN" /* WARN */, message, ...args2);
219
+ }
220
+ error(message, ...args2) {
221
+ this.log("ERROR" /* ERROR */, message, ...args2);
222
+ }
223
+ };
224
+ function createLogger(options) {
225
+ return new Logger(options);
226
+ }
227
+
228
+ // src/server/connection-manager.ts
229
+ var ConnectionManager = class {
230
+ logger;
231
+ /** Provider 连接(按页面 URL 索引) */
232
+ providers = /* @__PURE__ */ new Map();
233
+ /** Consumer 连接 */
234
+ consumers = /* @__PURE__ */ new Map();
235
+ /** 所有连接(按 ID 索引) */
236
+ allConnections = /* @__PURE__ */ new Map();
237
+ /** 心跳检查定时器 */
238
+ heartbeatTimer = null;
239
+ constructor(logger) {
240
+ this.logger = logger;
241
+ }
242
+ /**
243
+ * 启动心跳检查
244
+ */
245
+ startHeartbeatCheck(interval = 3e4) {
246
+ if (this.heartbeatTimer) {
247
+ clearInterval(this.heartbeatTimer);
248
+ }
249
+ this.heartbeatTimer = setInterval(() => {
250
+ this.checkHeartbeats();
251
+ }, interval);
252
+ }
253
+ /**
254
+ * 停止心跳检查
255
+ */
256
+ stopHeartbeatCheck() {
257
+ if (this.heartbeatTimer) {
258
+ clearInterval(this.heartbeatTimer);
259
+ this.heartbeatTimer = null;
260
+ }
261
+ }
262
+ /**
263
+ * 检查所有连接的心跳
264
+ */
265
+ checkHeartbeats() {
266
+ const now = Date.now();
267
+ for (const [id, ws] of this.allConnections) {
268
+ const lastActive = ws.connectionInfo.lastActiveAt.getTime();
269
+ const elapsed = now - lastActive;
270
+ if (elapsed > HEARTBEAT_TIMEOUT) {
271
+ this.logger.warn(`\u8FDE\u63A5 ${id} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5`);
272
+ this.removeConnection(ws);
273
+ ws.terminate();
274
+ }
275
+ }
276
+ }
277
+ /**
278
+ * 添加连接
279
+ */
280
+ addConnection(ws, type, pageUrl, pageId) {
281
+ const connectionId = generateId();
282
+ const now = /* @__PURE__ */ new Date();
283
+ const connectionInfo = {
284
+ id: connectionId,
285
+ type,
286
+ pageUrl,
287
+ pageId,
288
+ connectedAt: now,
289
+ lastActiveAt: now
290
+ };
291
+ const managedWs = ws;
292
+ managedWs.connectionId = connectionId;
293
+ managedWs.connectionInfo = connectionInfo;
294
+ managedWs.isAlive = true;
295
+ this.allConnections.set(connectionId, managedWs);
296
+ if (type === "provider" /* PROVIDER */ && pageUrl) {
297
+ const existing = this.providers.get(pageUrl);
298
+ if (existing) {
299
+ this.logger.info(`\u9875\u9762 ${pageUrl} \u5DF2\u6709\u8FDE\u63A5\uFF0C\u66FF\u6362\u4E3A\u65B0\u8FDE\u63A5`);
300
+ this.removeConnection(existing);
301
+ existing.close();
302
+ }
303
+ this.providers.set(pageUrl, managedWs);
304
+ this.logger.info(`Provider \u8FDE\u63A5: ${pageUrl}`);
305
+ } else if (type === "consumer" /* CONSUMER */) {
306
+ this.consumers.set(connectionId, managedWs);
307
+ this.logger.info(`Consumer \u8FDE\u63A5: ${connectionId}`);
308
+ }
309
+ return managedWs;
310
+ }
311
+ /**
312
+ * 移除连接
313
+ */
314
+ removeConnection(ws) {
315
+ const { connectionId, connectionInfo } = ws;
316
+ this.allConnections.delete(connectionId);
317
+ if (connectionInfo.type === "provider" /* PROVIDER */ && connectionInfo.pageUrl) {
318
+ this.providers.delete(connectionInfo.pageUrl);
319
+ this.logger.info(`Provider \u65AD\u5F00: ${connectionInfo.pageUrl}`);
320
+ } else if (connectionInfo.type === "consumer" /* CONSUMER */) {
321
+ this.consumers.delete(connectionId);
322
+ this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
323
+ }
324
+ }
325
+ /**
326
+ * 更新连接活跃时间
327
+ */
328
+ updateLastActive(ws) {
329
+ ws.connectionInfo.lastActiveAt = /* @__PURE__ */ new Date();
330
+ ws.isAlive = true;
331
+ }
332
+ /**
333
+ * 根据页面 URL 查找 Provider
334
+ */
335
+ findProviderByPageUrl(pageUrl) {
336
+ return this.providers.get(pageUrl);
337
+ }
338
+ /**
339
+ * 获取第一个可用的 Provider
340
+ */
341
+ getFirstProvider() {
342
+ const iterator = this.providers.values();
343
+ const first = iterator.next();
344
+ return first.value;
345
+ }
346
+ /**
347
+ * 获取所有 Provider 信息
348
+ */
349
+ getAllProviders() {
350
+ return Array.from(this.providers.values()).map((ws) => ws.connectionInfo);
351
+ }
352
+ /**
353
+ * 获取连接统计
354
+ */
355
+ getStats() {
356
+ return {
357
+ providers: this.providers.size,
358
+ consumers: this.consumers.size,
359
+ total: this.allConnections.size
360
+ };
361
+ }
362
+ /**
363
+ * 获取所有已连接的页面 URL
364
+ */
365
+ getConnectedPageUrls() {
366
+ return Array.from(this.providers.keys());
367
+ }
368
+ /**
369
+ * 关闭所有连接
370
+ */
371
+ closeAll() {
372
+ this.stopHeartbeatCheck();
373
+ for (const ws of this.allConnections.values()) {
374
+ ws.close();
375
+ }
376
+ this.providers.clear();
377
+ this.consumers.clear();
378
+ this.allConnections.clear();
379
+ }
380
+ };
381
+
382
+ // src/server/request-handler.ts
383
+ var RequestHandler = class {
384
+ logger;
385
+ connectionManager;
386
+ /** 待处理的请求 */
387
+ pendingRequests = /* @__PURE__ */ new Map();
388
+ constructor(connectionManager, logger) {
389
+ this.connectionManager = connectionManager;
390
+ this.logger = logger;
391
+ }
392
+ /**
393
+ * 处理 Consumer 请求
394
+ */
395
+ async handleRequest(consumer, message) {
396
+ const requestId = message.id || generateId();
397
+ const { type, pageUrl, params } = message;
398
+ this.logger.info(`\u6536\u5230\u8BF7\u6C42: ${type} (${requestId})`, { pageUrl });
399
+ let provider;
400
+ if (pageUrl) {
401
+ provider = this.connectionManager.findProviderByPageUrl(pageUrl);
402
+ if (!provider) {
403
+ this.sendError(consumer, requestId, "E004" /* PAGE_NOT_FOUND */, `\u672A\u627E\u5230\u9875\u9762: ${pageUrl}`);
404
+ return;
405
+ }
406
+ } else {
407
+ provider = this.connectionManager.getFirstProvider();
408
+ if (!provider) {
409
+ this.sendError(consumer, requestId, "E003" /* NO_PAGE_CONNECTED */, "\u6CA1\u6709\u9875\u9762\u8FDE\u63A5\u5230 Server");
410
+ return;
411
+ }
412
+ }
413
+ const timer = setTimeout(() => {
414
+ this.handleTimeout(requestId);
415
+ }, REQUEST_TIMEOUT);
416
+ this.pendingRequests.set(requestId, {
417
+ id: requestId,
418
+ consumer,
419
+ timer,
420
+ timestamp: Date.now()
421
+ });
422
+ const forwardMessage = {
423
+ id: requestId,
424
+ type,
425
+ pageUrl: pageUrl || provider.connectionInfo.pageUrl,
426
+ params,
427
+ timestamp: Date.now()
428
+ };
429
+ try {
430
+ provider.send(JSON.stringify(forwardMessage));
431
+ this.logger.info(`\u8BF7\u6C42\u8F6C\u53D1: ${type} -> ${provider.connectionInfo.pageUrl}`);
432
+ } catch {
433
+ this.cleanupRequest(requestId);
434
+ this.sendError(consumer, requestId, "E001" /* CONNECTION_FAILED */, "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25");
435
+ }
436
+ }
437
+ /**
438
+ * 处理 Provider 响应
439
+ */
440
+ handleResponse(response) {
441
+ const { id } = response;
442
+ const pending = this.pendingRequests.get(id);
443
+ if (!pending) {
444
+ this.logger.warn(`\u6536\u5230\u672A\u77E5\u8BF7\u6C42\u7684\u54CD\u5E94: ${id}`);
445
+ return;
446
+ }
447
+ this.cleanupRequest(id);
448
+ try {
449
+ pending.consumer.send(JSON.stringify(response));
450
+ this.logger.info(
451
+ `\u54CD\u5E94\u8FD4\u56DE: ${id} (${response.success ? "\u6210\u529F" : "\u5931\u8D25"})`
452
+ );
453
+ } catch (error) {
454
+ this.logger.error(`\u54CD\u5E94\u8F6C\u53D1\u5931\u8D25: ${id}`, error);
455
+ }
456
+ }
457
+ /**
458
+ * 处理请求超时
459
+ */
460
+ handleTimeout(requestId) {
461
+ const pending = this.pendingRequests.get(requestId);
462
+ if (!pending) return;
463
+ this.logger.warn(`\u8BF7\u6C42\u8D85\u65F6: ${requestId}`);
464
+ this.cleanupRequest(requestId);
465
+ this.sendError(pending.consumer, requestId, "E012" /* REQUEST_TIMEOUT */, "\u8BF7\u6C42\u8D85\u65F6");
466
+ }
467
+ /**
468
+ * 清理请求
469
+ */
470
+ cleanupRequest(requestId) {
471
+ const pending = this.pendingRequests.get(requestId);
472
+ if (pending) {
473
+ clearTimeout(pending.timer);
474
+ this.pendingRequests.delete(requestId);
475
+ }
476
+ }
477
+ /**
478
+ * 发送错误响应
479
+ */
480
+ sendError(consumer, requestId, code, message) {
481
+ const error = new MGError(code, message);
482
+ const response = {
483
+ id: requestId,
484
+ type: "error" /* ERROR */,
485
+ success: false,
486
+ data: null,
487
+ error: error.toJSON()
488
+ };
489
+ try {
490
+ consumer.send(JSON.stringify(response));
491
+ } catch (err) {
492
+ this.logger.error(`\u53D1\u9001\u9519\u8BEF\u54CD\u5E94\u5931\u8D25: ${requestId}`, err);
493
+ }
494
+ }
495
+ /**
496
+ * 清理特定连接的所有待处理请求
497
+ */
498
+ cleanupConnectionRequests(connectionId) {
499
+ for (const [requestId, pending] of this.pendingRequests) {
500
+ if (pending.consumer.connectionId === connectionId) {
501
+ this.cleanupRequest(requestId);
502
+ }
503
+ }
504
+ }
505
+ /**
506
+ * 清理所有待处理请求
507
+ */
508
+ cleanupAll() {
509
+ for (const [requestId] of this.pendingRequests) {
510
+ this.cleanupRequest(requestId);
511
+ }
512
+ }
513
+ };
514
+
515
+ // src/server/websocket-server.ts
516
+ var MGServer = class {
517
+ wss = null;
518
+ logger;
519
+ connectionManager;
520
+ requestHandler;
521
+ port;
522
+ isRunning = false;
523
+ constructor(options = {}) {
524
+ this.port = options.port || DEFAULT_PORT;
525
+ this.logger = options.logger || createLogger();
526
+ this.connectionManager = new ConnectionManager(this.logger);
527
+ this.requestHandler = new RequestHandler(this.connectionManager, this.logger);
528
+ }
529
+ /**
530
+ * 启动服务器
531
+ */
532
+ async start() {
533
+ if (this.isRunning) {
534
+ throw new MGError("E016" /* SERVER_ALREADY_RUNNING */, "Server \u5DF2\u5728\u8FD0\u884C\u4E2D");
535
+ }
536
+ const port2 = await this.findAvailablePort();
537
+ return new Promise((resolve2, reject) => {
538
+ this.wss = new WebSocketServer({ port: port2 });
539
+ this.wss.on("listening", () => {
540
+ this.port = port2;
541
+ this.isRunning = true;
542
+ this.logger.info(`Server \u542F\u52A8\u6210\u529F\uFF0C\u76D1\u542C\u7AEF\u53E3: ${port2}`);
543
+ this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL);
544
+ resolve2(port2);
545
+ });
546
+ this.wss.on("error", (error) => {
547
+ this.logger.error("Server \u9519\u8BEF:", error);
548
+ reject(error);
549
+ });
550
+ this.wss.on("connection", (ws, request) => {
551
+ this.handleConnection(ws, request);
552
+ });
553
+ });
554
+ }
555
+ /**
556
+ * 查找可用端口
557
+ */
558
+ async findAvailablePort() {
559
+ for (let port2 = PORT_RANGE_START; port2 <= PORT_RANGE_END; port2++) {
560
+ const available = await this.isPortAvailable(port2);
561
+ if (available) {
562
+ return port2;
563
+ }
564
+ this.logger.debug(`\u7AEF\u53E3 ${port2} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u4E0B\u4E00\u4E2A`);
565
+ }
566
+ throw new MGError(
567
+ "E013" /* PORT_EXHAUSTED */,
568
+ `\u7AEF\u53E3 ${PORT_RANGE_START}-${PORT_RANGE_END} \u5747\u88AB\u5360\u7528`
569
+ );
570
+ }
571
+ /**
572
+ * 检查端口是否可用
573
+ */
574
+ isPortAvailable(port2) {
575
+ return new Promise((resolve2) => {
576
+ const testServer = new WebSocketServer({ port: port2 });
577
+ testServer.on("listening", () => {
578
+ testServer.close();
579
+ resolve2(true);
580
+ });
581
+ testServer.on("error", () => {
582
+ resolve2(false);
583
+ });
584
+ });
585
+ }
586
+ /**
587
+ * 处理新连接
588
+ */
589
+ handleConnection(ws, _request) {
590
+ this.logger.info("\u65B0\u8FDE\u63A5\u5EFA\u7ACB");
591
+ const registerTimeout = setTimeout(() => {
592
+ this.logger.warn("\u8FDE\u63A5\u6CE8\u518C\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5");
593
+ ws.close();
594
+ }, 5e3);
595
+ ws.on("message", (data) => {
596
+ try {
597
+ const message = JSON.parse(data.toString());
598
+ if (message.type === "register" /* REGISTER */) {
599
+ clearTimeout(registerTimeout);
600
+ this.handleRegister(ws, message);
601
+ return;
602
+ }
603
+ const managedWs = ws;
604
+ if (!managedWs.connectionId) {
605
+ this.logger.warn("\u672A\u6CE8\u518C\u7684\u8FDE\u63A5\u53D1\u9001\u6D88\u606F\uFF0C\u5FFD\u7565");
606
+ return;
607
+ }
608
+ this.handleMessage(managedWs, message);
609
+ } catch (error) {
610
+ this.logger.error("\u6D88\u606F\u89E3\u6790\u5931\u8D25:", error);
611
+ }
612
+ });
613
+ ws.on("close", () => {
614
+ clearTimeout(registerTimeout);
615
+ const managedWs = ws;
616
+ if (managedWs.connectionId) {
617
+ this.requestHandler.cleanupConnectionRequests(managedWs.connectionId);
618
+ this.connectionManager.removeConnection(managedWs);
619
+ }
620
+ });
621
+ ws.on("error", (error) => {
622
+ this.logger.error("WebSocket \u9519\u8BEF:", error);
623
+ });
624
+ }
625
+ /**
626
+ * 处理注册消息
627
+ */
628
+ handleRegister(ws, message) {
629
+ const { connectionType, pageUrl, pageId } = message.data;
630
+ const managedWs = this.connectionManager.addConnection(
631
+ ws,
632
+ connectionType,
633
+ pageUrl,
634
+ pageId
635
+ );
636
+ const ack = {
637
+ id: message.id || "",
638
+ type: "register_ack" /* REGISTER_ACK */,
639
+ success: true,
640
+ data: {
641
+ connectionId: managedWs.connectionId,
642
+ pageUrl
643
+ }
644
+ };
645
+ ws.send(JSON.stringify(ack));
646
+ }
647
+ /**
648
+ * 处理消息
649
+ */
650
+ handleMessage(ws, message) {
651
+ this.connectionManager.updateLastActive(ws);
652
+ switch (message.type) {
653
+ case "ping" /* PING */:
654
+ this.handlePing(ws, message);
655
+ break;
656
+ case "response" /* RESPONSE */:
657
+ case "error" /* ERROR */:
658
+ this.requestHandler.handleResponse(message);
659
+ break;
660
+ default:
661
+ if (ws.connectionInfo.type === "consumer" /* CONSUMER */) {
662
+ this.requestHandler.handleRequest(ws, message);
663
+ }
664
+ break;
665
+ }
666
+ }
667
+ /**
668
+ * 处理心跳
669
+ */
670
+ handlePing(ws, message) {
671
+ const pong = {
672
+ type: "pong" /* PONG */,
673
+ timestamp: message.timestamp || Date.now()
674
+ };
675
+ ws.send(JSON.stringify(pong));
676
+ }
677
+ /**
678
+ * 停止服务器
679
+ */
680
+ async stop() {
681
+ if (!this.isRunning || !this.wss) {
682
+ return;
683
+ }
684
+ this.logger.info("\u6B63\u5728\u505C\u6B62 Server...");
685
+ this.requestHandler.cleanupAll();
686
+ this.connectionManager.closeAll();
687
+ return new Promise((resolve2) => {
688
+ this.wss.close(() => {
689
+ this.isRunning = false;
690
+ this.wss = null;
691
+ this.logger.info("Server \u5DF2\u505C\u6B62");
692
+ resolve2();
693
+ });
694
+ });
695
+ }
696
+ /**
697
+ * 获取运行状态
698
+ */
699
+ getStatus() {
700
+ return {
701
+ running: this.isRunning,
702
+ port: this.port,
703
+ stats: this.connectionManager.getStats(),
704
+ connectedPages: this.connectionManager.getConnectedPageUrls()
705
+ };
706
+ }
707
+ /**
708
+ * 获取端口
709
+ */
710
+ getPort() {
711
+ return this.port;
712
+ }
713
+ /**
714
+ * 是否运行中
715
+ */
716
+ isServerRunning() {
717
+ return this.isRunning;
718
+ }
719
+ };
720
+ function createServer(options) {
721
+ return new MGServer(options);
722
+ }
723
+
724
+ // src/server/daemon.ts
725
+ function isServerRunning() {
726
+ const info = readServerInfo();
727
+ if (!info) {
728
+ return { running: false, info: null };
729
+ }
730
+ if (!isProcessRunning(info.pid)) {
731
+ deleteServerInfo();
732
+ return { running: false, info: null };
733
+ }
734
+ return { running: true, info };
735
+ }
736
+ async function startServerForeground(port2) {
737
+ const { running, info } = isServerRunning();
738
+ if (running && info) {
739
+ throw new MGError(
740
+ "E016" /* SERVER_ALREADY_RUNNING */,
741
+ `Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
742
+ );
743
+ }
744
+ ensureConfigDir();
745
+ const logger = createLogger({
746
+ console: true,
747
+ file: true,
748
+ minLevel: "INFO" /* INFO */
749
+ });
750
+ const server = createServer({
751
+ port: port2 || DEFAULT_PORT,
752
+ logger
753
+ });
754
+ const cleanup = async () => {
755
+ console.log("\n\u6B63\u5728\u505C\u6B62 Server...");
756
+ await server.stop();
757
+ deleteServerInfo();
758
+ process.exit(0);
759
+ };
760
+ process.on("SIGINT", cleanup);
761
+ process.on("SIGTERM", cleanup);
762
+ try {
763
+ const actualPort = await server.start();
764
+ writeServerInfo({
765
+ port: actualPort,
766
+ pid: process.pid,
767
+ startedAt: getCurrentISOTime()
768
+ });
769
+ console.log(`
770
+ MG Server \u542F\u52A8\u6210\u529F`);
771
+ console.log(`\u76D1\u542C\u7AEF\u53E3: ${actualPort}`);
772
+ console.log(`\u8FDB\u7A0B PID: ${process.pid}`);
773
+ console.log(`\u8FD0\u884C\u6A21\u5F0F: \u524D\u53F0`);
774
+ console.log(`
775
+ \u6309 Ctrl+C \u505C\u6B62...`);
776
+ } catch (error) {
777
+ logger.error("Server \u542F\u52A8\u5931\u8D25:", error);
778
+ throw error;
779
+ }
780
+ }
781
+
782
+ // src/server/daemon-runner.ts
783
+ var args = process.argv.slice(2);
784
+ var port;
785
+ for (let i = 0; i < args.length; i++) {
786
+ if (args[i] === "--port" && args[i + 1]) {
787
+ port = parseInt(args[i + 1], 10);
788
+ }
789
+ }
790
+ startServerForeground(port).catch((error) => {
791
+ console.error("\u5B88\u62A4\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25:", error);
792
+ process.exit(1);
793
+ });
794
+ //# sourceMappingURL=daemon-runner.js.map