@hangox/mg-cli 1.0.2 → 1.0.5

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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as ServerInfo } from './index-DNrszrq9.js';
2
- export { A as AllPagesInfo, B as BaseMessage, C as CONFIG_DIR, x as CliOptions, u as ConnectionInfo, K as ConnectionManager, i as ConnectionType, D as DEFAULT_PORT, E as ErrorCode, q as ErrorInfo, l as ErrorMessages, k as ErrorNames, w as ExportImageParams, v as GetAllNodesParams, G as GetNodeParams, H as HEARTBEAT_INTERVAL, e as HEARTBEAT_TIMEOUT, L as LOG_DIR, W as LogLevel, U as Logger, X as LoggerOptions, M as MAX_PORT_ATTEMPTS, h as MAX_RETRY_COUNT, m as MGError, F as MGServer, Q as ManagedWebSocket, j as MessageType, z as MgPageInfo, N as NodeInfo, O as OutputFormatter, a as PORT_RANGE_END, P as PORT_RANGE_START, b as PORT_SCAN_TIMEOUT, s as PingMessage, t as PongMessage, R as REQUEST_TIMEOUT, g as RETRY_INTERVALS, y as RGBA, r as RegisterMessage, T as RequestHandler, o as RequestMessage, p as ResponseMessage, c as SERVER_INFO_FILE, d as SERVER_LOG_FILE, f as SERVER_START_TIMEOUT, J as ServerOptions, n as createError, V as createLogger, I as createServer } from './index-DNrszrq9.js';
1
+ import { S as ServerInfo, N as NodeInfo, a as SpaceNodeInfo } from './index--AigFaD9.js';
2
+ export { F as AllPagesInfo, B as BaseMessage, C as CONFIG_DIR, y as CliOptions, I as ConnectedPageInfo, v as ConnectionInfo, U as ConnectionManager, j as ConnectionType, D as DEFAULT_PORT, E as ErrorCode, r as ErrorInfo, m as ErrorMessages, l as ErrorNames, x as ExportImageParams, w as GetAllNodesParams, G as GetNodeParams, H as HEARTBEAT_INTERVAL, f as HEARTBEAT_TIMEOUT, L as LOG_DIR, Z as LogLevel, X as Logger, _ as LoggerOptions, M as MAX_PORT_ATTEMPTS, i as MAX_RETRY_COUNT, n as MGError, K as MGServer, V as ManagedWebSocket, k as MessageType, A as MgPageInfo, O as OutputFormatter, b as PORT_RANGE_END, P as PORT_RANGE_START, c as PORT_SCAN_TIMEOUT, t as PingMessage, u as PongMessage, R as REQUEST_TIMEOUT, h as RETRY_INTERVALS, z as RGBA, s as RegisterMessage, W as RequestHandler, p as RequestMessage, q as ResponseMessage, d as SERVER_INFO_FILE, e as SERVER_LOG_FILE, g as SERVER_START_TIMEOUT, T as ServerOptions, J as ServerStatusResponse, o as createError, Y as createLogger, Q as createServer } from './index--AigFaD9.js';
3
3
  import 'ws';
4
4
 
5
5
  /**
@@ -125,5 +125,32 @@ declare function extractFileIdFromMgpLink(link: string): string | null;
125
125
  * @returns fileId 或 null
126
126
  */
127
127
  declare function extractFileId(input: string): string | null;
128
+ /**
129
+ * 四舍五入到一位小数
130
+ * 25.666667938232422 -> 25.7
131
+ */
132
+ declare function roundToOneDecimal(value: number): number;
133
+ /**
134
+ * MasterGo 节点属性默认值
135
+ * 基于官方文档定义,当值等于默认值时可以省略以减少 JSON 体积
136
+ */
137
+ declare const NODE_DEFAULTS: Record<string, unknown>;
138
+ /**
139
+ * 精简节点数据,移除等于默认值的字段
140
+ * 递归处理 children 数组
141
+ *
142
+ * @param node 原始节点数据
143
+ * @returns 精简后的节点数据
144
+ */
145
+ declare function trimNodeDefaults<T extends Record<string, unknown>>(node: T): T;
146
+ /**
147
+ * 提取节点的空间信息
148
+ * 只保留 id、name、x、y、width、height 和 children 字段
149
+ * 用于让 AI 以最少的字段理解节点的空间布局关系
150
+ *
151
+ * @param node 原始节点数据
152
+ * @returns 精简的空间节点信息
153
+ */
154
+ declare function extractSpaceInfo(node: NodeInfo): SpaceNodeInfo;
128
155
 
129
- export { ServerInfo, deleteServerInfo, ensureConfigDir, ensureDir, ensureOutputDir, extractFileId, extractFileIdFromMgpLink, extractFileIdFromUrl, formatDuration, formatFileSize, formatLogTime, generateId, generateMgpLink, getCurrentISOTime, isDesignPageUrl, isProcessRunning, killProcess, normalizePageUrl, parseMgpLink, readServerInfo, resolveOutputPath, writeServerInfo };
156
+ export { NODE_DEFAULTS, NodeInfo, ServerInfo, SpaceNodeInfo, deleteServerInfo, ensureConfigDir, ensureDir, ensureOutputDir, extractFileId, extractFileIdFromMgpLink, extractFileIdFromUrl, extractSpaceInfo, formatDuration, formatFileSize, formatLogTime, generateId, generateMgpLink, getCurrentISOTime, isDesignPageUrl, isProcessRunning, killProcess, normalizePageUrl, parseMgpLink, readServerInfo, resolveOutputPath, roundToOneDecimal, trimNodeDefaults, writeServerInfo };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
32
32
  MessageType2["EXPORT_IMAGE"] = "export_image";
33
33
  MessageType2["EXECUTE_CODE"] = "execute_code";
34
34
  MessageType2["GET_ALL_PAGES"] = "get_all_pages";
35
+ MessageType2["GET_SERVER_STATUS"] = "get_server_status";
35
36
  MessageType2["RESPONSE"] = "response";
36
37
  MessageType2["ERROR"] = "error";
37
38
  return MessageType2;
@@ -317,6 +318,152 @@ function extractFileId(input) {
317
318
  }
318
319
  return extractFileIdFromUrl(trimmed);
319
320
  }
321
+ var NUMERIC_FIELDS = [
322
+ "x",
323
+ "y",
324
+ "width",
325
+ "height",
326
+ "rotation",
327
+ "opacity",
328
+ "cornerRadius",
329
+ "topLeftRadius",
330
+ "topRightRadius",
331
+ "bottomLeftRadius",
332
+ "bottomRightRadius",
333
+ "strokeWeight",
334
+ "strokeTopWeight",
335
+ "strokeLeftWeight",
336
+ "strokeBottomWeight",
337
+ "strokeRightWeight",
338
+ "paddingTop",
339
+ "paddingRight",
340
+ "paddingBottom",
341
+ "paddingLeft",
342
+ "itemSpacing",
343
+ "crossAxisSpacing",
344
+ "flexGrow"
345
+ ];
346
+ function roundToOneDecimal(value) {
347
+ return Math.round(value * 10) / 10;
348
+ }
349
+ var NODE_DEFAULTS = {
350
+ // Scene Node 属性
351
+ visible: true,
352
+ isVisible: true,
353
+ isLocked: false,
354
+ // Blend 属性
355
+ opacity: 1,
356
+ blendMode: "NORMAL",
357
+ isMask: false,
358
+ isMaskOutline: false,
359
+ isMaskVisible: false,
360
+ effectStyleId: "",
361
+ // Geometry 属性
362
+ strokeStyle: "SOLID",
363
+ strokeWeight: 0,
364
+ strokeTopWeight: 0,
365
+ strokeLeftWeight: 0,
366
+ strokeBottomWeight: 0,
367
+ strokeRightWeight: 0,
368
+ strokeAlign: "CENTER",
369
+ strokeCap: "NONE",
370
+ strokeJoin: "MITER",
371
+ dashCap: "NONE",
372
+ fillStyleId: "",
373
+ strokeStyleId: "",
374
+ // Corner 属性
375
+ cornerSmooth: 0,
376
+ cornerRadius: 0,
377
+ topLeftRadius: 0,
378
+ topRightRadius: 0,
379
+ bottomLeftRadius: 0,
380
+ bottomRightRadius: 0,
381
+ // Layout 属性
382
+ rotation: 0,
383
+ flexGrow: 0,
384
+ alignSelf: "INHERIT",
385
+ layoutPositioning: "AUTO",
386
+ constrainProportions: false,
387
+ // 容器专属属性 (FrameNode)
388
+ flexMode: "NONE",
389
+ flexWrap: "NO_WRAP",
390
+ itemSpacing: 0,
391
+ crossAxisSpacing: 0,
392
+ paddingTop: 0,
393
+ paddingRight: 0,
394
+ paddingBottom: 0,
395
+ paddingLeft: 0,
396
+ clipsContent: false,
397
+ itemReverseZIndex: false,
398
+ strokesIncludedInLayout: false,
399
+ // 其他属性
400
+ componentPropertyReferences: null
401
+ };
402
+ var EMPTY_ARRAY_FIELDS = [
403
+ "fills",
404
+ "strokes",
405
+ "effects",
406
+ "strokeDashes",
407
+ "exportSettings",
408
+ "reactions",
409
+ "attachedConnectors"
410
+ ];
411
+ function isEmptyArray(value) {
412
+ return Array.isArray(value) && value.length === 0;
413
+ }
414
+ function isEqual(a, b) {
415
+ if (a === b) return true;
416
+ if (a === null || b === null) return a === b;
417
+ if (typeof a !== typeof b) return false;
418
+ return false;
419
+ }
420
+ function trimNodeDefaults(node) {
421
+ const result = {};
422
+ for (const [key, value] of Object.entries(node)) {
423
+ if (key === "children" && Array.isArray(value)) {
424
+ const trimmedChildren = value.map(
425
+ (child) => typeof child === "object" && child !== null ? trimNodeDefaults(child) : child
426
+ );
427
+ if (trimmedChildren.length > 0) {
428
+ result[key] = trimmedChildren;
429
+ }
430
+ continue;
431
+ }
432
+ if (EMPTY_ARRAY_FIELDS.includes(key) && isEmptyArray(value)) {
433
+ continue;
434
+ }
435
+ if (NUMERIC_FIELDS.includes(key) && typeof value === "number") {
436
+ const roundedValue = roundToOneDecimal(value);
437
+ if (key in NODE_DEFAULTS && isEqual(roundedValue, NODE_DEFAULTS[key])) {
438
+ continue;
439
+ }
440
+ result[key] = roundedValue;
441
+ continue;
442
+ }
443
+ if (key in NODE_DEFAULTS) {
444
+ const defaultValue = NODE_DEFAULTS[key];
445
+ if (isEqual(value, defaultValue)) {
446
+ continue;
447
+ }
448
+ }
449
+ result[key] = value;
450
+ }
451
+ return result;
452
+ }
453
+ function extractSpaceInfo(node) {
454
+ const result = {
455
+ id: node.id,
456
+ name: node.name,
457
+ x: roundToOneDecimal(typeof node.x === "number" ? node.x : 0),
458
+ y: roundToOneDecimal(typeof node.y === "number" ? node.y : 0),
459
+ width: roundToOneDecimal(typeof node.width === "number" ? node.width : 0),
460
+ height: roundToOneDecimal(typeof node.height === "number" ? node.height : 0)
461
+ };
462
+ if (node.children && Array.isArray(node.children) && node.children.length > 0) {
463
+ result.children = node.children.map((child) => extractSpaceInfo(child));
464
+ }
465
+ return result;
466
+ }
320
467
 
321
468
  // src/server/websocket-server.ts
322
469
  import { WebSocketServer } from "ws";
@@ -687,6 +834,54 @@ var RequestHandler = class {
687
834
  }
688
835
  };
689
836
 
837
+ // src/shared/version.ts
838
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
839
+ import { fileURLToPath } from "url";
840
+ import { dirname as dirname3, join as join2 } from "path";
841
+ var cachedVersion = null;
842
+ function getVersion() {
843
+ if (cachedVersion !== null) {
844
+ return cachedVersion;
845
+ }
846
+ try {
847
+ const currentFile = fileURLToPath(import.meta.url);
848
+ const currentDir = dirname3(currentFile);
849
+ const versionFilePaths = [
850
+ join2(currentDir, "..", "VERSION"),
851
+ // dist/xxx.js -> ../VERSION
852
+ join2(currentDir, "..", "..", "VERSION")
853
+ // src/shared/version.ts -> ../../VERSION
854
+ ];
855
+ for (const versionFilePath of versionFilePaths) {
856
+ if (existsSync3(versionFilePath)) {
857
+ const version = readFileSync2(versionFilePath, "utf-8").trim();
858
+ if (version) {
859
+ cachedVersion = version;
860
+ return cachedVersion;
861
+ }
862
+ }
863
+ }
864
+ const packageJsonPaths = [
865
+ join2(currentDir, "..", "package.json"),
866
+ join2(currentDir, "..", "..", "package.json")
867
+ ];
868
+ for (const packageJsonPath of packageJsonPaths) {
869
+ if (existsSync3(packageJsonPath)) {
870
+ const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
871
+ if (packageJson.name === "@hangox/mg-cli") {
872
+ cachedVersion = packageJson.version || "0.0.0";
873
+ return cachedVersion;
874
+ }
875
+ }
876
+ }
877
+ cachedVersion = "0.0.0";
878
+ return cachedVersion;
879
+ } catch {
880
+ cachedVersion = "0.0.0";
881
+ return cachedVersion;
882
+ }
883
+ }
884
+
690
885
  // src/server/websocket-server.ts
691
886
  var MGServer = class {
692
887
  wss = null;
@@ -695,6 +890,7 @@ var MGServer = class {
695
890
  requestHandler;
696
891
  port;
697
892
  isRunning = false;
893
+ startedAt = null;
698
894
  constructor(options = {}) {
699
895
  this.port = options.port || DEFAULT_PORT;
700
896
  this.logger = options.logger || createLogger();
@@ -714,6 +910,7 @@ var MGServer = class {
714
910
  this.wss.on("listening", () => {
715
911
  this.port = port;
716
912
  this.isRunning = true;
913
+ this.startedAt = /* @__PURE__ */ new Date();
717
914
  this.logger.info(`Server \u542F\u52A8\u6210\u529F\uFF0C\u76D1\u542C\u7AEF\u53E3: ${port}`);
718
915
  this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL);
719
916
  resolve2(port);
@@ -814,7 +1011,8 @@ var MGServer = class {
814
1011
  success: true,
815
1012
  data: {
816
1013
  connectionId: managedWs.connectionId,
817
- pageUrl
1014
+ pageUrl,
1015
+ serverVersion: getVersion()
818
1016
  }
819
1017
  };
820
1018
  ws.send(JSON.stringify(ack));
@@ -828,6 +1026,9 @@ var MGServer = class {
828
1026
  case "ping" /* PING */:
829
1027
  this.handlePing(ws, message);
830
1028
  break;
1029
+ case "get_server_status" /* GET_SERVER_STATUS */:
1030
+ this.handleGetServerStatus(ws, message);
1031
+ break;
831
1032
  case "response" /* RESPONSE */:
832
1033
  case "error" /* ERROR */:
833
1034
  this.requestHandler.handleResponse(message);
@@ -849,6 +1050,37 @@ var MGServer = class {
849
1050
  };
850
1051
  ws.send(JSON.stringify(pong));
851
1052
  }
1053
+ /**
1054
+ * 处理 Server 状态查询
1055
+ */
1056
+ handleGetServerStatus(ws, message) {
1057
+ const providers = this.connectionManager.getAllProviders();
1058
+ const stats = this.connectionManager.getStats();
1059
+ const connectedPages = providers.map((info) => ({
1060
+ pageUrl: info.pageUrl || "",
1061
+ connectedAt: info.connectedAt.toISOString(),
1062
+ lastActiveAt: info.lastActiveAt.toISOString()
1063
+ }));
1064
+ const uptimeMs = this.startedAt ? Date.now() - this.startedAt.getTime() : 0;
1065
+ const statusData = {
1066
+ running: this.isRunning,
1067
+ port: this.port,
1068
+ pid: process.pid,
1069
+ startedAt: this.startedAt?.toISOString() || "",
1070
+ uptime: formatDuration(uptimeMs),
1071
+ version: getVersion(),
1072
+ stats,
1073
+ connectedPages
1074
+ };
1075
+ const response = {
1076
+ id: message.id || "",
1077
+ type: "response" /* RESPONSE */,
1078
+ success: true,
1079
+ data: statusData
1080
+ };
1081
+ ws.send(JSON.stringify(response));
1082
+ this.logger.info("\u8FD4\u56DE Server \u72B6\u6001\u4FE1\u606F");
1083
+ }
852
1084
  /**
853
1085
  * 停止服务器
854
1086
  */
@@ -913,6 +1145,7 @@ export {
913
1145
  MGError,
914
1146
  MGServer,
915
1147
  MessageType,
1148
+ NODE_DEFAULTS,
916
1149
  PORT_RANGE_END,
917
1150
  PORT_RANGE_START,
918
1151
  PORT_SCAN_TIMEOUT,
@@ -932,6 +1165,7 @@ export {
932
1165
  extractFileId,
933
1166
  extractFileIdFromMgpLink,
934
1167
  extractFileIdFromUrl,
1168
+ extractSpaceInfo,
935
1169
  formatDuration,
936
1170
  formatFileSize,
937
1171
  formatLogTime,
@@ -945,6 +1179,8 @@ export {
945
1179
  parseMgpLink,
946
1180
  readServerInfo,
947
1181
  resolveOutputPath,
1182
+ roundToOneDecimal,
1183
+ trimNodeDefaults,
948
1184
  writeServerInfo
949
1185
  };
950
1186
  //# sourceMappingURL=index.js.map