@hangox/mg-cli 1.0.0 → 1.0.1
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/cli.js +1574 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon-runner.js +794 -0
- package/dist/daemon-runner.js.map +1 -0
- package/dist/index-DNrszrq9.d.ts +568 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.js +950 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +689 -0
- package/dist/server.js.map +1 -0
- package/package.json +5 -1
- package/.eslintrc.cjs +0 -26
- package/CLAUDE.md +0 -43
- package/src/cli/client.ts +0 -266
- package/src/cli/commands/execute-code.ts +0 -59
- package/src/cli/commands/export-image.ts +0 -193
- package/src/cli/commands/get-all-nodes.ts +0 -81
- package/src/cli/commands/get-all-pages.ts +0 -118
- package/src/cli/commands/get-node-by-id.ts +0 -83
- package/src/cli/commands/get-node-by-link.ts +0 -105
- package/src/cli/commands/server.ts +0 -130
- package/src/cli/index.ts +0 -33
- package/src/index.ts +0 -9
- package/src/server/connection-manager.ts +0 -211
- package/src/server/daemon-runner.ts +0 -22
- package/src/server/daemon.ts +0 -211
- package/src/server/index.ts +0 -8
- package/src/server/logger.ts +0 -117
- package/src/server/request-handler.ts +0 -192
- package/src/server/websocket-server.ts +0 -297
- package/src/shared/constants.ts +0 -90
- package/src/shared/errors.ts +0 -131
- package/src/shared/index.ts +0 -8
- package/src/shared/types.ts +0 -227
- package/src/shared/utils.ts +0 -352
- package/tests/unit/shared/constants.test.ts +0 -66
- package/tests/unit/shared/errors.test.ts +0 -82
- package/tests/unit/shared/utils.test.ts +0 -208
- package/tsconfig.json +0 -22
- package/tsup.config.ts +0 -33
- package/vitest.config.ts +0 -22
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1574 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
import { Command as Command8 } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/cli/commands/server.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
|
|
9
|
+
// src/server/daemon.ts
|
|
10
|
+
import { spawn } from "child_process";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
13
|
+
|
|
14
|
+
// src/shared/utils.ts
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
16
|
+
import { dirname, resolve, isAbsolute } from "path";
|
|
17
|
+
|
|
18
|
+
// src/shared/constants.ts
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { join } from "path";
|
|
21
|
+
var DEFAULT_PORT = 9527;
|
|
22
|
+
var PORT_RANGE_START = 9527;
|
|
23
|
+
var PORT_RANGE_END = 9536;
|
|
24
|
+
var PORT_SCAN_TIMEOUT = 500;
|
|
25
|
+
var CONFIG_DIR = join(homedir(), ".mg-plugin");
|
|
26
|
+
var SERVER_INFO_FILE = join(CONFIG_DIR, "server.json");
|
|
27
|
+
var LOG_DIR = join(CONFIG_DIR, "logs");
|
|
28
|
+
var SERVER_LOG_FILE = join(LOG_DIR, "server.log");
|
|
29
|
+
var HEARTBEAT_INTERVAL = 3e4;
|
|
30
|
+
var HEARTBEAT_TIMEOUT = 9e4;
|
|
31
|
+
var REQUEST_TIMEOUT = 3e4;
|
|
32
|
+
var SERVER_START_TIMEOUT = 5e3;
|
|
33
|
+
var RETRY_INTERVALS = [1e3, 2e3, 4e3];
|
|
34
|
+
var MAX_RETRY_COUNT = 3;
|
|
35
|
+
|
|
36
|
+
// src/shared/utils.ts
|
|
37
|
+
function ensureDir(dir) {
|
|
38
|
+
if (!existsSync(dir)) {
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function ensureConfigDir() {
|
|
43
|
+
ensureDir(CONFIG_DIR);
|
|
44
|
+
ensureDir(LOG_DIR);
|
|
45
|
+
}
|
|
46
|
+
function readServerInfo() {
|
|
47
|
+
try {
|
|
48
|
+
if (!existsSync(SERVER_INFO_FILE)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const content = readFileSync(SERVER_INFO_FILE, "utf-8");
|
|
52
|
+
return JSON.parse(content);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function writeServerInfo(info) {
|
|
58
|
+
ensureConfigDir();
|
|
59
|
+
writeFileSync(SERVER_INFO_FILE, JSON.stringify(info, null, 2), "utf-8");
|
|
60
|
+
}
|
|
61
|
+
function deleteServerInfo() {
|
|
62
|
+
try {
|
|
63
|
+
if (existsSync(SERVER_INFO_FILE)) {
|
|
64
|
+
unlinkSync(SERVER_INFO_FILE);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function isProcessRunning(pid) {
|
|
70
|
+
try {
|
|
71
|
+
process.kill(pid, 0);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function killProcess(pid) {
|
|
78
|
+
try {
|
|
79
|
+
process.kill(pid, "SIGTERM");
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function parseMgpLink(link) {
|
|
86
|
+
if (!link.startsWith("mgp://")) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const urlPart = link.slice(6);
|
|
91
|
+
const questionMarkIndex = urlPart.indexOf("?");
|
|
92
|
+
if (questionMarkIndex === -1) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const pageUrl = urlPart.slice(0, questionMarkIndex);
|
|
96
|
+
const queryString = urlPart.slice(questionMarkIndex + 1);
|
|
97
|
+
const params = new URLSearchParams(queryString);
|
|
98
|
+
const encodedNodeId = params.get("nodeId");
|
|
99
|
+
if (!encodedNodeId) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const nodeId = decodeURIComponent(encodedNodeId);
|
|
103
|
+
if (!/^(\d+:\d+)(\/\d+:\d+)*$/.test(nodeId)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const encodedNodePath = params.get("nodePath");
|
|
107
|
+
let nodePath;
|
|
108
|
+
if (encodedNodePath) {
|
|
109
|
+
const decodedNodePath = decodeURIComponent(encodedNodePath);
|
|
110
|
+
nodePath = decodedNodePath.split("/").filter(Boolean);
|
|
111
|
+
if (!nodePath.every((segment) => /^\d+:\d+$/.test(segment))) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
pageUrl,
|
|
117
|
+
nodeId,
|
|
118
|
+
nodePath
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function formatDuration(ms) {
|
|
125
|
+
const seconds = Math.floor(ms / 1e3);
|
|
126
|
+
const minutes = Math.floor(seconds / 60);
|
|
127
|
+
const hours = Math.floor(minutes / 60);
|
|
128
|
+
const days = Math.floor(hours / 24);
|
|
129
|
+
if (days > 0) {
|
|
130
|
+
return `${days} \u5929 ${hours % 24} \u5C0F\u65F6`;
|
|
131
|
+
}
|
|
132
|
+
if (hours > 0) {
|
|
133
|
+
return `${hours} \u5C0F\u65F6 ${minutes % 60} \u5206\u949F`;
|
|
134
|
+
}
|
|
135
|
+
if (minutes > 0) {
|
|
136
|
+
return `${minutes} \u5206\u949F ${seconds % 60} \u79D2`;
|
|
137
|
+
}
|
|
138
|
+
return `${seconds} \u79D2`;
|
|
139
|
+
}
|
|
140
|
+
function generateId() {
|
|
141
|
+
return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
142
|
+
}
|
|
143
|
+
function getCurrentISOTime() {
|
|
144
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
145
|
+
}
|
|
146
|
+
function formatLogTime(date = /* @__PURE__ */ new Date()) {
|
|
147
|
+
const year = date.getFullYear();
|
|
148
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
149
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
150
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
151
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
152
|
+
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
153
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
154
|
+
}
|
|
155
|
+
function extractFileIdFromUrl(url) {
|
|
156
|
+
const match = url.match(/\/file\/(\d+)/);
|
|
157
|
+
return match ? match[1] : null;
|
|
158
|
+
}
|
|
159
|
+
function extractFileIdFromMgpLink(link) {
|
|
160
|
+
if (!link.startsWith("mgp://")) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return extractFileIdFromUrl(link);
|
|
164
|
+
}
|
|
165
|
+
function extractFileId(input) {
|
|
166
|
+
const trimmed = input.trim();
|
|
167
|
+
if (/^\d+$/.test(trimmed)) {
|
|
168
|
+
return trimmed;
|
|
169
|
+
}
|
|
170
|
+
if (trimmed.startsWith("mgp://")) {
|
|
171
|
+
return extractFileIdFromMgpLink(trimmed);
|
|
172
|
+
}
|
|
173
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
174
|
+
return extractFileIdFromUrl(trimmed);
|
|
175
|
+
}
|
|
176
|
+
return extractFileIdFromUrl(trimmed);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/shared/errors.ts
|
|
180
|
+
var ErrorNames = {
|
|
181
|
+
["E001" /* CONNECTION_FAILED */]: "CONNECTION_FAILED",
|
|
182
|
+
["E002" /* CONNECTION_TIMEOUT */]: "CONNECTION_TIMEOUT",
|
|
183
|
+
["E003" /* NO_PAGE_CONNECTED */]: "NO_PAGE_CONNECTED",
|
|
184
|
+
["E004" /* PAGE_NOT_FOUND */]: "PAGE_NOT_FOUND",
|
|
185
|
+
["E005" /* NODE_NOT_FOUND */]: "NODE_NOT_FOUND",
|
|
186
|
+
["E006" /* NO_SELECTION */]: "NO_SELECTION",
|
|
187
|
+
["E007" /* MG_UNAVAILABLE */]: "MG_UNAVAILABLE",
|
|
188
|
+
["E008" /* EXPORT_FAILED */]: "EXPORT_FAILED",
|
|
189
|
+
["E009" /* FILE_WRITE_FAILED */]: "FILE_WRITE_FAILED",
|
|
190
|
+
["E010" /* INVALID_LINK */]: "INVALID_LINK",
|
|
191
|
+
["E011" /* INVALID_PARAMS */]: "INVALID_PARAMS",
|
|
192
|
+
["E012" /* REQUEST_TIMEOUT */]: "REQUEST_TIMEOUT",
|
|
193
|
+
["E013" /* PORT_EXHAUSTED */]: "PORT_EXHAUSTED",
|
|
194
|
+
["E014" /* SERVER_DISCOVERY_FAILED */]: "SERVER_DISCOVERY_FAILED",
|
|
195
|
+
["E015" /* SERVER_START_FAILED */]: "SERVER_START_FAILED",
|
|
196
|
+
["E016" /* SERVER_ALREADY_RUNNING */]: "SERVER_ALREADY_RUNNING",
|
|
197
|
+
["E017" /* CONNECTION_LOST */]: "CONNECTION_LOST",
|
|
198
|
+
["E099" /* UNKNOWN_ERROR */]: "UNKNOWN_ERROR"
|
|
199
|
+
};
|
|
200
|
+
var ErrorMessages = {
|
|
201
|
+
["E001" /* CONNECTION_FAILED */]: "\u65E0\u6CD5\u8FDE\u63A5\u5230 MG Server",
|
|
202
|
+
["E002" /* CONNECTION_TIMEOUT */]: "\u8FDE\u63A5\u8D85\u65F6",
|
|
203
|
+
["E003" /* NO_PAGE_CONNECTED */]: "\u6CA1\u6709 MasterGo \u9875\u9762\u8FDE\u63A5\u5230 Server",
|
|
204
|
+
["E004" /* PAGE_NOT_FOUND */]: "\u672A\u627E\u5230\u5339\u914D\u7684\u9875\u9762",
|
|
205
|
+
["E005" /* NODE_NOT_FOUND */]: "\u8282\u70B9\u4E0D\u5B58\u5728",
|
|
206
|
+
["E006" /* NO_SELECTION */]: "\u6CA1\u6709\u9009\u4E2D\u4EFB\u4F55\u8282\u70B9",
|
|
207
|
+
["E007" /* MG_UNAVAILABLE */]: "mg \u5BF9\u8C61\u4E0D\u53EF\u7528",
|
|
208
|
+
["E008" /* EXPORT_FAILED */]: "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25",
|
|
209
|
+
["E009" /* FILE_WRITE_FAILED */]: "\u6587\u4EF6\u5199\u5165\u5931\u8D25",
|
|
210
|
+
["E010" /* INVALID_LINK */]: "\u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F",
|
|
211
|
+
["E011" /* INVALID_PARAMS */]: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
|
|
212
|
+
["E012" /* REQUEST_TIMEOUT */]: "\u8BF7\u6C42\u8D85\u65F6",
|
|
213
|
+
["E013" /* PORT_EXHAUSTED */]: "\u6240\u6709\u5907\u9009\u7AEF\u53E3\u5747\u88AB\u5360\u7528",
|
|
214
|
+
["E014" /* SERVER_DISCOVERY_FAILED */]: "\u65E0\u6CD5\u53D1\u73B0 Server (\u7AEF\u53E3\u626B\u63CF\u5931\u8D25)",
|
|
215
|
+
["E015" /* SERVER_START_FAILED */]: "\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25",
|
|
216
|
+
["E016" /* SERVER_ALREADY_RUNNING */]: "Server \u5DF2\u5728\u8FD0\u884C\u4E2D",
|
|
217
|
+
["E017" /* CONNECTION_LOST */]: "\u8FDE\u63A5\u65AD\u5F00",
|
|
218
|
+
["E099" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF"
|
|
219
|
+
};
|
|
220
|
+
var MGError = class extends Error {
|
|
221
|
+
/** 错误码 */
|
|
222
|
+
code;
|
|
223
|
+
/** 错误名称 */
|
|
224
|
+
errorName;
|
|
225
|
+
/** 额外详情 */
|
|
226
|
+
details;
|
|
227
|
+
constructor(code, message, details) {
|
|
228
|
+
super(message || ErrorMessages[code]);
|
|
229
|
+
this.name = "MGError";
|
|
230
|
+
this.code = code;
|
|
231
|
+
this.errorName = ErrorNames[code];
|
|
232
|
+
this.details = details;
|
|
233
|
+
}
|
|
234
|
+
/** 转换为 JSON 格式 */
|
|
235
|
+
toJSON() {
|
|
236
|
+
return {
|
|
237
|
+
code: this.code,
|
|
238
|
+
name: this.errorName,
|
|
239
|
+
message: this.message,
|
|
240
|
+
details: this.details
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/** 格式化输出 */
|
|
244
|
+
toString() {
|
|
245
|
+
return `\u9519\u8BEF [${this.code}]: ${this.message}`;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/server/websocket-server.ts
|
|
250
|
+
import { WebSocketServer } from "ws";
|
|
251
|
+
|
|
252
|
+
// src/server/logger.ts
|
|
253
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
254
|
+
import { dirname as dirname2 } from "path";
|
|
255
|
+
var levelPriority = {
|
|
256
|
+
["DEBUG" /* DEBUG */]: 0,
|
|
257
|
+
["INFO" /* INFO */]: 1,
|
|
258
|
+
["WARN" /* WARN */]: 2,
|
|
259
|
+
["ERROR" /* ERROR */]: 3
|
|
260
|
+
};
|
|
261
|
+
var Logger = class {
|
|
262
|
+
options;
|
|
263
|
+
constructor(options = {}) {
|
|
264
|
+
this.options = {
|
|
265
|
+
console: options.console ?? true,
|
|
266
|
+
file: options.file ?? false,
|
|
267
|
+
filePath: options.filePath ?? SERVER_LOG_FILE,
|
|
268
|
+
minLevel: options.minLevel ?? "INFO" /* INFO */
|
|
269
|
+
};
|
|
270
|
+
if (this.options.file) {
|
|
271
|
+
const dir = dirname2(this.options.filePath);
|
|
272
|
+
if (!existsSync2(dir)) {
|
|
273
|
+
mkdirSync2(dir, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* 记录日志
|
|
279
|
+
*/
|
|
280
|
+
log(level, message, ...args) {
|
|
281
|
+
if (levelPriority[level] < levelPriority[this.options.minLevel]) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const timestamp = formatLogTime();
|
|
285
|
+
const formattedMessage = `[${timestamp}] [${level}] ${message}`;
|
|
286
|
+
if (this.options.console) {
|
|
287
|
+
const consoleMethod = level === "ERROR" /* ERROR */ ? console.error : console.log;
|
|
288
|
+
if (args.length > 0) {
|
|
289
|
+
consoleMethod(formattedMessage, ...args);
|
|
290
|
+
} else {
|
|
291
|
+
consoleMethod(formattedMessage);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (this.options.file) {
|
|
295
|
+
try {
|
|
296
|
+
const fileMessage = args.length > 0 ? `${formattedMessage} ${JSON.stringify(args)}
|
|
297
|
+
` : `${formattedMessage}
|
|
298
|
+
`;
|
|
299
|
+
appendFileSync(this.options.filePath, fileMessage);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
if (this.options.console) {
|
|
302
|
+
console.error("\u65E5\u5FD7\u6587\u4EF6\u5199\u5165\u5931\u8D25:", error);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
debug(message, ...args) {
|
|
308
|
+
this.log("DEBUG" /* DEBUG */, message, ...args);
|
|
309
|
+
}
|
|
310
|
+
info(message, ...args) {
|
|
311
|
+
this.log("INFO" /* INFO */, message, ...args);
|
|
312
|
+
}
|
|
313
|
+
warn(message, ...args) {
|
|
314
|
+
this.log("WARN" /* WARN */, message, ...args);
|
|
315
|
+
}
|
|
316
|
+
error(message, ...args) {
|
|
317
|
+
this.log("ERROR" /* ERROR */, message, ...args);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
function createLogger(options) {
|
|
321
|
+
return new Logger(options);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/server/connection-manager.ts
|
|
325
|
+
var ConnectionManager = class {
|
|
326
|
+
logger;
|
|
327
|
+
/** Provider 连接(按页面 URL 索引) */
|
|
328
|
+
providers = /* @__PURE__ */ new Map();
|
|
329
|
+
/** Consumer 连接 */
|
|
330
|
+
consumers = /* @__PURE__ */ new Map();
|
|
331
|
+
/** 所有连接(按 ID 索引) */
|
|
332
|
+
allConnections = /* @__PURE__ */ new Map();
|
|
333
|
+
/** 心跳检查定时器 */
|
|
334
|
+
heartbeatTimer = null;
|
|
335
|
+
constructor(logger) {
|
|
336
|
+
this.logger = logger;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 启动心跳检查
|
|
340
|
+
*/
|
|
341
|
+
startHeartbeatCheck(interval = 3e4) {
|
|
342
|
+
if (this.heartbeatTimer) {
|
|
343
|
+
clearInterval(this.heartbeatTimer);
|
|
344
|
+
}
|
|
345
|
+
this.heartbeatTimer = setInterval(() => {
|
|
346
|
+
this.checkHeartbeats();
|
|
347
|
+
}, interval);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* 停止心跳检查
|
|
351
|
+
*/
|
|
352
|
+
stopHeartbeatCheck() {
|
|
353
|
+
if (this.heartbeatTimer) {
|
|
354
|
+
clearInterval(this.heartbeatTimer);
|
|
355
|
+
this.heartbeatTimer = null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 检查所有连接的心跳
|
|
360
|
+
*/
|
|
361
|
+
checkHeartbeats() {
|
|
362
|
+
const now = Date.now();
|
|
363
|
+
for (const [id, ws] of this.allConnections) {
|
|
364
|
+
const lastActive = ws.connectionInfo.lastActiveAt.getTime();
|
|
365
|
+
const elapsed = now - lastActive;
|
|
366
|
+
if (elapsed > HEARTBEAT_TIMEOUT) {
|
|
367
|
+
this.logger.warn(`\u8FDE\u63A5 ${id} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5`);
|
|
368
|
+
this.removeConnection(ws);
|
|
369
|
+
ws.terminate();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 添加连接
|
|
375
|
+
*/
|
|
376
|
+
addConnection(ws, type, pageUrl, pageId) {
|
|
377
|
+
const connectionId = generateId();
|
|
378
|
+
const now = /* @__PURE__ */ new Date();
|
|
379
|
+
const connectionInfo = {
|
|
380
|
+
id: connectionId,
|
|
381
|
+
type,
|
|
382
|
+
pageUrl,
|
|
383
|
+
pageId,
|
|
384
|
+
connectedAt: now,
|
|
385
|
+
lastActiveAt: now
|
|
386
|
+
};
|
|
387
|
+
const managedWs = ws;
|
|
388
|
+
managedWs.connectionId = connectionId;
|
|
389
|
+
managedWs.connectionInfo = connectionInfo;
|
|
390
|
+
managedWs.isAlive = true;
|
|
391
|
+
this.allConnections.set(connectionId, managedWs);
|
|
392
|
+
if (type === "provider" /* PROVIDER */ && pageUrl) {
|
|
393
|
+
const existing = this.providers.get(pageUrl);
|
|
394
|
+
if (existing) {
|
|
395
|
+
this.logger.info(`\u9875\u9762 ${pageUrl} \u5DF2\u6709\u8FDE\u63A5\uFF0C\u66FF\u6362\u4E3A\u65B0\u8FDE\u63A5`);
|
|
396
|
+
this.removeConnection(existing);
|
|
397
|
+
existing.close();
|
|
398
|
+
}
|
|
399
|
+
this.providers.set(pageUrl, managedWs);
|
|
400
|
+
this.logger.info(`Provider \u8FDE\u63A5: ${pageUrl}`);
|
|
401
|
+
} else if (type === "consumer" /* CONSUMER */) {
|
|
402
|
+
this.consumers.set(connectionId, managedWs);
|
|
403
|
+
this.logger.info(`Consumer \u8FDE\u63A5: ${connectionId}`);
|
|
404
|
+
}
|
|
405
|
+
return managedWs;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 移除连接
|
|
409
|
+
*/
|
|
410
|
+
removeConnection(ws) {
|
|
411
|
+
const { connectionId, connectionInfo } = ws;
|
|
412
|
+
this.allConnections.delete(connectionId);
|
|
413
|
+
if (connectionInfo.type === "provider" /* PROVIDER */ && connectionInfo.pageUrl) {
|
|
414
|
+
this.providers.delete(connectionInfo.pageUrl);
|
|
415
|
+
this.logger.info(`Provider \u65AD\u5F00: ${connectionInfo.pageUrl}`);
|
|
416
|
+
} else if (connectionInfo.type === "consumer" /* CONSUMER */) {
|
|
417
|
+
this.consumers.delete(connectionId);
|
|
418
|
+
this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 更新连接活跃时间
|
|
423
|
+
*/
|
|
424
|
+
updateLastActive(ws) {
|
|
425
|
+
ws.connectionInfo.lastActiveAt = /* @__PURE__ */ new Date();
|
|
426
|
+
ws.isAlive = true;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* 根据页面 URL 查找 Provider
|
|
430
|
+
*/
|
|
431
|
+
findProviderByPageUrl(pageUrl) {
|
|
432
|
+
return this.providers.get(pageUrl);
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* 获取第一个可用的 Provider
|
|
436
|
+
*/
|
|
437
|
+
getFirstProvider() {
|
|
438
|
+
const iterator = this.providers.values();
|
|
439
|
+
const first = iterator.next();
|
|
440
|
+
return first.value;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* 获取所有 Provider 信息
|
|
444
|
+
*/
|
|
445
|
+
getAllProviders() {
|
|
446
|
+
return Array.from(this.providers.values()).map((ws) => ws.connectionInfo);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 获取连接统计
|
|
450
|
+
*/
|
|
451
|
+
getStats() {
|
|
452
|
+
return {
|
|
453
|
+
providers: this.providers.size,
|
|
454
|
+
consumers: this.consumers.size,
|
|
455
|
+
total: this.allConnections.size
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* 获取所有已连接的页面 URL
|
|
460
|
+
*/
|
|
461
|
+
getConnectedPageUrls() {
|
|
462
|
+
return Array.from(this.providers.keys());
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* 关闭所有连接
|
|
466
|
+
*/
|
|
467
|
+
closeAll() {
|
|
468
|
+
this.stopHeartbeatCheck();
|
|
469
|
+
for (const ws of this.allConnections.values()) {
|
|
470
|
+
ws.close();
|
|
471
|
+
}
|
|
472
|
+
this.providers.clear();
|
|
473
|
+
this.consumers.clear();
|
|
474
|
+
this.allConnections.clear();
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/server/request-handler.ts
|
|
479
|
+
var RequestHandler = class {
|
|
480
|
+
logger;
|
|
481
|
+
connectionManager;
|
|
482
|
+
/** 待处理的请求 */
|
|
483
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
484
|
+
constructor(connectionManager, logger) {
|
|
485
|
+
this.connectionManager = connectionManager;
|
|
486
|
+
this.logger = logger;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* 处理 Consumer 请求
|
|
490
|
+
*/
|
|
491
|
+
async handleRequest(consumer, message) {
|
|
492
|
+
const requestId = message.id || generateId();
|
|
493
|
+
const { type, pageUrl, params } = message;
|
|
494
|
+
this.logger.info(`\u6536\u5230\u8BF7\u6C42: ${type} (${requestId})`, { pageUrl });
|
|
495
|
+
let provider;
|
|
496
|
+
if (pageUrl) {
|
|
497
|
+
provider = this.connectionManager.findProviderByPageUrl(pageUrl);
|
|
498
|
+
if (!provider) {
|
|
499
|
+
this.sendError(consumer, requestId, "E004" /* PAGE_NOT_FOUND */, `\u672A\u627E\u5230\u9875\u9762: ${pageUrl}`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
provider = this.connectionManager.getFirstProvider();
|
|
504
|
+
if (!provider) {
|
|
505
|
+
this.sendError(consumer, requestId, "E003" /* NO_PAGE_CONNECTED */, "\u6CA1\u6709\u9875\u9762\u8FDE\u63A5\u5230 Server");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const timer = setTimeout(() => {
|
|
510
|
+
this.handleTimeout(requestId);
|
|
511
|
+
}, REQUEST_TIMEOUT);
|
|
512
|
+
this.pendingRequests.set(requestId, {
|
|
513
|
+
id: requestId,
|
|
514
|
+
consumer,
|
|
515
|
+
timer,
|
|
516
|
+
timestamp: Date.now()
|
|
517
|
+
});
|
|
518
|
+
const forwardMessage = {
|
|
519
|
+
id: requestId,
|
|
520
|
+
type,
|
|
521
|
+
pageUrl: pageUrl || provider.connectionInfo.pageUrl,
|
|
522
|
+
params,
|
|
523
|
+
timestamp: Date.now()
|
|
524
|
+
};
|
|
525
|
+
try {
|
|
526
|
+
provider.send(JSON.stringify(forwardMessage));
|
|
527
|
+
this.logger.info(`\u8BF7\u6C42\u8F6C\u53D1: ${type} -> ${provider.connectionInfo.pageUrl}`);
|
|
528
|
+
} catch {
|
|
529
|
+
this.cleanupRequest(requestId);
|
|
530
|
+
this.sendError(consumer, requestId, "E001" /* CONNECTION_FAILED */, "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* 处理 Provider 响应
|
|
535
|
+
*/
|
|
536
|
+
handleResponse(response) {
|
|
537
|
+
const { id } = response;
|
|
538
|
+
const pending = this.pendingRequests.get(id);
|
|
539
|
+
if (!pending) {
|
|
540
|
+
this.logger.warn(`\u6536\u5230\u672A\u77E5\u8BF7\u6C42\u7684\u54CD\u5E94: ${id}`);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
this.cleanupRequest(id);
|
|
544
|
+
try {
|
|
545
|
+
pending.consumer.send(JSON.stringify(response));
|
|
546
|
+
this.logger.info(
|
|
547
|
+
`\u54CD\u5E94\u8FD4\u56DE: ${id} (${response.success ? "\u6210\u529F" : "\u5931\u8D25"})`
|
|
548
|
+
);
|
|
549
|
+
} catch (error) {
|
|
550
|
+
this.logger.error(`\u54CD\u5E94\u8F6C\u53D1\u5931\u8D25: ${id}`, error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 处理请求超时
|
|
555
|
+
*/
|
|
556
|
+
handleTimeout(requestId) {
|
|
557
|
+
const pending = this.pendingRequests.get(requestId);
|
|
558
|
+
if (!pending) return;
|
|
559
|
+
this.logger.warn(`\u8BF7\u6C42\u8D85\u65F6: ${requestId}`);
|
|
560
|
+
this.cleanupRequest(requestId);
|
|
561
|
+
this.sendError(pending.consumer, requestId, "E012" /* REQUEST_TIMEOUT */, "\u8BF7\u6C42\u8D85\u65F6");
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* 清理请求
|
|
565
|
+
*/
|
|
566
|
+
cleanupRequest(requestId) {
|
|
567
|
+
const pending = this.pendingRequests.get(requestId);
|
|
568
|
+
if (pending) {
|
|
569
|
+
clearTimeout(pending.timer);
|
|
570
|
+
this.pendingRequests.delete(requestId);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 发送错误响应
|
|
575
|
+
*/
|
|
576
|
+
sendError(consumer, requestId, code, message) {
|
|
577
|
+
const error = new MGError(code, message);
|
|
578
|
+
const response = {
|
|
579
|
+
id: requestId,
|
|
580
|
+
type: "error" /* ERROR */,
|
|
581
|
+
success: false,
|
|
582
|
+
data: null,
|
|
583
|
+
error: error.toJSON()
|
|
584
|
+
};
|
|
585
|
+
try {
|
|
586
|
+
consumer.send(JSON.stringify(response));
|
|
587
|
+
} catch (err) {
|
|
588
|
+
this.logger.error(`\u53D1\u9001\u9519\u8BEF\u54CD\u5E94\u5931\u8D25: ${requestId}`, err);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* 清理特定连接的所有待处理请求
|
|
593
|
+
*/
|
|
594
|
+
cleanupConnectionRequests(connectionId) {
|
|
595
|
+
for (const [requestId, pending] of this.pendingRequests) {
|
|
596
|
+
if (pending.consumer.connectionId === connectionId) {
|
|
597
|
+
this.cleanupRequest(requestId);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* 清理所有待处理请求
|
|
603
|
+
*/
|
|
604
|
+
cleanupAll() {
|
|
605
|
+
for (const [requestId] of this.pendingRequests) {
|
|
606
|
+
this.cleanupRequest(requestId);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// src/server/websocket-server.ts
|
|
612
|
+
var MGServer = class {
|
|
613
|
+
wss = null;
|
|
614
|
+
logger;
|
|
615
|
+
connectionManager;
|
|
616
|
+
requestHandler;
|
|
617
|
+
port;
|
|
618
|
+
isRunning = false;
|
|
619
|
+
constructor(options = {}) {
|
|
620
|
+
this.port = options.port || DEFAULT_PORT;
|
|
621
|
+
this.logger = options.logger || createLogger();
|
|
622
|
+
this.connectionManager = new ConnectionManager(this.logger);
|
|
623
|
+
this.requestHandler = new RequestHandler(this.connectionManager, this.logger);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* 启动服务器
|
|
627
|
+
*/
|
|
628
|
+
async start() {
|
|
629
|
+
if (this.isRunning) {
|
|
630
|
+
throw new MGError("E016" /* SERVER_ALREADY_RUNNING */, "Server \u5DF2\u5728\u8FD0\u884C\u4E2D");
|
|
631
|
+
}
|
|
632
|
+
const port = await this.findAvailablePort();
|
|
633
|
+
return new Promise((resolve7, reject) => {
|
|
634
|
+
this.wss = new WebSocketServer({ port });
|
|
635
|
+
this.wss.on("listening", () => {
|
|
636
|
+
this.port = port;
|
|
637
|
+
this.isRunning = true;
|
|
638
|
+
this.logger.info(`Server \u542F\u52A8\u6210\u529F\uFF0C\u76D1\u542C\u7AEF\u53E3: ${port}`);
|
|
639
|
+
this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL);
|
|
640
|
+
resolve7(port);
|
|
641
|
+
});
|
|
642
|
+
this.wss.on("error", (error) => {
|
|
643
|
+
this.logger.error("Server \u9519\u8BEF:", error);
|
|
644
|
+
reject(error);
|
|
645
|
+
});
|
|
646
|
+
this.wss.on("connection", (ws, request) => {
|
|
647
|
+
this.handleConnection(ws, request);
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* 查找可用端口
|
|
653
|
+
*/
|
|
654
|
+
async findAvailablePort() {
|
|
655
|
+
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
656
|
+
const available = await this.isPortAvailable(port);
|
|
657
|
+
if (available) {
|
|
658
|
+
return port;
|
|
659
|
+
}
|
|
660
|
+
this.logger.debug(`\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u4E0B\u4E00\u4E2A`);
|
|
661
|
+
}
|
|
662
|
+
throw new MGError(
|
|
663
|
+
"E013" /* PORT_EXHAUSTED */,
|
|
664
|
+
`\u7AEF\u53E3 ${PORT_RANGE_START}-${PORT_RANGE_END} \u5747\u88AB\u5360\u7528`
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* 检查端口是否可用
|
|
669
|
+
*/
|
|
670
|
+
isPortAvailable(port) {
|
|
671
|
+
return new Promise((resolve7) => {
|
|
672
|
+
const testServer = new WebSocketServer({ port });
|
|
673
|
+
testServer.on("listening", () => {
|
|
674
|
+
testServer.close();
|
|
675
|
+
resolve7(true);
|
|
676
|
+
});
|
|
677
|
+
testServer.on("error", () => {
|
|
678
|
+
resolve7(false);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* 处理新连接
|
|
684
|
+
*/
|
|
685
|
+
handleConnection(ws, _request) {
|
|
686
|
+
this.logger.info("\u65B0\u8FDE\u63A5\u5EFA\u7ACB");
|
|
687
|
+
const registerTimeout = setTimeout(() => {
|
|
688
|
+
this.logger.warn("\u8FDE\u63A5\u6CE8\u518C\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5");
|
|
689
|
+
ws.close();
|
|
690
|
+
}, 5e3);
|
|
691
|
+
ws.on("message", (data) => {
|
|
692
|
+
try {
|
|
693
|
+
const message = JSON.parse(data.toString());
|
|
694
|
+
if (message.type === "register" /* REGISTER */) {
|
|
695
|
+
clearTimeout(registerTimeout);
|
|
696
|
+
this.handleRegister(ws, message);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const managedWs = ws;
|
|
700
|
+
if (!managedWs.connectionId) {
|
|
701
|
+
this.logger.warn("\u672A\u6CE8\u518C\u7684\u8FDE\u63A5\u53D1\u9001\u6D88\u606F\uFF0C\u5FFD\u7565");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
this.handleMessage(managedWs, message);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
this.logger.error("\u6D88\u606F\u89E3\u6790\u5931\u8D25:", error);
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
ws.on("close", () => {
|
|
710
|
+
clearTimeout(registerTimeout);
|
|
711
|
+
const managedWs = ws;
|
|
712
|
+
if (managedWs.connectionId) {
|
|
713
|
+
this.requestHandler.cleanupConnectionRequests(managedWs.connectionId);
|
|
714
|
+
this.connectionManager.removeConnection(managedWs);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
ws.on("error", (error) => {
|
|
718
|
+
this.logger.error("WebSocket \u9519\u8BEF:", error);
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* 处理注册消息
|
|
723
|
+
*/
|
|
724
|
+
handleRegister(ws, message) {
|
|
725
|
+
const { connectionType, pageUrl, pageId } = message.data;
|
|
726
|
+
const managedWs = this.connectionManager.addConnection(
|
|
727
|
+
ws,
|
|
728
|
+
connectionType,
|
|
729
|
+
pageUrl,
|
|
730
|
+
pageId
|
|
731
|
+
);
|
|
732
|
+
const ack = {
|
|
733
|
+
id: message.id || "",
|
|
734
|
+
type: "register_ack" /* REGISTER_ACK */,
|
|
735
|
+
success: true,
|
|
736
|
+
data: {
|
|
737
|
+
connectionId: managedWs.connectionId,
|
|
738
|
+
pageUrl
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
ws.send(JSON.stringify(ack));
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 处理消息
|
|
745
|
+
*/
|
|
746
|
+
handleMessage(ws, message) {
|
|
747
|
+
this.connectionManager.updateLastActive(ws);
|
|
748
|
+
switch (message.type) {
|
|
749
|
+
case "ping" /* PING */:
|
|
750
|
+
this.handlePing(ws, message);
|
|
751
|
+
break;
|
|
752
|
+
case "response" /* RESPONSE */:
|
|
753
|
+
case "error" /* ERROR */:
|
|
754
|
+
this.requestHandler.handleResponse(message);
|
|
755
|
+
break;
|
|
756
|
+
default:
|
|
757
|
+
if (ws.connectionInfo.type === "consumer" /* CONSUMER */) {
|
|
758
|
+
this.requestHandler.handleRequest(ws, message);
|
|
759
|
+
}
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* 处理心跳
|
|
765
|
+
*/
|
|
766
|
+
handlePing(ws, message) {
|
|
767
|
+
const pong = {
|
|
768
|
+
type: "pong" /* PONG */,
|
|
769
|
+
timestamp: message.timestamp || Date.now()
|
|
770
|
+
};
|
|
771
|
+
ws.send(JSON.stringify(pong));
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* 停止服务器
|
|
775
|
+
*/
|
|
776
|
+
async stop() {
|
|
777
|
+
if (!this.isRunning || !this.wss) {
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
this.logger.info("\u6B63\u5728\u505C\u6B62 Server...");
|
|
781
|
+
this.requestHandler.cleanupAll();
|
|
782
|
+
this.connectionManager.closeAll();
|
|
783
|
+
return new Promise((resolve7) => {
|
|
784
|
+
this.wss.close(() => {
|
|
785
|
+
this.isRunning = false;
|
|
786
|
+
this.wss = null;
|
|
787
|
+
this.logger.info("Server \u5DF2\u505C\u6B62");
|
|
788
|
+
resolve7();
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* 获取运行状态
|
|
794
|
+
*/
|
|
795
|
+
getStatus() {
|
|
796
|
+
return {
|
|
797
|
+
running: this.isRunning,
|
|
798
|
+
port: this.port,
|
|
799
|
+
stats: this.connectionManager.getStats(),
|
|
800
|
+
connectedPages: this.connectionManager.getConnectedPageUrls()
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* 获取端口
|
|
805
|
+
*/
|
|
806
|
+
getPort() {
|
|
807
|
+
return this.port;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* 是否运行中
|
|
811
|
+
*/
|
|
812
|
+
isServerRunning() {
|
|
813
|
+
return this.isRunning;
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
function createServer(options) {
|
|
817
|
+
return new MGServer(options);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/server/daemon.ts
|
|
821
|
+
function isServerRunning() {
|
|
822
|
+
const info = readServerInfo();
|
|
823
|
+
if (!info) {
|
|
824
|
+
return { running: false, info: null };
|
|
825
|
+
}
|
|
826
|
+
if (!isProcessRunning(info.pid)) {
|
|
827
|
+
deleteServerInfo();
|
|
828
|
+
return { running: false, info: null };
|
|
829
|
+
}
|
|
830
|
+
return { running: true, info };
|
|
831
|
+
}
|
|
832
|
+
async function startServerForeground(port) {
|
|
833
|
+
const { running, info } = isServerRunning();
|
|
834
|
+
if (running && info) {
|
|
835
|
+
throw new MGError(
|
|
836
|
+
"E016" /* SERVER_ALREADY_RUNNING */,
|
|
837
|
+
`Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
ensureConfigDir();
|
|
841
|
+
const logger = createLogger({
|
|
842
|
+
console: true,
|
|
843
|
+
file: true,
|
|
844
|
+
minLevel: "INFO" /* INFO */
|
|
845
|
+
});
|
|
846
|
+
const server = createServer({
|
|
847
|
+
port: port || DEFAULT_PORT,
|
|
848
|
+
logger
|
|
849
|
+
});
|
|
850
|
+
const cleanup = async () => {
|
|
851
|
+
console.log("\n\u6B63\u5728\u505C\u6B62 Server...");
|
|
852
|
+
await server.stop();
|
|
853
|
+
deleteServerInfo();
|
|
854
|
+
process.exit(0);
|
|
855
|
+
};
|
|
856
|
+
process.on("SIGINT", cleanup);
|
|
857
|
+
process.on("SIGTERM", cleanup);
|
|
858
|
+
try {
|
|
859
|
+
const actualPort = await server.start();
|
|
860
|
+
writeServerInfo({
|
|
861
|
+
port: actualPort,
|
|
862
|
+
pid: process.pid,
|
|
863
|
+
startedAt: getCurrentISOTime()
|
|
864
|
+
});
|
|
865
|
+
console.log(`
|
|
866
|
+
MG Server \u542F\u52A8\u6210\u529F`);
|
|
867
|
+
console.log(`\u76D1\u542C\u7AEF\u53E3: ${actualPort}`);
|
|
868
|
+
console.log(`\u8FDB\u7A0B PID: ${process.pid}`);
|
|
869
|
+
console.log(`\u8FD0\u884C\u6A21\u5F0F: \u524D\u53F0`);
|
|
870
|
+
console.log(`
|
|
871
|
+
\u6309 Ctrl+C \u505C\u6B62...`);
|
|
872
|
+
} catch (error) {
|
|
873
|
+
logger.error("Server \u542F\u52A8\u5931\u8D25:", error);
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async function startServerDaemon(port) {
|
|
878
|
+
const { running, info } = isServerRunning();
|
|
879
|
+
if (running && info) {
|
|
880
|
+
throw new MGError(
|
|
881
|
+
"E016" /* SERVER_ALREADY_RUNNING */,
|
|
882
|
+
`Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
ensureConfigDir();
|
|
886
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
887
|
+
const currentDir = dirname3(currentFile);
|
|
888
|
+
const serverScript = join2(currentDir, "daemon-runner.js");
|
|
889
|
+
const args = ["--foreground"];
|
|
890
|
+
if (port) {
|
|
891
|
+
args.push("--port", String(port));
|
|
892
|
+
}
|
|
893
|
+
const child = spawn(process.execPath, [serverScript, ...args], {
|
|
894
|
+
detached: true,
|
|
895
|
+
stdio: "ignore",
|
|
896
|
+
env: {
|
|
897
|
+
...process.env,
|
|
898
|
+
MG_DAEMON: "1"
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
child.unref();
|
|
902
|
+
const startTime = Date.now();
|
|
903
|
+
while (Date.now() - startTime < SERVER_START_TIMEOUT) {
|
|
904
|
+
await new Promise((resolve7) => setTimeout(resolve7, 200));
|
|
905
|
+
const { running: running2, info: info2 } = isServerRunning();
|
|
906
|
+
if (running2 && info2) {
|
|
907
|
+
return info2;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
throw new MGError("E015" /* SERVER_START_FAILED */, "Server \u542F\u52A8\u8D85\u65F6");
|
|
911
|
+
}
|
|
912
|
+
function stopServer() {
|
|
913
|
+
const { running, info } = isServerRunning();
|
|
914
|
+
if (!running || !info) {
|
|
915
|
+
return { stopped: false, info: null };
|
|
916
|
+
}
|
|
917
|
+
const killed = killProcess(info.pid);
|
|
918
|
+
if (killed) {
|
|
919
|
+
deleteServerInfo();
|
|
920
|
+
}
|
|
921
|
+
return { stopped: killed, info };
|
|
922
|
+
}
|
|
923
|
+
async function restartServer(port) {
|
|
924
|
+
const { info: oldInfo } = stopServer();
|
|
925
|
+
await new Promise((resolve7) => setTimeout(resolve7, 500));
|
|
926
|
+
return startServerDaemon(port || oldInfo?.port);
|
|
927
|
+
}
|
|
928
|
+
function getServerStatus() {
|
|
929
|
+
const { running, info } = isServerRunning();
|
|
930
|
+
if (!running || !info) {
|
|
931
|
+
return { running: false };
|
|
932
|
+
}
|
|
933
|
+
const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
|
|
934
|
+
return {
|
|
935
|
+
running: true,
|
|
936
|
+
port: info.port,
|
|
937
|
+
pid: info.pid,
|
|
938
|
+
startedAt: info.startedAt,
|
|
939
|
+
uptime: formatDuration(uptimeMs)
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/cli/commands/server.ts
|
|
944
|
+
function createServerCommand() {
|
|
945
|
+
const serverCmd = new Command("server").description("Server \u7BA1\u7406\u547D\u4EE4");
|
|
946
|
+
serverCmd.command("start").description("\u542F\u52A8 MG Server").option("--port <number>", "\u6307\u5B9A\u542F\u52A8\u7AEF\u53E3", (value) => parseInt(value, 10)).option("--foreground", "\u524D\u53F0\u6A21\u5F0F\u8FD0\u884C\uFF08\u4E0D\u4F5C\u4E3A\u5B88\u62A4\u8FDB\u7A0B\uFF09", false).action(async (options) => {
|
|
947
|
+
try {
|
|
948
|
+
if (options.foreground) {
|
|
949
|
+
await startServerForeground(options.port);
|
|
950
|
+
} else {
|
|
951
|
+
const info = await startServerDaemon(options.port);
|
|
952
|
+
console.log("MG Server \u542F\u52A8\u6210\u529F");
|
|
953
|
+
console.log(`\u76D1\u542C\u7AEF\u53E3: ${info.port}`);
|
|
954
|
+
console.log(`\u8FDB\u7A0B PID: ${info.pid}`);
|
|
955
|
+
console.log(`\u8FD0\u884C\u6A21\u5F0F: \u5B88\u62A4\u8FDB\u7A0B`);
|
|
956
|
+
}
|
|
957
|
+
} catch (error) {
|
|
958
|
+
console.error(`\u9519\u8BEF: ${error.message}`);
|
|
959
|
+
process.exit(1);
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
serverCmd.command("stop").description("\u505C\u6B62 MG Server").action(() => {
|
|
963
|
+
try {
|
|
964
|
+
const { stopped, info } = stopServer();
|
|
965
|
+
if (stopped && info) {
|
|
966
|
+
console.log("MG Server \u5DF2\u505C\u6B62");
|
|
967
|
+
console.log(`PID: ${info.pid}`);
|
|
968
|
+
const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
|
|
969
|
+
const seconds = Math.floor(uptimeMs / 1e3);
|
|
970
|
+
const minutes = Math.floor(seconds / 60);
|
|
971
|
+
const hours = Math.floor(minutes / 60);
|
|
972
|
+
let uptime = "";
|
|
973
|
+
if (hours > 0) {
|
|
974
|
+
uptime = `${hours} \u5C0F\u65F6 ${minutes % 60} \u5206\u949F`;
|
|
975
|
+
} else if (minutes > 0) {
|
|
976
|
+
uptime = `${minutes} \u5206\u949F ${seconds % 60} \u79D2`;
|
|
977
|
+
} else {
|
|
978
|
+
uptime = `${seconds} \u79D2`;
|
|
979
|
+
}
|
|
980
|
+
console.log(`\u8FD0\u884C\u65F6\u957F: ${uptime}`);
|
|
981
|
+
} else {
|
|
982
|
+
console.log("MG Server \u672A\u8FD0\u884C");
|
|
983
|
+
}
|
|
984
|
+
} catch (error) {
|
|
985
|
+
console.error(`\u9519\u8BEF: ${error.message}`);
|
|
986
|
+
process.exit(1);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
serverCmd.command("restart").description("\u91CD\u542F MG Server").option("--port <number>", "\u91CD\u542F\u540E\u4F7F\u7528\u7684\u7AEF\u53E3", (value) => parseInt(value, 10)).action(async (options) => {
|
|
990
|
+
try {
|
|
991
|
+
const status = getServerStatus();
|
|
992
|
+
if (status.running) {
|
|
993
|
+
console.log(`\u6B63\u5728\u505C\u6B62 MG Server (PID: ${status.pid})...`);
|
|
994
|
+
}
|
|
995
|
+
const info = await restartServer(options.port);
|
|
996
|
+
console.log("MG Server \u5DF2\u91CD\u542F");
|
|
997
|
+
console.log(`\u76D1\u542C\u7AEF\u53E3: ${info.port}`);
|
|
998
|
+
console.log(`\u65B0\u8FDB\u7A0B PID: ${info.pid}`);
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
console.error(`\u9519\u8BEF: ${error.message}`);
|
|
1001
|
+
process.exit(1);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
serverCmd.command("status").description("\u67E5\u770B Server \u8FD0\u884C\u72B6\u6001").action(() => {
|
|
1005
|
+
try {
|
|
1006
|
+
const status = getServerStatus();
|
|
1007
|
+
if (status.running) {
|
|
1008
|
+
console.log("MG Server \u72B6\u6001: \u8FD0\u884C\u4E2D \u2713");
|
|
1009
|
+
console.log(`\u76D1\u542C\u7AEF\u53E3: ${status.port}`);
|
|
1010
|
+
console.log(`\u8FDB\u7A0B PID: ${status.pid}`);
|
|
1011
|
+
console.log(`\u542F\u52A8\u65F6\u95F4: ${status.startedAt}`);
|
|
1012
|
+
console.log(`\u8FD0\u884C\u65F6\u957F: ${status.uptime}`);
|
|
1013
|
+
} else {
|
|
1014
|
+
console.log("MG Server \u72B6\u6001: \u672A\u8FD0\u884C \u2717");
|
|
1015
|
+
console.log("\u63D0\u793A: \u4F7F\u7528 'mg-cli server start' \u542F\u52A8 Server");
|
|
1016
|
+
}
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
console.error(`\u9519\u8BEF: ${error.message}`);
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
return serverCmd;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/cli/commands/get-node-by-id.ts
|
|
1026
|
+
import { Command as Command2 } from "commander";
|
|
1027
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
1028
|
+
import { resolve as resolve2, dirname as dirname4 } from "path";
|
|
1029
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1030
|
+
|
|
1031
|
+
// src/cli/client.ts
|
|
1032
|
+
import WebSocket2 from "ws";
|
|
1033
|
+
var MGClient = class {
|
|
1034
|
+
ws = null;
|
|
1035
|
+
options;
|
|
1036
|
+
constructor(options = {}) {
|
|
1037
|
+
this.options = options;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* 连接到 Server
|
|
1041
|
+
*/
|
|
1042
|
+
async connect() {
|
|
1043
|
+
const serverInfo = readServerInfo();
|
|
1044
|
+
if (serverInfo) {
|
|
1045
|
+
try {
|
|
1046
|
+
await this.tryConnect(serverInfo.port);
|
|
1047
|
+
return;
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
1052
|
+
try {
|
|
1053
|
+
await this.tryConnect(port);
|
|
1054
|
+
return;
|
|
1055
|
+
} catch {
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (!this.options.noAutoStart) {
|
|
1059
|
+
console.log("Server \u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u81EA\u52A8\u542F\u52A8...");
|
|
1060
|
+
try {
|
|
1061
|
+
const info = await startServerDaemon();
|
|
1062
|
+
console.log(`Server \u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3: ${info.port}`);
|
|
1063
|
+
await this.waitForServer(info.port);
|
|
1064
|
+
return;
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
throw new MGError(
|
|
1067
|
+
"E015" /* SERVER_START_FAILED */,
|
|
1068
|
+
`\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25: ${error instanceof Error ? error.message : error}`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
throw new MGError("E001" /* CONNECTION_FAILED */, ErrorMessages["E001" /* CONNECTION_FAILED */]);
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* 尝试连接指定端口
|
|
1076
|
+
*/
|
|
1077
|
+
tryConnect(port) {
|
|
1078
|
+
return new Promise((resolve7, reject) => {
|
|
1079
|
+
const ws = new WebSocket2(`ws://localhost:${port}`);
|
|
1080
|
+
const timer = setTimeout(() => {
|
|
1081
|
+
ws.close();
|
|
1082
|
+
reject(new Error("\u8FDE\u63A5\u8D85\u65F6"));
|
|
1083
|
+
}, PORT_SCAN_TIMEOUT);
|
|
1084
|
+
ws.on("open", () => {
|
|
1085
|
+
clearTimeout(timer);
|
|
1086
|
+
this.ws = ws;
|
|
1087
|
+
this.register();
|
|
1088
|
+
resolve7();
|
|
1089
|
+
});
|
|
1090
|
+
ws.on("error", (error) => {
|
|
1091
|
+
clearTimeout(timer);
|
|
1092
|
+
reject(error);
|
|
1093
|
+
});
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* 等待 Server 就绪
|
|
1098
|
+
*/
|
|
1099
|
+
async waitForServer(port) {
|
|
1100
|
+
const startTime = Date.now();
|
|
1101
|
+
const interval = 500;
|
|
1102
|
+
while (Date.now() - startTime < SERVER_START_TIMEOUT) {
|
|
1103
|
+
try {
|
|
1104
|
+
await this.tryConnect(port);
|
|
1105
|
+
return;
|
|
1106
|
+
} catch {
|
|
1107
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
throw new Error("\u7B49\u5F85 Server \u542F\u52A8\u8D85\u65F6");
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* 注册为 Consumer
|
|
1114
|
+
*/
|
|
1115
|
+
register() {
|
|
1116
|
+
if (!this.ws) return;
|
|
1117
|
+
const message = {
|
|
1118
|
+
type: "register" /* REGISTER */,
|
|
1119
|
+
data: {
|
|
1120
|
+
connectionType: "consumer" /* CONSUMER */
|
|
1121
|
+
},
|
|
1122
|
+
timestamp: Date.now()
|
|
1123
|
+
};
|
|
1124
|
+
this.ws.send(JSON.stringify(message));
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* 发送请求并等待响应
|
|
1128
|
+
*/
|
|
1129
|
+
async request(type, params, pageUrl) {
|
|
1130
|
+
if (!this.ws) {
|
|
1131
|
+
throw new MGError("E001" /* CONNECTION_FAILED */, "\u672A\u8FDE\u63A5\u5230 Server");
|
|
1132
|
+
}
|
|
1133
|
+
const requestId = generateId();
|
|
1134
|
+
const message = {
|
|
1135
|
+
id: requestId,
|
|
1136
|
+
type,
|
|
1137
|
+
params,
|
|
1138
|
+
pageUrl,
|
|
1139
|
+
timestamp: Date.now()
|
|
1140
|
+
};
|
|
1141
|
+
return new Promise((resolve7, reject) => {
|
|
1142
|
+
const timer = setTimeout(() => {
|
|
1143
|
+
reject(new MGError("E012" /* REQUEST_TIMEOUT */, ErrorMessages["E012" /* REQUEST_TIMEOUT */]));
|
|
1144
|
+
}, REQUEST_TIMEOUT);
|
|
1145
|
+
const messageHandler = (data) => {
|
|
1146
|
+
try {
|
|
1147
|
+
const response = JSON.parse(data.toString());
|
|
1148
|
+
if (response.id === requestId) {
|
|
1149
|
+
clearTimeout(timer);
|
|
1150
|
+
this.ws?.off("message", messageHandler);
|
|
1151
|
+
if (response.success) {
|
|
1152
|
+
resolve7(response.data);
|
|
1153
|
+
} else {
|
|
1154
|
+
const error = response.error;
|
|
1155
|
+
reject(
|
|
1156
|
+
new MGError(
|
|
1157
|
+
error?.code || "E099" /* UNKNOWN_ERROR */,
|
|
1158
|
+
error?.message || "\u672A\u77E5\u9519\u8BEF"
|
|
1159
|
+
)
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
} catch {
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
this.ws.on("message", messageHandler);
|
|
1167
|
+
this.ws.send(JSON.stringify(message));
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* 带重试的请求
|
|
1172
|
+
*/
|
|
1173
|
+
async requestWithRetry(type, params, pageUrl) {
|
|
1174
|
+
if (this.options.noRetry) {
|
|
1175
|
+
return this.request(type, params, pageUrl);
|
|
1176
|
+
}
|
|
1177
|
+
let lastError = null;
|
|
1178
|
+
for (let attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++) {
|
|
1179
|
+
try {
|
|
1180
|
+
return await this.request(type, params, pageUrl);
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1183
|
+
if (error instanceof MGError) {
|
|
1184
|
+
const retryable = ["E017" /* CONNECTION_LOST */, "E012" /* REQUEST_TIMEOUT */];
|
|
1185
|
+
if (!retryable.includes(error.code)) {
|
|
1186
|
+
throw error;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (attempt < MAX_RETRY_COUNT) {
|
|
1190
|
+
const delay = RETRY_INTERVALS[attempt] || RETRY_INTERVALS[RETRY_INTERVALS.length - 1];
|
|
1191
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1192
|
+
try {
|
|
1193
|
+
await this.connect();
|
|
1194
|
+
} catch {
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
throw lastError || new MGError("E099" /* UNKNOWN_ERROR */, "\u8BF7\u6C42\u5931\u8D25");
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* 关闭连接
|
|
1203
|
+
*/
|
|
1204
|
+
close() {
|
|
1205
|
+
if (this.ws) {
|
|
1206
|
+
this.ws.close();
|
|
1207
|
+
this.ws = null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
// src/cli/commands/get-node-by-id.ts
|
|
1213
|
+
function createGetNodeByIdCommand() {
|
|
1214
|
+
return new Command2("get_node_by_id").description("\u6839\u636E\u8282\u70B9 ID \u83B7\u53D6\u8282\u70B9\u8BE6\u7EC6\u4FE1\u606F\u3002\u6570\u636E\u4FDD\u5B58\u5230\u6307\u5B9A JSON \u6587\u4EF6\uFF0C\u8FD4\u56DE\u6587\u4EF6\u8DEF\u5F84\u548C\u5927\u5C0F\u4FE1\u606F").requiredOption("--nodeId <id>", "\u8282\u70B9 ID\uFF0C\u683C\u5F0F\u5982 123:456\u3002\u53EF\u4ECE MasterGo \u6D6E\u7A97\u94FE\u63A5\u4E2D\u83B7\u53D6").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84\u3002\u652F\u6301\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84").option("--maxDepth <number>", "\u904D\u5386\u6DF1\u5EA6\uFF0C\u9ED8\u8BA4 1\u3002\u589E\u52A0\u6DF1\u5EA6\u4F1A\u663E\u8457\u589E\u52A0\u6570\u636E\u91CF", "1").option("--includeInvisible", "\u5305\u542B\u4E0D\u53EF\u89C1\u8282\u70B9\uFF08visible: false\uFF09\uFF0C\u9ED8\u8BA4\u4E0D\u5305\u542B", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
|
|
1215
|
+
await handleGetNodeById(options);
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
async function handleGetNodeById(options) {
|
|
1219
|
+
const client = new MGClient({
|
|
1220
|
+
noAutoStart: options.noAutoStart,
|
|
1221
|
+
noRetry: options.noRetry
|
|
1222
|
+
});
|
|
1223
|
+
try {
|
|
1224
|
+
await client.connect();
|
|
1225
|
+
const params = {
|
|
1226
|
+
nodeId: options.nodeId,
|
|
1227
|
+
maxDepth: parseInt(options.maxDepth || "1", 10),
|
|
1228
|
+
includeInvisible: options.includeInvisible || false
|
|
1229
|
+
};
|
|
1230
|
+
const data = await client.requestWithRetry("get_node_by_id" /* GET_NODE_BY_ID */, params);
|
|
1231
|
+
const outputPath = resolve2(options.output);
|
|
1232
|
+
const outputDir = dirname4(outputPath);
|
|
1233
|
+
mkdirSync3(outputDir, { recursive: true });
|
|
1234
|
+
const jsonContent = JSON.stringify(data, null, 2);
|
|
1235
|
+
writeFileSync2(outputPath, jsonContent, "utf-8");
|
|
1236
|
+
const size = jsonContent.length;
|
|
1237
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
1238
|
+
console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
|
|
1239
|
+
console.log(`\u8282\u70B9 ID: ${options.nodeId}`);
|
|
1240
|
+
console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
|
|
1241
|
+
console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
} finally {
|
|
1246
|
+
client.close();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/cli/commands/get-node-by-link.ts
|
|
1251
|
+
import { Command as Command3 } from "commander";
|
|
1252
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1253
|
+
import { resolve as resolve3, dirname as dirname5 } from "path";
|
|
1254
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
1255
|
+
function createGetNodeByLinkCommand() {
|
|
1256
|
+
return new Command3("get_node_by_link").description("\u89E3\u6790 mgp:// \u534F\u8BAE\u94FE\u63A5\u5E76\u83B7\u53D6\u8282\u70B9\u4FE1\u606F").requiredOption("--link <url>", "mgp:// \u534F\u8BAE\u94FE\u63A5").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84").option("--maxDepth <number>", "\u904D\u5386\u6DF1\u5EA6", "1").option("--includeInvisible", "\u5305\u542B\u4E0D\u53EF\u89C1\u8282\u70B9", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
|
|
1257
|
+
await handleGetNodeByLink(options);
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
async function handleGetNodeByLink(options) {
|
|
1261
|
+
const parsed = parseMgpLink(options.link);
|
|
1262
|
+
if (!parsed) {
|
|
1263
|
+
console.error(`\u9519\u8BEF [${"E010" /* INVALID_LINK */}]: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F`);
|
|
1264
|
+
console.error(`\u63D0\u4F9B\u7684\u94FE\u63A5: ${options.link}`);
|
|
1265
|
+
console.error(`\u671F\u671B\u683C\u5F0F: mgp://[mastergo_page_url]/nodeId`);
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
const { pageUrl, nodeId } = parsed;
|
|
1269
|
+
const client = new MGClient({
|
|
1270
|
+
noAutoStart: options.noAutoStart,
|
|
1271
|
+
noRetry: options.noRetry
|
|
1272
|
+
});
|
|
1273
|
+
try {
|
|
1274
|
+
await client.connect();
|
|
1275
|
+
const params = {
|
|
1276
|
+
nodeId,
|
|
1277
|
+
maxDepth: parseInt(options.maxDepth || "1", 10),
|
|
1278
|
+
includeInvisible: options.includeInvisible || false
|
|
1279
|
+
};
|
|
1280
|
+
const data = await client.requestWithRetry(
|
|
1281
|
+
"get_node_by_id" /* GET_NODE_BY_ID */,
|
|
1282
|
+
params,
|
|
1283
|
+
pageUrl
|
|
1284
|
+
);
|
|
1285
|
+
const outputPath = resolve3(options.output);
|
|
1286
|
+
const outputDir = dirname5(outputPath);
|
|
1287
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
1288
|
+
const jsonContent = JSON.stringify(data, null, 2);
|
|
1289
|
+
writeFileSync3(outputPath, jsonContent, "utf-8");
|
|
1290
|
+
const size = jsonContent.length;
|
|
1291
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
1292
|
+
console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
|
|
1293
|
+
console.log(`Link: ${options.link}`);
|
|
1294
|
+
console.log(`\u9875\u9762 URL: ${pageUrl}`);
|
|
1295
|
+
console.log(`\u8282\u70B9 ID: ${nodeId}`);
|
|
1296
|
+
console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
|
|
1297
|
+
console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
if (error instanceof MGError) {
|
|
1300
|
+
console.error(`\u9519\u8BEF [${error.code}]: ${error.message}`);
|
|
1301
|
+
} else {
|
|
1302
|
+
console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
|
|
1303
|
+
}
|
|
1304
|
+
process.exit(1);
|
|
1305
|
+
} finally {
|
|
1306
|
+
client.close();
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/cli/commands/get-all-nodes.ts
|
|
1311
|
+
import { Command as Command4 } from "commander";
|
|
1312
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1313
|
+
import { resolve as resolve4, dirname as dirname6 } from "path";
|
|
1314
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
1315
|
+
function createGetAllNodesCommand() {
|
|
1316
|
+
return new Command4("get_all_nodes").description("\u83B7\u53D6\u5F53\u524D\u9875\u9762\u7684\u6240\u6709\u8282\u70B9\u6811\u3002\u8B66\u544A\uFF1A\u6DF1\u5EA6\u6BCF\u589E\u52A0 1\uFF0C\u6570\u636E\u91CF\u53EF\u80FD\u5448\u6307\u6570\u7EA7\u589E\u957F\u3002\u5EFA\u8BAE\u4ECE maxDepth=1 \u5F00\u59CB").requiredOption("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84\u3002\u652F\u6301\u7EDD\u5BF9\u8DEF\u5F84\u6216\u76F8\u5BF9\u8DEF\u5F84").option("--maxDepth <number>", "\u6700\u5927\u6DF1\u5EA6\uFF0C\u9ED8\u8BA4 1\u3002\u6DF1\u5EA6 2 \u53EF\u80FD\u4EA7\u751F 100KB-500KB\uFF0C\u6DF1\u5EA6 3 \u53EF\u80FD\u8D85\u8FC7 1MB", "1").option("--includeInvisible", "\u5305\u542B\u4E0D\u53EF\u89C1\u8282\u70B9\uFF08visible: false\uFF09\uFF0C\u9ED8\u8BA4\u4E0D\u5305\u542B", false).option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
|
|
1317
|
+
await handleGetAllNodes(options);
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
async function handleGetAllNodes(options) {
|
|
1321
|
+
const client = new MGClient({
|
|
1322
|
+
noAutoStart: options.noAutoStart,
|
|
1323
|
+
noRetry: options.noRetry
|
|
1324
|
+
});
|
|
1325
|
+
try {
|
|
1326
|
+
await client.connect();
|
|
1327
|
+
const params = {
|
|
1328
|
+
maxDepth: parseInt(options.maxDepth || "1", 10),
|
|
1329
|
+
includeInvisible: options.includeInvisible || false
|
|
1330
|
+
};
|
|
1331
|
+
const data = await client.requestWithRetry("get_all_nodes" /* GET_ALL_NODES */, params);
|
|
1332
|
+
const outputPath = resolve4(options.output);
|
|
1333
|
+
const outputDir = dirname6(outputPath);
|
|
1334
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
1335
|
+
const jsonContent = JSON.stringify(data, null, 2);
|
|
1336
|
+
writeFileSync4(outputPath, jsonContent, "utf-8");
|
|
1337
|
+
const size = jsonContent.length;
|
|
1338
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
1339
|
+
const nodeCount = Array.isArray(data) ? data.length : 1;
|
|
1340
|
+
console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
|
|
1341
|
+
console.log(`\u8282\u70B9\u6570\u91CF: ${nodeCount}`);
|
|
1342
|
+
console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
|
|
1343
|
+
console.log(`\u8282\u70B9\u6DF1\u5EA6: ${params.maxDepth}`);
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
|
|
1346
|
+
process.exit(1);
|
|
1347
|
+
} finally {
|
|
1348
|
+
client.close();
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// src/cli/commands/export-image.ts
|
|
1353
|
+
import { Command as Command5 } from "commander";
|
|
1354
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
1355
|
+
import { resolve as resolve5, dirname as dirname7, extname } from "path";
|
|
1356
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
1357
|
+
import { tmpdir } from "os";
|
|
1358
|
+
function createExportImageCommand() {
|
|
1359
|
+
return new Command5("export_image").description("\u5BFC\u51FA MasterGo \u8282\u70B9\u4E3A\u56FE\u7247\u6587\u4EF6\u3002\u5F3A\u70C8\u5EFA\u8BAE\u6307\u5B9A --output\uFF0C\u5426\u5219\u4FDD\u5B58\u5230\u4E34\u65F6\u76EE\u5F55\u53EF\u80FD\u88AB\u7CFB\u7EDF\u6E05\u7406").option("--output <path>", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\u3002\u5F3A\u70C8\u5EFA\u8BAE\u6307\u5B9A\uFF0C\u5426\u5219\u4FDD\u5B58\u5230\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55\u53EF\u80FD\u88AB\u6E05\u7406").option("--link <mgp-link>", "mgp:// \u534F\u8BAE\u94FE\u63A5\u3002\u4E0D\u6307\u5B9A\u5219\u5BFC\u51FA\u5F53\u524D\u9009\u4E2D\u8282\u70B9").option("--format <type>", "\u5BFC\u51FA\u683C\u5F0F\uFF1APNG\uFF08\u65E0\u635F\u900F\u660E\uFF09\u3001JPG\uFF08\u6709\u635F\uFF09\u3001SVG\uFF08\u77E2\u91CF\uFF09\u3001PDF\u3001WEBP", "PNG").option("--scale <number>", "\u7F29\u653E\u500D\u7387\uFF08\u5982 1\u30012\u30013\uFF09\u3002\u4E0E width/height \u4E92\u65A5").option("--width <number>", "\u56FA\u5B9A\u5BBD\u5EA6\uFF08\u50CF\u7D20\uFF09\u3002\u4E0E scale/height \u4E92\u65A5").option("--height <number>", "\u56FA\u5B9A\u9AD8\u5EA6\uFF08\u50CF\u7D20\uFF09\u3002\u4E0E scale/width \u4E92\u65A5").option("--useAbsoluteBounds", "\u4F7F\u7528\u5B8C\u6574\u5C3A\u5BF8\u3002true: \u5305\u542B\u88AB\u88C1\u526A\u90E8\u5206\uFF0Cfalse: \u53EA\u5BFC\u51FA\u53EF\u89C1\u533A\u57DF", false).option("--no-use-render-bounds", "\u4E0D\u5305\u542B\u7279\u6548\u548C\u5916\u63CF\u8FB9\u3002\u9ED8\u8BA4\u5305\u542B\u9634\u5F71\u3001\u5916\u63CF\u8FB9\u7B49").option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
|
|
1360
|
+
await handleExportImage(options);
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
async function handleExportImage(options) {
|
|
1364
|
+
const format = options.format?.toUpperCase() || "PNG";
|
|
1365
|
+
const validFormats = ["PNG", "JPG", "SVG", "PDF", "WEBP"];
|
|
1366
|
+
if (!validFormats.includes(format)) {
|
|
1367
|
+
console.error(`\u9519\u8BEF: \u4E0D\u652F\u6301\u7684\u683C\u5F0F "${options.format}"`);
|
|
1368
|
+
console.error(`\u652F\u6301\u7684\u683C\u5F0F: ${validFormats.join(", ")}`);
|
|
1369
|
+
process.exit(1);
|
|
1370
|
+
}
|
|
1371
|
+
const sizeParams = [options.scale, options.width, options.height].filter(Boolean);
|
|
1372
|
+
if (sizeParams.length > 1) {
|
|
1373
|
+
console.error("\u9519\u8BEF: scale\u3001width\u3001height \u4E09\u8005\u4E92\u65A5\uFF0C\u53EA\u80FD\u6307\u5B9A\u5176\u4E2D\u4E00\u4E2A");
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
let pageUrl;
|
|
1377
|
+
let nodeId;
|
|
1378
|
+
if (options.link) {
|
|
1379
|
+
const linkInfo = parseMgpLink(options.link);
|
|
1380
|
+
if (!linkInfo) {
|
|
1381
|
+
console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F: ${options.link}`);
|
|
1382
|
+
process.exit(1);
|
|
1383
|
+
}
|
|
1384
|
+
pageUrl = linkInfo.pageUrl;
|
|
1385
|
+
nodeId = linkInfo.nodeId;
|
|
1386
|
+
}
|
|
1387
|
+
const client = new MGClient({
|
|
1388
|
+
noAutoStart: options.noAutoStart,
|
|
1389
|
+
noRetry: options.noRetry
|
|
1390
|
+
});
|
|
1391
|
+
try {
|
|
1392
|
+
await client.connect();
|
|
1393
|
+
const params = {
|
|
1394
|
+
format,
|
|
1395
|
+
useAbsoluteBounds: options.useAbsoluteBounds || false,
|
|
1396
|
+
useRenderBounds: options.useRenderBounds !== false
|
|
1397
|
+
};
|
|
1398
|
+
if (nodeId) {
|
|
1399
|
+
params.nodeId = nodeId;
|
|
1400
|
+
}
|
|
1401
|
+
if (options.scale) {
|
|
1402
|
+
params.scale = parseFloat(options.scale);
|
|
1403
|
+
}
|
|
1404
|
+
if (options.width) {
|
|
1405
|
+
params.width = parseInt(options.width, 10);
|
|
1406
|
+
}
|
|
1407
|
+
if (options.height) {
|
|
1408
|
+
params.height = parseInt(options.height, 10);
|
|
1409
|
+
}
|
|
1410
|
+
const response = await client.requestWithRetry(
|
|
1411
|
+
"export_image" /* EXPORT_IMAGE */,
|
|
1412
|
+
params,
|
|
1413
|
+
pageUrl
|
|
1414
|
+
);
|
|
1415
|
+
const ext = getExtension(format);
|
|
1416
|
+
let outputPath;
|
|
1417
|
+
if (options.output) {
|
|
1418
|
+
outputPath = resolve5(options.output);
|
|
1419
|
+
if (!extname(outputPath)) {
|
|
1420
|
+
outputPath = `${outputPath}${ext}`;
|
|
1421
|
+
}
|
|
1422
|
+
} else {
|
|
1423
|
+
const filename = response.filename || `export_${Date.now()}${ext}`;
|
|
1424
|
+
outputPath = resolve5(tmpdir(), filename);
|
|
1425
|
+
console.log("\u8B66\u544A: \u672A\u6307\u5B9A --output\uFF0C\u6587\u4EF6\u5C06\u4FDD\u5B58\u5230\u4E34\u65F6\u76EE\u5F55\uFF0C\u53EF\u80FD\u4F1A\u88AB\u7CFB\u7EDF\u6E05\u7406");
|
|
1426
|
+
}
|
|
1427
|
+
const outputDir = dirname7(outputPath);
|
|
1428
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
1429
|
+
const buffer = Buffer.from(response.data, "base64");
|
|
1430
|
+
writeFileSync5(outputPath, buffer);
|
|
1431
|
+
const sizeKB = (buffer.length / 1024).toFixed(2);
|
|
1432
|
+
console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
|
|
1433
|
+
if (options.link) {
|
|
1434
|
+
console.log(`Link: ${options.link}`);
|
|
1435
|
+
}
|
|
1436
|
+
if (nodeId) {
|
|
1437
|
+
console.log(`\u8282\u70B9 ID: ${nodeId}`);
|
|
1438
|
+
} else {
|
|
1439
|
+
console.log("\u8282\u70B9 ID: (\u9009\u4E2D\u7684\u8282\u70B9)");
|
|
1440
|
+
}
|
|
1441
|
+
console.log(`\u5BFC\u51FA\u683C\u5F0F: ${format}`);
|
|
1442
|
+
console.log(`\u6587\u4EF6\u5927\u5C0F: ${buffer.length.toLocaleString()} \u5B57\u8282 (\u7EA6 ${sizeKB} KB)`);
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
if (error instanceof MGError) {
|
|
1445
|
+
console.error(`\u9519\u8BEF [${error.code}]: ${error.message}`);
|
|
1446
|
+
} else {
|
|
1447
|
+
console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
|
|
1448
|
+
}
|
|
1449
|
+
process.exit(1);
|
|
1450
|
+
} finally {
|
|
1451
|
+
client.close();
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function getExtension(format) {
|
|
1455
|
+
const extensions = {
|
|
1456
|
+
PNG: ".png",
|
|
1457
|
+
JPG: ".jpg",
|
|
1458
|
+
SVG: ".svg",
|
|
1459
|
+
PDF: ".pdf",
|
|
1460
|
+
WEBP: ".webp"
|
|
1461
|
+
};
|
|
1462
|
+
return extensions[format];
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// src/cli/commands/execute-code.ts
|
|
1466
|
+
import { Command as Command6 } from "commander";
|
|
1467
|
+
function createExecuteCodeCommand() {
|
|
1468
|
+
return new Command6("execute_code").description("\u5728 MasterGo \u9875\u9762\u6267\u884C\u81EA\u5B9A\u4E49 JavaScript \u4EE3\u7801\u3002\u901A\u8FC7 mg \u53D8\u91CF\u8BBF\u95EE MasterGo API\uFF0C\u7ED3\u679C\u4F1A\u88AB JSON \u5E8F\u5217\u5316\u8FD4\u56DE").argument("<code>", "\u8981\u6267\u884C\u7684\u4EE3\u7801\u3002\u53EF\u4F7F\u7528 mg \u53D8\u91CF\uFF0C\u5982 mg.currentPage.name\u3001mg.currentPage.selection").option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (code, options) => {
|
|
1469
|
+
await handleExecuteCode(code, options);
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
async function handleExecuteCode(code, options) {
|
|
1473
|
+
const client = new MGClient({
|
|
1474
|
+
noAutoStart: options.noAutoStart,
|
|
1475
|
+
noRetry: options.noRetry
|
|
1476
|
+
});
|
|
1477
|
+
try {
|
|
1478
|
+
await client.connect();
|
|
1479
|
+
const result = await client.requestWithRetry("execute_code" /* EXECUTE_CODE */, { code });
|
|
1480
|
+
if (result === null || result === void 0) {
|
|
1481
|
+
console.log("\u6267\u884C\u5B8C\u6210\uFF08\u65E0\u8FD4\u56DE\u503C\uFF09");
|
|
1482
|
+
} else if (typeof result === "object") {
|
|
1483
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1484
|
+
} else {
|
|
1485
|
+
console.log(result);
|
|
1486
|
+
}
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
|
|
1489
|
+
process.exit(1);
|
|
1490
|
+
} finally {
|
|
1491
|
+
client.close();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/cli/commands/get-all-pages.ts
|
|
1496
|
+
import { Command as Command7 } from "commander";
|
|
1497
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1498
|
+
import { resolve as resolve6, dirname as dirname8 } from "path";
|
|
1499
|
+
import { mkdirSync as mkdirSync7 } from "fs";
|
|
1500
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1501
|
+
function createGetAllPagesCommand() {
|
|
1502
|
+
return new Command7("get_all_pages").description("\u83B7\u53D6 MasterGo \u6587\u6863\u7684\u6240\u6709\u9875\u9762\u4FE1\u606F\u3002\u4E0D\u6307\u5B9A --output \u65F6\u4FDD\u5B58\u5230\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55").option("--link <url>", "\u9875\u9762\u94FE\u63A5\u3002\u652F\u6301\u5B8C\u6574 URL \u6216 mgp:// \u534F\u8BAE").option("--fileId <id>", "\u6587\u4EF6 ID\uFF08\u7EAF\u6570\u5B57\uFF09\u3002\u4ECE URL \u4E2D /file/ \u540E\u9762\u7684\u6570\u5B57").option("--output <path>", "\u8F93\u51FA JSON \u6587\u4EF6\u8DEF\u5F84\u3002\u4E0D\u6307\u5B9A\u5219\u4FDD\u5B58\u5230\u7CFB\u7EDF\u4E34\u65F6\u76EE\u5F55").option("--no-auto-start", "\u7981\u7528\u81EA\u52A8\u542F\u52A8 Server").option("--no-retry", "\u7981\u7528\u81EA\u52A8\u91CD\u8BD5").action(async (options) => {
|
|
1503
|
+
await handleGetAllPages(options);
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
async function handleGetAllPages(options) {
|
|
1507
|
+
let fileId = null;
|
|
1508
|
+
if (options.fileId) {
|
|
1509
|
+
fileId = options.fileId;
|
|
1510
|
+
} else if (options.link) {
|
|
1511
|
+
fileId = extractFileId(options.link);
|
|
1512
|
+
if (!fileId) {
|
|
1513
|
+
console.error("\u9519\u8BEF: \u65E0\u6CD5\u4ECE\u94FE\u63A5\u4E2D\u63D0\u53D6 fileId");
|
|
1514
|
+
console.error("\u652F\u6301\u7684\u683C\u5F0F:");
|
|
1515
|
+
console.error(" - \u5B8C\u6574 URL: https://mastergo.netease.com/file/174875497054651");
|
|
1516
|
+
console.error(" - mgp \u534F\u8BAE: mgp://mastergo.netease.com/file/174875497054651");
|
|
1517
|
+
console.error(" - \u7EAF fileId: 174875497054651");
|
|
1518
|
+
process.exit(1);
|
|
1519
|
+
}
|
|
1520
|
+
} else {
|
|
1521
|
+
console.log("\u672A\u63D0\u4F9B --link \u6216 --fileId\uFF0C\u5C06\u83B7\u53D6\u5F53\u524D\u8FDE\u63A5\u9875\u9762\u7684\u6240\u6709\u9875\u9762\u4FE1\u606F");
|
|
1522
|
+
}
|
|
1523
|
+
const client = new MGClient({
|
|
1524
|
+
noAutoStart: options.noAutoStart,
|
|
1525
|
+
noRetry: options.noRetry
|
|
1526
|
+
});
|
|
1527
|
+
try {
|
|
1528
|
+
await client.connect();
|
|
1529
|
+
let pageUrl;
|
|
1530
|
+
if (fileId) {
|
|
1531
|
+
pageUrl = `mastergo.netease.com/file/${fileId}`;
|
|
1532
|
+
}
|
|
1533
|
+
const data = await client.requestWithRetry(
|
|
1534
|
+
"get_all_pages" /* GET_ALL_PAGES */,
|
|
1535
|
+
{},
|
|
1536
|
+
pageUrl
|
|
1537
|
+
);
|
|
1538
|
+
let outputPath;
|
|
1539
|
+
if (options.output) {
|
|
1540
|
+
outputPath = resolve6(options.output);
|
|
1541
|
+
} else {
|
|
1542
|
+
const filename = `pages_${fileId || "current"}_${Date.now()}.json`;
|
|
1543
|
+
outputPath = resolve6(tmpdir2(), filename);
|
|
1544
|
+
}
|
|
1545
|
+
const outputDir = dirname8(outputPath);
|
|
1546
|
+
mkdirSync7(outputDir, { recursive: true });
|
|
1547
|
+
const jsonContent = JSON.stringify(data, null, 2);
|
|
1548
|
+
writeFileSync6(outputPath, jsonContent, "utf-8");
|
|
1549
|
+
const size = jsonContent.length;
|
|
1550
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
1551
|
+
console.log(`\u6587\u4EF6\u8DEF\u5F84: ${outputPath}`);
|
|
1552
|
+
console.log(`\u6587\u6863\u540D\u79F0: ${data.documentName}`);
|
|
1553
|
+
console.log(`\u9875\u9762\u6570\u91CF: ${data.totalCount}`);
|
|
1554
|
+
console.log(`\u6570\u636E\u5927\u5C0F: ${size.toLocaleString()} \u5B57\u7B26 (\u7EA6 ${sizeKB} KB)`);
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
console.error(`\u9519\u8BEF: ${error instanceof Error ? error.message : error}`);
|
|
1557
|
+
process.exit(1);
|
|
1558
|
+
} finally {
|
|
1559
|
+
client.close();
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// src/cli/index.ts
|
|
1564
|
+
var program = new Command8();
|
|
1565
|
+
program.name("mg-cli").description("MasterGo CLI \u5DE5\u5177 - \u7528\u4E8E Claude Code \u4E0E MasterGo \u901A\u4FE1").version("1.0.0");
|
|
1566
|
+
program.addCommand(createServerCommand());
|
|
1567
|
+
program.addCommand(createGetNodeByIdCommand());
|
|
1568
|
+
program.addCommand(createGetNodeByLinkCommand());
|
|
1569
|
+
program.addCommand(createGetAllNodesCommand());
|
|
1570
|
+
program.addCommand(createExportImageCommand());
|
|
1571
|
+
program.addCommand(createExecuteCodeCommand());
|
|
1572
|
+
program.addCommand(createGetAllPagesCommand());
|
|
1573
|
+
program.parse();
|
|
1574
|
+
//# sourceMappingURL=cli.js.map
|