@sfmc-bds/management 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +663 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +10 -0
  5. package/dist/index.js.map +7 -0
  6. package/dist/node/client.d.ts +16 -0
  7. package/dist/node/client.d.ts.map +1 -0
  8. package/dist/node/client.js +82 -0
  9. package/dist/node/client.js.map +7 -0
  10. package/dist/node/index.d.ts +3 -0
  11. package/dist/node/index.d.ts.map +1 -0
  12. package/dist/node/index.js +3 -0
  13. package/dist/node/index.js.map +7 -0
  14. package/dist/node/maintenance-lock.d.ts +4 -0
  15. package/dist/node/maintenance-lock.d.ts.map +1 -0
  16. package/dist/node/maintenance-lock.js +46 -0
  17. package/dist/node/maintenance-lock.js.map +7 -0
  18. package/dist/protocol/errors.d.ts +43 -0
  19. package/dist/protocol/errors.d.ts.map +1 -0
  20. package/dist/protocol/errors.js +18 -0
  21. package/dist/protocol/errors.js.map +7 -0
  22. package/dist/protocol/frames.d.ts +59 -0
  23. package/dist/protocol/frames.d.ts.map +1 -0
  24. package/dist/protocol/frames.js +21 -0
  25. package/dist/protocol/frames.js.map +7 -0
  26. package/dist/protocol/handshake.d.ts +35 -0
  27. package/dist/protocol/handshake.d.ts.map +1 -0
  28. package/dist/protocol/handshake.js +1 -0
  29. package/dist/protocol/handshake.js.map +7 -0
  30. package/dist/protocol/logs.d.ts +33 -0
  31. package/dist/protocol/logs.d.ts.map +1 -0
  32. package/dist/protocol/logs.js +1 -0
  33. package/dist/protocol/logs.js.map +7 -0
  34. package/dist/protocol/methods.d.ts +101 -0
  35. package/dist/protocol/methods.d.ts.map +1 -0
  36. package/dist/protocol/methods.js +1 -0
  37. package/dist/protocol/methods.js.map +7 -0
  38. package/dist/protocol/metrics.d.ts +41 -0
  39. package/dist/protocol/metrics.d.ts.map +1 -0
  40. package/dist/protocol/metrics.js +1 -0
  41. package/dist/protocol/metrics.js.map +7 -0
  42. package/dist/protocol/operations.d.ts +62 -0
  43. package/dist/protocol/operations.d.ts.map +1 -0
  44. package/dist/protocol/operations.js +1 -0
  45. package/dist/protocol/operations.js.map +7 -0
  46. package/dist/protocol/services.d.ts +49 -0
  47. package/dist/protocol/services.d.ts.map +1 -0
  48. package/dist/protocol/services.js +1 -0
  49. package/dist/protocol/services.js.map +7 -0
  50. package/dist/protocol/version.d.ts +9 -0
  51. package/dist/protocol/version.d.ts.map +1 -0
  52. package/dist/protocol/version.js +5 -0
  53. package/dist/protocol/version.js.map +7 -0
  54. package/package.json +57 -0
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export * from "./protocol/version.js";
2
+ export * from "./protocol/errors.js";
3
+ export * from "./protocol/frames.js";
4
+ export * from "./protocol/handshake.js";
5
+ export * from "./protocol/logs.js";
6
+ export * from "./protocol/operations.js";
7
+ export * from "./protocol/services.js";
8
+ export * from "./protocol/methods.js";
9
+ export * from "./protocol/metrics.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["export * from \"./protocol/version.js\";\nexport * from \"./protocol/errors.js\";\nexport * from \"./protocol/frames.js\";\nexport * from \"./protocol/handshake.js\";\nexport * from \"./protocol/logs.js\";\nexport * from \"./protocol/operations.js\";\nexport * from \"./protocol/services.js\";\nexport * from \"./protocol/methods.js\";\nexport * from \"./protocol/metrics.js\";\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { Duplex } from "node:stream";
3
+ import type { ManagementMethod, ManagementMethodMap } from "../index.js";
4
+ /** 每条连接独立持有请求;断线只拒绝等待,不重发写操作。 */
5
+ export declare class ManagementClient extends EventEmitter {
6
+ private stream;
7
+ private buffer;
8
+ private pending;
9
+ private closed;
10
+ constructor(stream: Duplex);
11
+ handshake(root?: string): Promise<import("../index.js").Handshake>;
12
+ call<K extends ManagementMethod>(method: K, params?: unknown, timeout?: number): Promise<ManagementMethodMap[K]>;
13
+ disconnect(): void;
14
+ private end;
15
+ }
16
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/node/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAmB,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE1F,iCAAiC;AACjC,qBAAa,gBAAiB,SAAQ,YAAY;IAIpC,OAAO,CAAC,MAAM;IAH1B,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAmH;IAClI,OAAO,CAAC,MAAM,CAAS;IACvB,YAAoB,MAAM,EAAE,MAAM,EAsBjC;IACK,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,4CAI5B;IACD,IAAI,CAAC,CAAC,SAAS,gBAAgB,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAE,OAAY,EAAE,OAAO,SAAS,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAUnH;IACD,UAAU,SAA4D;IACtE,OAAO,CAAC,GAAG;CAMZ"}
@@ -0,0 +1,82 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { randomUUID } from "node:crypto";
3
+ import { decodeFrame, MANAGEMENT_PROTOCOL_VERSION } from "../index.js";
4
+ class ManagementClient extends EventEmitter {
5
+ constructor(stream) {
6
+ super();
7
+ this.stream = stream;
8
+ stream.setEncoding("utf8");
9
+ stream.on("data", (chunk) => {
10
+ this.buffer += chunk;
11
+ if (this.buffer.length > 8 * 1024 * 1024) {
12
+ this.disconnect();
13
+ return;
14
+ }
15
+ let index;
16
+ while ((index = this.buffer.indexOf("\n")) !== -1) {
17
+ const line = this.buffer.slice(0, index);
18
+ this.buffer = this.buffer.slice(index + 1);
19
+ const frame = decodeFrame(line);
20
+ if (!frame) continue;
21
+ if (frame.type === "event") {
22
+ this.emit("event", frame);
23
+ continue;
24
+ }
25
+ const request = this.pending.get(frame.id);
26
+ if (!request) continue;
27
+ clearTimeout(request.timer);
28
+ this.pending.delete(frame.id);
29
+ if (frame.ok) request.resolve(frame.result);
30
+ else request.reject(Object.assign(new Error(frame.error?.message ?? "\u7BA1\u7406\u8BF7\u6C42\u5931\u8D25"), { code: frame.error?.code, details: frame.error?.details }));
31
+ }
32
+ });
33
+ stream.on("error", (error) => this.end(error));
34
+ stream.on("close", () => this.end(new Error("\u7BA1\u7406\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF1B\u540E\u53F0\u4EFB\u52A1\u7EE7\u7EED\u8FD0\u884C")));
35
+ stream.on("end", () => this.end(new Error("\u7BA1\u7406\u8FDE\u63A5\u5DF2\u7ED3\u675F")));
36
+ }
37
+ stream;
38
+ buffer = "";
39
+ pending = /* @__PURE__ */ new Map();
40
+ closed = false;
41
+ async handshake(root) {
42
+ const result = await this.call("handshake", root ? { root } : {});
43
+ if (result.protocolVersion !== MANAGEMENT_PROTOCOL_VERSION) throw new Error("\u7BA1\u7406\u534F\u8BAE\u7248\u672C\u4E0D\u517C\u5BB9\uFF0C\u8BF7\u66F4\u65B0\u5BA2\u6237\u7AEF\u6216\u5E73\u53F0");
44
+ return result;
45
+ }
46
+ call(method, params = {}, timeout = 3e4) {
47
+ if (this.closed) return Promise.reject(new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED"));
48
+ const id = randomUUID();
49
+ return new Promise((resolve, reject) => {
50
+ const timer = setTimeout(() => {
51
+ this.pending.delete(id);
52
+ reject(new Error("\u8BF7\u6C42\u8D85\u65F6\uFF1B\u8BF7\u67E5\u8BE2\u4EFB\u52A1\u8BB0\u5F55\uFF0C\u52FF\u91CD\u590D\u63D0\u4EA4"));
53
+ }, timeout);
54
+ this.pending.set(id, { resolve: (value) => resolve(value), reject, timer });
55
+ this.stream.write(JSON.stringify({ id, method, params }) + "\n", (error) => {
56
+ if (error) {
57
+ clearTimeout(timer);
58
+ this.pending.delete(id);
59
+ reject(error);
60
+ }
61
+ });
62
+ });
63
+ }
64
+ disconnect() {
65
+ this.end(new Error("\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00"));
66
+ this.stream.destroy();
67
+ }
68
+ end(error) {
69
+ if (this.closed) return;
70
+ this.closed = true;
71
+ for (const request of this.pending.values()) {
72
+ clearTimeout(request.timer);
73
+ request.reject(error);
74
+ }
75
+ this.pending.clear();
76
+ this.emit("disconnected", error.message);
77
+ }
78
+ }
79
+ export {
80
+ ManagementClient
81
+ };
82
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/node/client.ts"],
4
+ "sourcesContent": ["import { EventEmitter } from \"node:events\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Duplex } from \"node:stream\";\nimport { decodeFrame, MANAGEMENT_PROTOCOL_VERSION } from \"../index.js\";\nimport type { ManagementEvent, ManagementMethod, ManagementMethodMap } from \"../index.js\";\n\n/** \u6BCF\u6761\u8FDE\u63A5\u72EC\u7ACB\u6301\u6709\u8BF7\u6C42\uFF1B\u65AD\u7EBF\u53EA\u62D2\u7EDD\u7B49\u5F85\uFF0C\u4E0D\u91CD\u53D1\u5199\u64CD\u4F5C\u3002 */\nexport class ManagementClient extends EventEmitter {\n private buffer = \"\";\n private pending = new Map<string, { resolve: (value: unknown) => void; reject: (error: Error) => void; timer: NodeJS.Timeout }>();\n private closed = false;\n constructor(private stream: Duplex) {\n super();\n stream.setEncoding(\"utf8\");\n stream.on(\"data\", (chunk: string) => {\n this.buffer += chunk;\n if (this.buffer.length > 8 * 1024 * 1024) { this.disconnect(); return; }\n let index: number;\n while ((index = this.buffer.indexOf(\"\\n\")) !== -1) {\n const line = this.buffer.slice(0, index); this.buffer = this.buffer.slice(index + 1);\n const frame = decodeFrame(line);\n if (!frame) continue;\n if (frame.type === \"event\") { this.emit(\"event\", frame as ManagementEvent); continue; }\n const request = this.pending.get(frame.id);\n if (!request) continue;\n clearTimeout(request.timer); this.pending.delete(frame.id);\n if (frame.ok) request.resolve(frame.result);\n else request.reject(Object.assign(new Error(frame.error?.message ?? \"\u7BA1\u7406\u8BF7\u6C42\u5931\u8D25\"), { code: frame.error?.code, details: frame.error?.details }));\n }\n });\n stream.on(\"error\", (error: Error) => this.end(error));\n stream.on(\"close\", () => this.end(new Error(\"\u7BA1\u7406\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF1B\u540E\u53F0\u4EFB\u52A1\u7EE7\u7EED\u8FD0\u884C\")));\n stream.on(\"end\", () => this.end(new Error(\"\u7BA1\u7406\u8FDE\u63A5\u5DF2\u7ED3\u675F\")));\n }\n async handshake(root?: string) {\n const result = await this.call(\"handshake\", root ? { root } : {});\n if (result.protocolVersion !== MANAGEMENT_PROTOCOL_VERSION) throw new Error(\"\u7BA1\u7406\u534F\u8BAE\u7248\u672C\u4E0D\u517C\u5BB9\uFF0C\u8BF7\u66F4\u65B0\u5BA2\u6237\u7AEF\u6216\u5E73\u53F0\");\n return result;\n }\n call<K extends ManagementMethod>(method: K, params: unknown = {}, timeout = 30_000): Promise<ManagementMethodMap[K]> {\n if (this.closed) return Promise.reject(new Error(\"\u8FDE\u63A5\u5DF2\u5173\u95ED\"));\n const id = randomUUID();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => { this.pending.delete(id); reject(new Error(\"\u8BF7\u6C42\u8D85\u65F6\uFF1B\u8BF7\u67E5\u8BE2\u4EFB\u52A1\u8BB0\u5F55\uFF0C\u52FF\u91CD\u590D\u63D0\u4EA4\")); }, timeout);\n this.pending.set(id, { resolve: value => resolve(value as ManagementMethodMap[K]), reject, timer });\n this.stream.write(JSON.stringify({ id, method, params }) + \"\\n\", error => {\n if (error) { clearTimeout(timer); this.pending.delete(id); reject(error); }\n });\n });\n }\n disconnect() { this.end(new Error(\"\u5BA2\u6237\u7AEF\u5DF2\u65AD\u5F00\")); this.stream.destroy(); }\n private end(error: Error) {\n if (this.closed) return;\n this.closed = true;\n for (const request of this.pending.values()) { clearTimeout(request.timer); request.reject(error); }\n this.pending.clear(); this.emit(\"disconnected\", error.message);\n }\n}\n\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAE3B,SAAS,aAAa,mCAAmC;AAIlD,MAAM,yBAAyB,aAAa;AAAA,EAIjD,YAAoB,QAAgB;AAClC,UAAM;AADY;AAElB,WAAO,YAAY,MAAM;AACzB,WAAO,GAAG,QAAQ,CAAC,UAAkB;AACnC,WAAK,UAAU;AACf,UAAI,KAAK,OAAO,SAAS,IAAI,OAAO,MAAM;AAAE,aAAK,WAAW;AAAG;AAAA,MAAQ;AACvE,UAAI;AACJ,cAAQ,QAAQ,KAAK,OAAO,QAAQ,IAAI,OAAO,IAAI;AACjD,cAAM,OAAO,KAAK,OAAO,MAAM,GAAG,KAAK;AAAG,aAAK,SAAS,KAAK,OAAO,MAAM,QAAQ,CAAC;AACnF,cAAM,QAAQ,YAAY,IAAI;AAC9B,YAAI,CAAC,MAAO;AACZ,YAAI,MAAM,SAAS,SAAS;AAAE,eAAK,KAAK,SAAS,KAAwB;AAAG;AAAA,QAAU;AACtF,cAAM,UAAU,KAAK,QAAQ,IAAI,MAAM,EAAE;AACzC,YAAI,CAAC,QAAS;AACd,qBAAa,QAAQ,KAAK;AAAG,aAAK,QAAQ,OAAO,MAAM,EAAE;AACzD,YAAI,MAAM,GAAI,SAAQ,QAAQ,MAAM,MAAM;AAAA,YACrC,SAAQ,OAAO,OAAO,OAAO,IAAI,MAAM,MAAM,OAAO,WAAW,sCAAQ,GAAG,EAAE,MAAM,MAAM,OAAO,MAAM,SAAS,MAAM,OAAO,QAAQ,CAAC,CAAC;AAAA,MAC5I;AAAA,IACF,CAAC;AACD,WAAO,GAAG,SAAS,CAAC,UAAiB,KAAK,IAAI,KAAK,CAAC;AACpD,WAAO,GAAG,SAAS,MAAM,KAAK,IAAI,IAAI,MAAM,kGAAkB,CAAC,CAAC;AAChE,WAAO,GAAG,OAAO,MAAM,KAAK,IAAI,IAAI,MAAM,4CAAS,CAAC,CAAC;AAAA,EACvD;AAAA,EAtBoB;AAAA,EAHZ,SAAS;AAAA,EACT,UAAU,oBAAI,IAA0G;AAAA,EACxH,SAAS;AAAA,EAwBjB,MAAM,UAAU,MAAe;AAC7B,UAAM,SAAS,MAAM,KAAK,KAAK,aAAa,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC;AAChE,QAAI,OAAO,oBAAoB,4BAA6B,OAAM,IAAI,MAAM,oHAAqB;AACjG,WAAO;AAAA,EACT;AAAA,EACA,KAAiC,QAAW,SAAkB,CAAC,GAAG,UAAU,KAAyC;AACnH,QAAI,KAAK,OAAQ,QAAO,QAAQ,OAAO,IAAI,MAAM,gCAAO,CAAC;AACzD,UAAM,KAAK,WAAW;AACtB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,QAAQ,WAAW,MAAM;AAAE,aAAK,QAAQ,OAAO,EAAE;AAAG,eAAO,IAAI,MAAM,8GAAoB,CAAC;AAAA,MAAG,GAAG,OAAO;AAC7G,WAAK,QAAQ,IAAI,IAAI,EAAE,SAAS,WAAS,QAAQ,KAA+B,GAAG,QAAQ,MAAM,CAAC;AAClG,WAAK,OAAO,MAAM,KAAK,UAAU,EAAE,IAAI,QAAQ,OAAO,CAAC,IAAI,MAAM,WAAS;AACxE,YAAI,OAAO;AAAE,uBAAa,KAAK;AAAG,eAAK,QAAQ,OAAO,EAAE;AAAG,iBAAO,KAAK;AAAA,QAAG;AAAA,MAC5E,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EACA,aAAa;AAAE,SAAK,IAAI,IAAI,MAAM,sCAAQ,CAAC;AAAG,SAAK,OAAO,QAAQ;AAAA,EAAG;AAAA,EAC7D,IAAI,OAAc;AACxB,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,WAAW,KAAK,QAAQ,OAAO,GAAG;AAAE,mBAAa,QAAQ,KAAK;AAAG,cAAQ,OAAO,KAAK;AAAA,IAAG;AACnG,SAAK,QAAQ,MAAM;AAAG,SAAK,KAAK,gBAAgB,MAAM,OAAO;AAAA,EAC/D;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./client.js";
2
+ export * from "./maintenance-lock.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./client.js";
2
+ export * from "./maintenance-lock.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/node/index.ts"],
4
+ "sourcesContent": ["export * from \"./client.js\";\nexport * from \"./maintenance-lock.js\";\n\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,4 @@
1
+ export declare function maintenanceToken(root: string): string | undefined;
2
+ /** 跨 CLI、守护进程和独立维护进程共享的实例锁;释放前校验所有权。 */
3
+ export declare function withMaintenanceLock<T>(root: string, action: () => Promise<T>): Promise<T>;
4
+ //# sourceMappingURL=maintenance-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maintenance-lock.d.ts","sourceRoot":"","sources":["../../src/node/maintenance-lock.ts"],"names":[],"mappings":"AAMA,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGjE;AAED,wCAAwC;AACxC,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAyB/F"}
@@ -0,0 +1,46 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ const locks = new AsyncLocalStorage();
6
+ function maintenanceToken(root) {
7
+ const scope = locks.getStore();
8
+ return scope?.root === path.resolve(root) ? scope.nonce : void 0;
9
+ }
10
+ async function withMaintenanceLock(root, action) {
11
+ if (maintenanceToken(root)) return action();
12
+ const file = path.join(root, ".sfmc", "maintenance.lock");
13
+ await fs.mkdir(path.dirname(file), { recursive: true });
14
+ const nonce = randomUUID();
15
+ let handle;
16
+ try {
17
+ handle = await fs.open(file, "wx", 384);
18
+ } catch (error) {
19
+ if (error.code !== "EEXIST") throw error;
20
+ const owner = await fs.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
21
+ if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw Object.assign(new Error("\u7EF4\u62A4\u9501\u6B63\u5728\u521B\u5EFA\u6216\u5DF2\u635F\u574F\uFF1B\u8BF7\u7A0D\u540E\u91CD\u8BD5\u5E76\u6838\u5BF9\u4EFB\u52A1\u72B6\u6001"), { code: "locked" });
22
+ let alive = true;
23
+ try {
24
+ process.kill(owner.pid, 0);
25
+ } catch (probe) {
26
+ alive = probe.code !== "ESRCH";
27
+ }
28
+ if (alive) throw Object.assign(new Error("\u53E6\u4E00\u4E2A\u7BA1\u7406\u64CD\u4F5C\u6B63\u5728\u6267\u884C"), { code: "locked" });
29
+ throw Object.assign(new Error("\u524D\u4E00\u6B21\u7EF4\u62A4\u5F02\u5E38\u9000\u51FA\uFF1B\u8BF7\u6838\u5BF9\u4EFB\u52A1\u4E0E\u5907\u4EFD\u540E\u4EBA\u5DE5\u89E3\u9664\u7EF4\u62A4\u9501"), { code: "locked" });
30
+ }
31
+ try {
32
+ await handle.writeFile(JSON.stringify({ pid: process.pid, nonce, createdAt: (/* @__PURE__ */ new Date()).toISOString() }));
33
+ await handle.close();
34
+ return await locks.run({ root: path.resolve(root), nonce }, action);
35
+ } finally {
36
+ await handle.close().catch(() => {
37
+ });
38
+ const owner = await fs.readFile(file, "utf8").then((text) => JSON.parse(text)).catch(() => null);
39
+ if (owner?.nonce === nonce) await fs.unlink(file);
40
+ }
41
+ }
42
+ export {
43
+ maintenanceToken,
44
+ withMaintenanceLock
45
+ };
46
+ //# sourceMappingURL=maintenance-lock.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/node/maintenance-lock.ts"],
4
+ "sourcesContent": ["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nconst locks = new AsyncLocalStorage<{ root: string; nonce: string }>();\nexport function maintenanceToken(root: string): string | undefined {\n const scope = locks.getStore();\n return scope?.root === path.resolve(root) ? scope.nonce : undefined;\n}\n\n/** \u8DE8 CLI\u3001\u5B88\u62A4\u8FDB\u7A0B\u548C\u72EC\u7ACB\u7EF4\u62A4\u8FDB\u7A0B\u5171\u4EAB\u7684\u5B9E\u4F8B\u9501\uFF1B\u91CA\u653E\u524D\u6821\u9A8C\u6240\u6709\u6743\u3002 */\nexport async function withMaintenanceLock<T>(root: string, action: () => Promise<T>): Promise<T> {\n if (maintenanceToken(root)) return action();\n const file = path.join(root, \".sfmc\", \"maintenance.lock\");\n await fs.mkdir(path.dirname(file), { recursive: true });\n const nonce = randomUUID();\n let handle;\n try { handle = await fs.open(file, \"wx\", 0o600); }\n catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n const owner = await fs.readFile(file, \"utf8\").then(text => JSON.parse(text) as { pid: number }).catch(() => null);\n if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) throw Object.assign(new Error(\"\u7EF4\u62A4\u9501\u6B63\u5728\u521B\u5EFA\u6216\u5DF2\u635F\u574F\uFF1B\u8BF7\u7A0D\u540E\u91CD\u8BD5\u5E76\u6838\u5BF9\u4EFB\u52A1\u72B6\u6001\"), { code: \"locked\" });\n let alive = true;\n try { process.kill(owner.pid, 0); } catch (probe) { alive = (probe as NodeJS.ErrnoException).code !== \"ESRCH\"; }\n if (alive) throw Object.assign(new Error(\"\u53E6\u4E00\u4E2A\u7BA1\u7406\u64CD\u4F5C\u6B63\u5728\u6267\u884C\"), { code: \"locked\" });\n throw Object.assign(new Error(\"\u524D\u4E00\u6B21\u7EF4\u62A4\u5F02\u5E38\u9000\u51FA\uFF1B\u8BF7\u6838\u5BF9\u4EFB\u52A1\u4E0E\u5907\u4EFD\u540E\u4EBA\u5DE5\u89E3\u9664\u7EF4\u62A4\u9501\"), { code: \"locked\" });\n }\n try {\n await handle.writeFile(JSON.stringify({ pid: process.pid, nonce, createdAt: new Date().toISOString() }));\n await handle.close();\n return await locks.run({ root: path.resolve(root), nonce }, action);\n } finally {\n await handle.close().catch(() => {});\n const owner = await fs.readFile(file, \"utf8\").then(text => JSON.parse(text) as { nonce: string }).catch(() => null);\n if (owner?.nonce === nonce) await fs.unlink(file);\n }\n}\n"],
5
+ "mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAC3B,SAAS,yBAAyB;AAElC,MAAM,QAAQ,IAAI,kBAAmD;AAC9D,SAAS,iBAAiB,MAAkC;AACjE,QAAM,QAAQ,MAAM,SAAS;AAC7B,SAAO,OAAO,SAAS,KAAK,QAAQ,IAAI,IAAI,MAAM,QAAQ;AAC5D;AAGA,eAAsB,oBAAuB,MAAc,QAAsC;AAC/F,MAAI,iBAAiB,IAAI,EAAG,QAAO,OAAO;AAC1C,QAAM,OAAO,KAAK,KAAK,MAAM,SAAS,kBAAkB;AACxD,QAAM,GAAG,MAAM,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,QAAM,QAAQ,WAAW;AACzB,MAAI;AACJ,MAAI;AAAE,aAAS,MAAM,GAAG,KAAK,MAAM,MAAM,GAAK;AAAA,EAAG,SAC1C,OAAO;AACZ,QAAK,MAAgC,SAAS,SAAU,OAAM;AAC9D,UAAM,QAAQ,MAAM,GAAG,SAAS,MAAM,MAAM,EAAE,KAAK,UAAQ,KAAK,MAAM,IAAI,CAAoB,EAAE,MAAM,MAAM,IAAI;AAChH,QAAI,CAAC,SAAS,CAAC,OAAO,cAAc,MAAM,GAAG,KAAK,MAAM,OAAO,EAAG,OAAM,OAAO,OAAO,IAAI,MAAM,kJAA0B,GAAG,EAAE,MAAM,SAAS,CAAC;AAC/I,QAAI,QAAQ;AACZ,QAAI;AAAE,cAAQ,KAAK,MAAM,KAAK,CAAC;AAAA,IAAG,SAAS,OAAO;AAAE,cAAS,MAAgC,SAAS;AAAA,IAAS;AAC/G,QAAI,MAAO,OAAM,OAAO,OAAO,IAAI,MAAM,oEAAa,GAAG,EAAE,MAAM,SAAS,CAAC;AAC3E,UAAM,OAAO,OAAO,IAAI,MAAM,8JAA4B,GAAG,EAAE,MAAM,SAAS,CAAC;AAAA,EACjF;AACA,MAAI;AACF,UAAM,OAAO,UAAU,KAAK,UAAU,EAAE,KAAK,QAAQ,KAAK,OAAO,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC,CAAC;AACvG,UAAM,OAAO,MAAM;AACnB,WAAO,MAAM,MAAM,IAAI,EAAE,MAAM,KAAK,QAAQ,IAAI,GAAG,MAAM,GAAG,MAAM;AAAA,EACpE,UAAE;AACA,UAAM,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACnC,UAAM,QAAQ,MAAM,GAAG,SAAS,MAAM,MAAM,EAAE,KAAK,UAAQ,KAAK,MAAM,IAAI,CAAsB,EAAE,MAAM,MAAM,IAAI;AAClH,QAAI,OAAO,UAAU,MAAO,OAAM,GAAG,OAAO,IAAI;AAAA,EAClD;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * errors.ts — 管理协议结构化错误信封
3
+ *
4
+ * 使用场景:所有管理方法的失败响应统一携带本信封(LSP:读/写方法错误结构一致),
5
+ * 界面按 code 做本地化与分流处理,message 仅作诊断展示,不做字符串匹配。
6
+ */
7
+ /** 结构化错误码(界面据此本地化;新增错误类型在此扩展) */
8
+ export type ManagementErrorCode =
9
+ /** 请求帧无法解析或缺少必要字段 */
10
+ "invalid_request"
11
+ /** 鉴权失败(token 不符) */
12
+ | "unauthorized"
13
+ /** 当前平台版本/能力集不支持该方法 */
14
+ | "unsupported"
15
+ /** 守护进程或目标服务不可用(未运行、连接断开) */
16
+ | "unavailable"
17
+ /** 目标对象不存在(模块、配置、任务等) */
18
+ | "not_found"
19
+ /** 状态冲突(已在运行、重复接入等) */
20
+ | "conflict"
21
+ /** 维护锁被占用,写操作需稍后重试 */
22
+ | "locked"
23
+ /** 参数或配置校验失败 */
24
+ | "validation"
25
+ /** 底层 IO / 网络 / 子进程失败 */
26
+ | "io"
27
+ /** 未分类内部错误 */
28
+ | "internal";
29
+ /** 统一错误信封 */
30
+ export interface ManagementError {
31
+ code: ManagementErrorCode;
32
+ /** 诊断用技术描述(服务端语言,不做本地化匹配) */
33
+ message: string;
34
+ /** 可选结构化补充(如校验失败的字段列表) */
35
+ details?: unknown;
36
+ }
37
+ /** 构造 ManagementError 的便捷助手 */
38
+ export declare function managementError(code: ManagementErrorCode, message: string, details?: unknown): ManagementError;
39
+ /** 类型守卫:未知值是否为 ManagementError 信封 */
40
+ export declare function isManagementError(value: unknown): value is ManagementError;
41
+ /** 把捕获的异常规整为 ManagementError(默认 internal) */
42
+ export declare function toManagementError(e: unknown, code?: ManagementErrorCode): ManagementError;
43
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/protocol/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iCAAiC;AACjC,MAAM,MAAM,mBAAmB;AAC7B,qBAAqB;AACnB,iBAAiB;AACnB,qBAAqB;GACnB,cAAc;AAChB,uBAAuB;GACrB,aAAa;AACf,6BAA6B;GAC3B,aAAa;AACf,yBAAyB;GACvB,WAAW;AACb,uBAAuB;GACrB,UAAU;AACZ,sBAAsB;GACpB,QAAQ;AACV,gBAAgB;GACd,YAAY;AACd,yBAAyB;GACvB,IAAI;AACN,cAAc;GACZ,UAAU,CAAC;AAEf,aAAa;AACb,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,+BAA+B;AAC/B,wBAAgB,eAAe,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe,CAE9G;AAED,qCAAqC;AACrC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAI1E;AAED,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,GAAE,mBAAgC,GAAG,eAAe,CAGrG"}
@@ -0,0 +1,18 @@
1
+ function managementError(code, message, details) {
2
+ return details === void 0 ? { code, message } : { code, message, details };
3
+ }
4
+ function isManagementError(value) {
5
+ if (typeof value !== "object" || value === null) return false;
6
+ const v = value;
7
+ return typeof v.code === "string" && typeof v.message === "string";
8
+ }
9
+ function toManagementError(e, code = "internal") {
10
+ if (isManagementError(e)) return { code: e.code, message: e.message, ...e.details === void 0 ? {} : { details: e.details } };
11
+ return { code, message: e instanceof Error ? e.message : String(e) };
12
+ }
13
+ export {
14
+ isManagementError,
15
+ managementError,
16
+ toManagementError
17
+ };
18
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/protocol/errors.ts"],
4
+ "sourcesContent": ["/**\n * errors.ts \u2014 \u7BA1\u7406\u534F\u8BAE\u7ED3\u6784\u5316\u9519\u8BEF\u4FE1\u5C01\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A\u6240\u6709\u7BA1\u7406\u65B9\u6CD5\u7684\u5931\u8D25\u54CD\u5E94\u7EDF\u4E00\u643A\u5E26\u672C\u4FE1\u5C01\uFF08LSP\uFF1A\u8BFB/\u5199\u65B9\u6CD5\u9519\u8BEF\u7ED3\u6784\u4E00\u81F4\uFF09\uFF0C\n * \u754C\u9762\u6309 code \u505A\u672C\u5730\u5316\u4E0E\u5206\u6D41\u5904\u7406\uFF0Cmessage \u4EC5\u4F5C\u8BCA\u65AD\u5C55\u793A\uFF0C\u4E0D\u505A\u5B57\u7B26\u4E32\u5339\u914D\u3002\n */\n\n/** \u7ED3\u6784\u5316\u9519\u8BEF\u7801\uFF08\u754C\u9762\u636E\u6B64\u672C\u5730\u5316\uFF1B\u65B0\u589E\u9519\u8BEF\u7C7B\u578B\u5728\u6B64\u6269\u5C55\uFF09 */\nexport type ManagementErrorCode =\n /** \u8BF7\u6C42\u5E27\u65E0\u6CD5\u89E3\u6790\u6216\u7F3A\u5C11\u5FC5\u8981\u5B57\u6BB5 */\n | \"invalid_request\"\n /** \u9274\u6743\u5931\u8D25\uFF08token \u4E0D\u7B26\uFF09 */\n | \"unauthorized\"\n /** \u5F53\u524D\u5E73\u53F0\u7248\u672C/\u80FD\u529B\u96C6\u4E0D\u652F\u6301\u8BE5\u65B9\u6CD5 */\n | \"unsupported\"\n /** \u5B88\u62A4\u8FDB\u7A0B\u6216\u76EE\u6807\u670D\u52A1\u4E0D\u53EF\u7528\uFF08\u672A\u8FD0\u884C\u3001\u8FDE\u63A5\u65AD\u5F00\uFF09 */\n | \"unavailable\"\n /** \u76EE\u6807\u5BF9\u8C61\u4E0D\u5B58\u5728\uFF08\u6A21\u5757\u3001\u914D\u7F6E\u3001\u4EFB\u52A1\u7B49\uFF09 */\n | \"not_found\"\n /** \u72B6\u6001\u51B2\u7A81\uFF08\u5DF2\u5728\u8FD0\u884C\u3001\u91CD\u590D\u63A5\u5165\u7B49\uFF09 */\n | \"conflict\"\n /** \u7EF4\u62A4\u9501\u88AB\u5360\u7528\uFF0C\u5199\u64CD\u4F5C\u9700\u7A0D\u540E\u91CD\u8BD5 */\n | \"locked\"\n /** \u53C2\u6570\u6216\u914D\u7F6E\u6821\u9A8C\u5931\u8D25 */\n | \"validation\"\n /** \u5E95\u5C42 IO / \u7F51\u7EDC / \u5B50\u8FDB\u7A0B\u5931\u8D25 */\n | \"io\"\n /** \u672A\u5206\u7C7B\u5185\u90E8\u9519\u8BEF */\n | \"internal\";\n\n/** \u7EDF\u4E00\u9519\u8BEF\u4FE1\u5C01 */\nexport interface ManagementError {\n code: ManagementErrorCode;\n /** \u8BCA\u65AD\u7528\u6280\u672F\u63CF\u8FF0\uFF08\u670D\u52A1\u7AEF\u8BED\u8A00\uFF0C\u4E0D\u505A\u672C\u5730\u5316\u5339\u914D\uFF09 */\n message: string;\n /** \u53EF\u9009\u7ED3\u6784\u5316\u8865\u5145\uFF08\u5982\u6821\u9A8C\u5931\u8D25\u7684\u5B57\u6BB5\u5217\u8868\uFF09 */\n details?: unknown;\n}\n\n/** \u6784\u9020 ManagementError \u7684\u4FBF\u6377\u52A9\u624B */\nexport function managementError(code: ManagementErrorCode, message: string, details?: unknown): ManagementError {\n return details === undefined ? { code, message } : { code, message, details };\n}\n\n/** \u7C7B\u578B\u5B88\u536B\uFF1A\u672A\u77E5\u503C\u662F\u5426\u4E3A ManagementError \u4FE1\u5C01 */\nexport function isManagementError(value: unknown): value is ManagementError {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Partial<ManagementError>;\n return typeof v.code === \"string\" && typeof v.message === \"string\";\n}\n\n/** \u628A\u6355\u83B7\u7684\u5F02\u5E38\u89C4\u6574\u4E3A ManagementError\uFF08\u9ED8\u8BA4 internal\uFF09 */\nexport function toManagementError(e: unknown, code: ManagementErrorCode = \"internal\"): ManagementError {\n if (isManagementError(e)) return { code: e.code, message: e.message, ...(e.details === undefined ? {} : { details: e.details }) };\n return { code, message: e instanceof Error ? e.message : String(e) };\n}\n"],
5
+ "mappings": "AAwCO,SAAS,gBAAgB,MAA2B,SAAiB,SAAoC;AAC9G,SAAO,YAAY,SAAY,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,SAAS,QAAQ;AAC9E;AAGO,SAAS,kBAAkB,OAA0C;AAC1E,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,IAAI;AACV,SAAO,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,YAAY;AAC5D;AAGO,SAAS,kBAAkB,GAAY,OAA4B,YAA6B;AACrG,MAAI,kBAAkB,CAAC,EAAG,QAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,SAAS,GAAI,EAAE,YAAY,SAAY,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAG;AAChI,SAAO,EAAE,MAAM,SAAS,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE;AACrE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * frames.ts — 管理协议帧结构(换行分隔 JSON,与守护进程协议同构)
3
+ *
4
+ * 使用场景:`sfmc manage --stdio` 的标准输入/输出、以及桌面端 SSH 通道上
5
+ * 传输的帧格式。请求/响应按 id 配对;订阅后服务端可推送 event 帧。
6
+ * 诊断信息一律走 stderr,不得混入 stdout 帧流。
7
+ */
8
+ import type { ManagementError } from "./errors.js";
9
+ import type { LogEventPayload } from "./logs.js";
10
+ import type { OperationRecord } from "./operations.js";
11
+ import type { ServiceStateChanged } from "./services.js";
12
+ /** 客户端 → 服务端请求帧 */
13
+ export interface ManagementRequest {
14
+ /** 请求标识(客户端生成,响应原样带回) */
15
+ id: string;
16
+ /** 方法名(见 methods.ts 的 ManagementMethodMap) */
17
+ method: string;
18
+ /** 方法参数(按 method 而定) */
19
+ params?: unknown;
20
+ }
21
+ /** 服务端 → 客户端响应帧 */
22
+ export interface ManagementResponse {
23
+ type: "res";
24
+ id: string;
25
+ ok: boolean;
26
+ /** 成功时的结构化结果(按 method 而定) */
27
+ result?: unknown;
28
+ /** 失败时的结构化错误信封 */
29
+ error?: ManagementError;
30
+ }
31
+ /** 可订阅的事件种类 */
32
+ export type ManagementEventKind = "log" | "serviceState" | "operation";
33
+ /** 服务端 → 客户端事件推送帧(events.subscribe 之后) */
34
+ export interface ManagementEvent {
35
+ instanceId: string;
36
+ type: "event";
37
+ event: ManagementEventKind;
38
+ /** 事件载荷:log→LogEventPayload;serviceState→ServiceStateChanged;operation→OperationRecord */
39
+ payload: LogEventPayload | ServiceStateChanged | OperationRecord;
40
+ }
41
+ /** 线缆帧 = 响应 | 事件 */
42
+ export type ManagementFrame = ManagementResponse | ManagementEvent;
43
+ /** events.subscribe 参数 */
44
+ export interface EventsSubscribeParams {
45
+ /** 仅订阅这些事件种类;缺省订阅全部 */
46
+ kinds?: ManagementEventKind[];
47
+ }
48
+ /** events.subscribe 结果 */
49
+ export interface EventsSubscribeResult {
50
+ subscribed: true;
51
+ kinds: ManagementEventKind[];
52
+ }
53
+ /** 类型守卫:帧是否为事件 */
54
+ export declare function isManagementEvent(frame: ManagementFrame): frame is ManagementEvent;
55
+ /** 序列化一帧为一行 NDJSON(不含换行符由调用方追加亦可,这里统一带上) */
56
+ export declare function encodeFrame(frame: ManagementFrame): string;
57
+ /** 解析一行 NDJSON 为帧;格式非法返回 null(调用方忽略坏行) */
58
+ export declare function decodeFrame(line: string): ManagementFrame | null;
59
+ //# sourceMappingURL=frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/protocol/frames.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,mBAAmB;AACnB,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,mBAAmB;AACnB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,6BAA6B;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kBAAkB;IAClB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,eAAe;AACf,MAAM,MAAM,mBAAmB,GAAG,KAAK,GAAG,cAAc,GAAG,WAAW,CAAC;AAEvE,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,mBAAmB,CAAC;IAC3B,0FAA0F;IAC1F,OAAO,EAAE,eAAe,GAAG,mBAAmB,GAAG,eAAe,CAAC;CAClE;AAED,oBAAoB;AACpB,MAAM,MAAM,eAAe,GAAG,kBAAkB,GAAG,eAAe,CAAC;AAEnE,0BAA0B;AAC1B,MAAM,WAAW,qBAAqB;IACpC,uBAAuB;IACvB,KAAK,CAAC,EAAE,mBAAmB,EAAE,CAAC;CAC/B;AAED,0BAA0B;AAC1B,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,IAAI,CAAC;IACjB,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAED,kBAAkB;AAClB,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,IAAI,eAAe,CAElF;AAED,4CAA4C;AAC5C,wBAAgB,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAE1D;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQhE"}
@@ -0,0 +1,21 @@
1
+ function isManagementEvent(frame) {
2
+ return frame.type === "event";
3
+ }
4
+ function encodeFrame(frame) {
5
+ return JSON.stringify(frame) + "\n";
6
+ }
7
+ function decodeFrame(line) {
8
+ try {
9
+ const v = JSON.parse(line);
10
+ if (v && (v.type === "res" || v.type === "event")) return v;
11
+ return null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+ export {
17
+ decodeFrame,
18
+ encodeFrame,
19
+ isManagementEvent
20
+ };
21
+ //# sourceMappingURL=frames.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/protocol/frames.ts"],
4
+ "sourcesContent": ["/**\n * frames.ts \u2014 \u7BA1\u7406\u534F\u8BAE\u5E27\u7ED3\u6784\uFF08\u6362\u884C\u5206\u9694 JSON\uFF0C\u4E0E\u5B88\u62A4\u8FDB\u7A0B\u534F\u8BAE\u540C\u6784\uFF09\n *\n * \u4F7F\u7528\u573A\u666F\uFF1A`sfmc manage --stdio` \u7684\u6807\u51C6\u8F93\u5165/\u8F93\u51FA\u3001\u4EE5\u53CA\u684C\u9762\u7AEF SSH \u901A\u9053\u4E0A\n * \u4F20\u8F93\u7684\u5E27\u683C\u5F0F\u3002\u8BF7\u6C42/\u54CD\u5E94\u6309 id \u914D\u5BF9\uFF1B\u8BA2\u9605\u540E\u670D\u52A1\u7AEF\u53EF\u63A8\u9001 event \u5E27\u3002\n * \u8BCA\u65AD\u4FE1\u606F\u4E00\u5F8B\u8D70 stderr\uFF0C\u4E0D\u5F97\u6DF7\u5165 stdout \u5E27\u6D41\u3002\n */\n\nimport type { ManagementError } from \"./errors.js\";\nimport type { LogEventPayload } from \"./logs.js\";\nimport type { OperationRecord } from \"./operations.js\";\nimport type { ServiceStateChanged } from \"./services.js\";\n\n/** \u5BA2\u6237\u7AEF \u2192 \u670D\u52A1\u7AEF\u8BF7\u6C42\u5E27 */\nexport interface ManagementRequest {\n /** \u8BF7\u6C42\u6807\u8BC6\uFF08\u5BA2\u6237\u7AEF\u751F\u6210\uFF0C\u54CD\u5E94\u539F\u6837\u5E26\u56DE\uFF09 */\n id: string;\n /** \u65B9\u6CD5\u540D\uFF08\u89C1 methods.ts \u7684 ManagementMethodMap\uFF09 */\n method: string;\n /** \u65B9\u6CD5\u53C2\u6570\uFF08\u6309 method \u800C\u5B9A\uFF09 */\n params?: unknown;\n}\n\n/** \u670D\u52A1\u7AEF \u2192 \u5BA2\u6237\u7AEF\u54CD\u5E94\u5E27 */\nexport interface ManagementResponse {\n type: \"res\";\n id: string;\n ok: boolean;\n /** \u6210\u529F\u65F6\u7684\u7ED3\u6784\u5316\u7ED3\u679C\uFF08\u6309 method \u800C\u5B9A\uFF09 */\n result?: unknown;\n /** \u5931\u8D25\u65F6\u7684\u7ED3\u6784\u5316\u9519\u8BEF\u4FE1\u5C01 */\n error?: ManagementError;\n}\n\n/** \u53EF\u8BA2\u9605\u7684\u4E8B\u4EF6\u79CD\u7C7B */\nexport type ManagementEventKind = \"log\" | \"serviceState\" | \"operation\";\n\n/** \u670D\u52A1\u7AEF \u2192 \u5BA2\u6237\u7AEF\u4E8B\u4EF6\u63A8\u9001\u5E27\uFF08events.subscribe \u4E4B\u540E\uFF09 */\nexport interface ManagementEvent {\n instanceId: string;\n type: \"event\";\n event: ManagementEventKind;\n /** \u4E8B\u4EF6\u8F7D\u8377\uFF1Alog\u2192LogEventPayload\uFF1BserviceState\u2192ServiceStateChanged\uFF1Boperation\u2192OperationRecord */\n payload: LogEventPayload | ServiceStateChanged | OperationRecord;\n}\n\n/** \u7EBF\u7F06\u5E27 = \u54CD\u5E94 | \u4E8B\u4EF6 */\nexport type ManagementFrame = ManagementResponse | ManagementEvent;\n\n/** events.subscribe \u53C2\u6570 */\nexport interface EventsSubscribeParams {\n /** \u4EC5\u8BA2\u9605\u8FD9\u4E9B\u4E8B\u4EF6\u79CD\u7C7B\uFF1B\u7F3A\u7701\u8BA2\u9605\u5168\u90E8 */\n kinds?: ManagementEventKind[];\n}\n\n/** events.subscribe \u7ED3\u679C */\nexport interface EventsSubscribeResult {\n subscribed: true;\n kinds: ManagementEventKind[];\n}\n\n/** \u7C7B\u578B\u5B88\u536B\uFF1A\u5E27\u662F\u5426\u4E3A\u4E8B\u4EF6 */\nexport function isManagementEvent(frame: ManagementFrame): frame is ManagementEvent {\n return frame.type === \"event\";\n}\n\n/** \u5E8F\u5217\u5316\u4E00\u5E27\u4E3A\u4E00\u884C NDJSON\uFF08\u4E0D\u542B\u6362\u884C\u7B26\u7531\u8C03\u7528\u65B9\u8FFD\u52A0\u4EA6\u53EF\uFF0C\u8FD9\u91CC\u7EDF\u4E00\u5E26\u4E0A\uFF09 */\nexport function encodeFrame(frame: ManagementFrame): string {\n return JSON.stringify(frame) + \"\\n\";\n}\n\n/** \u89E3\u6790\u4E00\u884C NDJSON \u4E3A\u5E27\uFF1B\u683C\u5F0F\u975E\u6CD5\u8FD4\u56DE null\uFF08\u8C03\u7528\u65B9\u5FFD\u7565\u574F\u884C\uFF09 */\nexport function decodeFrame(line: string): ManagementFrame | null {\n try {\n const v = JSON.parse(line) as ManagementFrame;\n if (v && (v.type === \"res\" || v.type === \"event\")) return v;\n return null;\n } catch {\n return null;\n }\n}\n"],
5
+ "mappings": "AA8DO,SAAS,kBAAkB,OAAkD;AAClF,SAAO,MAAM,SAAS;AACxB;AAGO,SAAS,YAAY,OAAgC;AAC1D,SAAO,KAAK,UAAU,KAAK,IAAI;AACjC;AAGO,SAAS,YAAY,MAAsC;AAChE,MAAI;AACF,UAAM,IAAI,KAAK,MAAM,IAAI;AACzB,QAAI,MAAM,EAAE,SAAS,SAAS,EAAE,SAAS,SAAU,QAAO;AAC1D,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * handshake.ts — 协议握手与能力发现类型
3
+ *
4
+ * 使用场景:桌面端建立连接后的第一个请求(handshake),
5
+ * 依据返回的协议版本、宿主系统与能力列表开放对应界面功能。
6
+ */
7
+ /** 宿主操作系统标识(windows / linux;其余值为兜底,界面需做兼容检测) */
8
+ export type HostOs = "windows" | "linux" | (string & {});
9
+ /** 服务端能力标识;握手返回可用能力列表,界面据此开放功能入口 */
10
+ export type ManagementCapability = "services" | "logs" | "modules" | "config" | "packs" | "players" | "updates" | "operations" | "metrics";
11
+ /** handshake 方法结果 */
12
+ export interface Handshake {
13
+ legacy?: boolean;
14
+ /** 管理协议版本(见 version.ts) */
15
+ protocolVersion: number;
16
+ /** 平台版本(服务端 CLI 包版本) */
17
+ platformVersion: string;
18
+ /** 宿主系统信息 */
19
+ host: {
20
+ os: HostOs;
21
+ arch: string;
22
+ release: string;
23
+ };
24
+ /** 部署根目录(SFMC_ROOT) */
25
+ root: string;
26
+ /** 当前可用能力列表 */
27
+ capabilities: ManagementCapability[];
28
+ /** 守护进程 PID(管理操作的实际执行者) */
29
+ daemonPid: number;
30
+ /** 守护进程启动时间(ISO) */
31
+ daemonStartedAt: string;
32
+ /** 部署是否已完成初始化向导(未初始化时界面应引导新建/接入流程) */
33
+ initialized: boolean;
34
+ }
35
+ //# sourceMappingURL=handshake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/protocol/handshake.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,gDAAgD;AAChD,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEzD,oCAAoC;AACpC,MAAM,MAAM,oBAAoB,GAC5B,UAAU,GACV,MAAM,GACN,SAAS,GACT,QAAQ,GACR,OAAO,GACP,SAAS,GACT,SAAS,GACT,YAAY,GACZ,SAAS,CAAC;AAEd,qBAAqB;AACrB,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,wBAAwB;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa;IACb,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe;IACf,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,WAAW,EAAE,OAAO,CAAC;CACtB"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * logs.ts — 日志查询与实时推送的协议类型
3
+ *
4
+ * 使用场景:桌面端日志控制台(logs.tail 拉历史 + 订阅 log 事件收实时增量)。
5
+ * 日志条目结构与 CLI 统一日志层(UnifiedLog)对齐,时间为 ISO 字符串(线缆格式)。
6
+ */
7
+ /** 日志级别(与 @sfmc-bds/sdk/logs 的 LogLevel 对齐) */
8
+ export type LogLevel = "info" | "warn" | "error" | "success" | "debug";
9
+ /** 日志来源标识(bds / db / qq / llbot / tunnel / system / update / pack / module …) */
10
+ export type LogSource = string;
11
+ /** 单条日志(线缆格式;time 为 ISO 字符串) */
12
+ export interface LogEntryWire {
13
+ time: string;
14
+ source: LogSource;
15
+ level: LogLevel;
16
+ text: string;
17
+ }
18
+ /** logs.tail 参数:拉取落盘历史日志(权威来源 `.sfmc/logs/*.log`) */
19
+ export interface LogsTailParams {
20
+ /** 仅包含这些来源;缺省为全部 */
21
+ sources?: LogSource[];
22
+ /** 仅包含这些级别;缺省为全部 */
23
+ levels?: LogLevel[];
24
+ /** 仅保留时间序最后 N 条;缺省由服务端给上限 */
25
+ limit?: number;
26
+ }
27
+ /** logs.tail 结果 */
28
+ export interface LogsTailResult {
29
+ entries: LogEntryWire[];
30
+ }
31
+ /** 实时日志推送载荷(event: "log") */
32
+ export type LogEventPayload = LogEntryWire;
33
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/protocol/logs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,+CAA+C;AAC/C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAEvE,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,gCAAgC;AAChC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,oBAAoB;IACpB,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;IACtB,oBAAoB;IACpB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mBAAmB;AACnB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,6BAA6B;AAC7B,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,101 @@
1
+ import type { Handshake } from "./handshake.js";
2
+ import type { OperationAccepted, OperationsGetResult, OperationsListResult } from "./operations.js";
3
+ import type { LogsTailResult } from "./logs.js";
4
+ import type { ServicesListResult } from "./services.js";
5
+ import type { MetricsResult } from "./metrics.js";
6
+ export interface ConfigDocument {
7
+ key: string;
8
+ text: string;
9
+ revision: string;
10
+ schema?: object;
11
+ format: "json" | "jsonc" | "properties";
12
+ affectedServices: string[];
13
+ }
14
+ export interface ModuleRow {
15
+ id: string;
16
+ folder: string;
17
+ name: string;
18
+ version: string;
19
+ enabled: boolean;
20
+ linked: boolean;
21
+ }
22
+ export interface PackRow {
23
+ id: string;
24
+ name: string;
25
+ kind: "behavior" | "resource";
26
+ enabled: boolean;
27
+ version: string;
28
+ }
29
+ export interface PlayerRow {
30
+ name: string;
31
+ xuid: string;
32
+ online: boolean | null;
33
+ }
34
+ export interface AttachmentPlan {
35
+ currentVersion: string;
36
+ targetVersion: string;
37
+ development: boolean;
38
+ upgradeRequired: boolean;
39
+ externalServices: string[];
40
+ steps: string[];
41
+ }
42
+ export interface ManagementMethodMap {
43
+ handshake: Handshake;
44
+ "services.list": ServicesListResult;
45
+ "services.start": OperationAccepted;
46
+ "services.stop": OperationAccepted;
47
+ "services.restart": OperationAccepted;
48
+ "services.send": {
49
+ delivered: boolean;
50
+ };
51
+ "events.subscribe": {
52
+ subscribed: true;
53
+ };
54
+ "logs.tail": LogsTailResult;
55
+ "modules.list": {
56
+ modules: ModuleRow[];
57
+ };
58
+ "modules.search": {
59
+ modules: unknown[];
60
+ };
61
+ "modules.install": OperationAccepted;
62
+ "modules.uninstall": OperationAccepted;
63
+ "modules.toggle": OperationAccepted;
64
+ "config.list": {
65
+ keys: string[];
66
+ };
67
+ "config.read": ConfigDocument;
68
+ "config.apply": OperationAccepted;
69
+ "packs.list": {
70
+ packs: PackRow[];
71
+ };
72
+ "packs.import": OperationAccepted;
73
+ "packs.toggle": OperationAccepted;
74
+ "players.list": {
75
+ players: PlayerRow[];
76
+ updatedAt: string;
77
+ fresh: boolean;
78
+ allowlist: unknown[];
79
+ permissions: unknown[];
80
+ sfmcPermissions: unknown[];
81
+ };
82
+ "players.apply": OperationAccepted;
83
+ "metrics.read": MetricsResult;
84
+ "updates.check": unknown;
85
+ "updates.run": OperationAccepted;
86
+ "attachment.plan": AttachmentPlan;
87
+ "attachment.apply": OperationAccepted;
88
+ "deployment.create": OperationAccepted;
89
+ "operations.list": OperationsListResult;
90
+ "operations.get": OperationsGetResult;
91
+ "backups.list": {
92
+ backups: string[];
93
+ };
94
+ "backups.restore": OperationAccepted;
95
+ "startup.plan": {
96
+ script: string;
97
+ filename: string;
98
+ };
99
+ }
100
+ export type ManagementMethod = keyof ManagementMethodMap;
101
+ //# sourceMappingURL=methods.d.ts.map