@sfmc-bds/cli 0.2.2 → 0.2.3

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/dist/command-surface.d.ts.map +1 -1
  2. package/dist/command-surface.js +2 -0
  3. package/dist/command-surface.js.map +2 -2
  4. package/dist/commands.d.ts.map +1 -1
  5. package/dist/commands.js +43 -12
  6. package/dist/commands.js.map +2 -2
  7. package/dist/daemon/client.d.ts +67 -0
  8. package/dist/daemon/client.d.ts.map +1 -0
  9. package/dist/daemon/client.js +272 -0
  10. package/dist/daemon/client.js.map +7 -0
  11. package/dist/daemon/paths.d.ts +35 -0
  12. package/dist/daemon/paths.d.ts.map +1 -0
  13. package/dist/daemon/paths.js +69 -0
  14. package/dist/daemon/paths.js.map +7 -0
  15. package/dist/daemon/protocol.d.ts +66 -0
  16. package/dist/daemon/protocol.d.ts.map +1 -0
  17. package/dist/daemon/protocol.js +16 -0
  18. package/dist/daemon/protocol.js.map +7 -0
  19. package/dist/daemon/role.d.ts +13 -0
  20. package/dist/daemon/role.d.ts.map +1 -0
  21. package/dist/daemon/role.js +12 -0
  22. package/dist/daemon/role.js.map +7 -0
  23. package/dist/daemon/server.d.ts +12 -0
  24. package/dist/daemon/server.d.ts.map +1 -0
  25. package/dist/daemon/server.js +221 -0
  26. package/dist/daemon/server.js.map +7 -0
  27. package/dist/help-text.d.ts.map +1 -1
  28. package/dist/help-text.js +3 -1
  29. package/dist/help-text.js.map +2 -2
  30. package/dist/i18n/locales/en.d.ts +8 -1
  31. package/dist/i18n/locales/en.d.ts.map +1 -1
  32. package/dist/i18n/locales/en.js +9 -1
  33. package/dist/i18n/locales/en.js.map +2 -2
  34. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  35. package/dist/i18n/locales/zh-CN.js +9 -1
  36. package/dist/i18n/locales/zh-CN.js.map +2 -2
  37. package/dist/logs.d.ts +11 -1
  38. package/dist/logs.d.ts.map +1 -1
  39. package/dist/logs.js +8 -0
  40. package/dist/logs.js.map +2 -2
  41. package/dist/main.js +27 -4
  42. package/dist/main.js.map +2 -2
  43. package/dist/repl.d.ts.map +1 -1
  44. package/dist/repl.js +47 -31
  45. package/dist/repl.js.map +3 -3
  46. package/dist/runtime.d.ts +1 -0
  47. package/dist/runtime.d.ts.map +1 -1
  48. package/dist/runtime.js +2 -2
  49. package/dist/runtime.js.map +2 -2
  50. package/dist/services.d.ts +3 -1
  51. package/dist/services.d.ts.map +1 -1
  52. package/dist/services.js +17 -10
  53. package/dist/services.js.map +2 -2
  54. package/package.json +4 -4
@@ -0,0 +1,272 @@
1
+ import { isProcessAlive } from "@sfmc-bds/bds-tools/process-probe";
2
+ import { logsDir } from "@sfmc-bds/sdk/node/config";
3
+ import { spawn } from "node:child_process";
4
+ import { randomBytes } from "node:crypto";
5
+ import fs from "node:fs";
6
+ import net from "node:net";
7
+ import process from "node:process";
8
+ import { EventEmitter } from "node:events";
9
+ import { ingestRemoteLog } from "../logs.js";
10
+ import { ROOT } from "../runtime.js";
11
+ import {
12
+ clearDaemonMeta,
13
+ daemonLogPath,
14
+ daemonMetaPath,
15
+ readDaemonMeta
16
+ } from "./paths.js";
17
+ import { isLogPayload } from "./protocol.js";
18
+ import { isDaemonServer } from "./role.js";
19
+ const CALL_TIMEOUT_MS = 12e4;
20
+ const READY_TIMEOUT_MS = 15e3;
21
+ const READY_POLL_MS = 150;
22
+ let shared = null;
23
+ const remoteStateBus = new EventEmitter();
24
+ function onRemoteServiceStateChange(fn) {
25
+ remoteStateBus.on("state", fn);
26
+ return () => {
27
+ remoteStateBus.off("state", fn);
28
+ };
29
+ }
30
+ class DaemonConnection {
31
+ sock;
32
+ meta;
33
+ buf = "";
34
+ pending = /* @__PURE__ */ new Map();
35
+ subscribed = false;
36
+ closed = false;
37
+ constructor(sock, meta) {
38
+ this.sock = sock;
39
+ this.meta = meta;
40
+ sock.setEncoding("utf8");
41
+ sock.on("data", (chunk) => this.onData(chunk));
42
+ sock.on("close", () => this.onClose(new Error("daemon connection closed")));
43
+ sock.on("error", (e) => this.onClose(e instanceof Error ? e : new Error(String(e))));
44
+ }
45
+ onData(chunk) {
46
+ this.buf += chunk;
47
+ let idx;
48
+ while ((idx = this.buf.indexOf("\n")) >= 0) {
49
+ const line = this.buf.slice(0, idx).trim();
50
+ this.buf = this.buf.slice(idx + 1);
51
+ if (!line) continue;
52
+ let frame;
53
+ try {
54
+ frame = JSON.parse(line);
55
+ } catch {
56
+ continue;
57
+ }
58
+ if (frame.type === "event") {
59
+ this.handleEvent(frame);
60
+ continue;
61
+ }
62
+ if (frame.type === "res") {
63
+ const wait = this.pending.get(frame.id);
64
+ if (!wait) continue;
65
+ this.pending.delete(frame.id);
66
+ clearTimeout(wait.timer);
67
+ if (frame.ok && frame.result) wait.resolve(frame.result);
68
+ else wait.reject(new Error(frame.error ?? "daemon error"));
69
+ }
70
+ }
71
+ }
72
+ handleEvent(ev) {
73
+ if (ev.event === "log" && isLogPayload(ev.payload)) {
74
+ ingestRemoteLog(ev.payload.text, ev.payload.source, ev.payload.level);
75
+ return;
76
+ }
77
+ if (ev.event === "state") {
78
+ remoteStateBus.emit("state", ev.payload);
79
+ }
80
+ }
81
+ onClose(err) {
82
+ if (this.closed) return;
83
+ this.closed = true;
84
+ for (const [, wait] of this.pending) {
85
+ clearTimeout(wait.timer);
86
+ wait.reject(err);
87
+ }
88
+ this.pending.clear();
89
+ if (shared === this) shared = null;
90
+ }
91
+ /** 发送 RPC 并等待匹配 id 的响应 */
92
+ call(method, params, timeoutMs = CALL_TIMEOUT_MS) {
93
+ if (this.closed || this.sock.destroyed) {
94
+ return Promise.reject(new Error("daemon not connected"));
95
+ }
96
+ const id = randomBytes(8).toString("hex");
97
+ const req = params ? { id, token: this.meta.token, method, params } : { id, token: this.meta.token, method };
98
+ return new Promise((resolve, reject) => {
99
+ const timer = setTimeout(() => {
100
+ this.pending.delete(id);
101
+ reject(new Error(`daemon call timeout: ${method}`));
102
+ }, timeoutMs);
103
+ this.pending.set(id, { resolve, reject, timer });
104
+ try {
105
+ this.sock.write(JSON.stringify(req) + "\n");
106
+ } catch (e) {
107
+ this.pending.delete(id);
108
+ clearTimeout(timer);
109
+ reject(e instanceof Error ? e : new Error(String(e)));
110
+ }
111
+ });
112
+ }
113
+ /** 订阅日志与状态事件(同一连接上继续发 RPC) */
114
+ async subscribe() {
115
+ if (this.subscribed) return;
116
+ await this.call("subscribe");
117
+ this.subscribed = true;
118
+ }
119
+ /** 断开连接(不停守护进程) */
120
+ disconnect() {
121
+ this.closed = true;
122
+ try {
123
+ this.sock.end();
124
+ } catch {
125
+ }
126
+ if (shared === this) shared = null;
127
+ }
128
+ }
129
+ function connectSocket(pipe, timeoutMs = 3e3) {
130
+ return new Promise((resolve, reject) => {
131
+ const sock = net.createConnection(pipe);
132
+ const timer = setTimeout(() => {
133
+ sock.destroy();
134
+ reject(new Error("connect timeout"));
135
+ }, timeoutMs);
136
+ sock.once("connect", () => {
137
+ clearTimeout(timer);
138
+ resolve(sock);
139
+ });
140
+ sock.once("error", (e) => {
141
+ clearTimeout(timer);
142
+ reject(e);
143
+ });
144
+ });
145
+ }
146
+ async function tryConnectExisting() {
147
+ const meta = readDaemonMeta();
148
+ if (!meta) return null;
149
+ if (!await isProcessAlive(meta.pid)) {
150
+ clearDaemonMeta();
151
+ return null;
152
+ }
153
+ try {
154
+ const sock = await connectSocket(meta.pipe);
155
+ const conn = new DaemonConnection(sock, meta);
156
+ await conn.call("ping", void 0, 3e3);
157
+ return conn;
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ function spawnDaemonProcess() {
163
+ const entry = process.argv[1];
164
+ if (!entry) throw new Error("cannot resolve sfmc entry for daemon spawn");
165
+ fs.mkdirSync(logsDir(ROOT), { recursive: true });
166
+ const logFd = fs.openSync(daemonLogPath(), "a");
167
+ const child = spawn(process.execPath, [entry, "--daemon"], {
168
+ detached: true,
169
+ windowsHide: true,
170
+ stdio: ["ignore", logFd, logFd],
171
+ env: { ...process.env, SFMC_ROOT: ROOT },
172
+ cwd: ROOT
173
+ });
174
+ child.unref();
175
+ try {
176
+ fs.closeSync(logFd);
177
+ } catch {
178
+ }
179
+ }
180
+ async function ensureDaemon() {
181
+ if (isDaemonServer()) {
182
+ throw new Error("ensureDaemon must not run inside daemon server");
183
+ }
184
+ if (shared && !shared.closed) return shared;
185
+ let conn = await tryConnectExisting();
186
+ if (conn) {
187
+ shared = conn;
188
+ return conn;
189
+ }
190
+ const existing = readDaemonMeta();
191
+ if (existing && await isProcessAlive(existing.pid)) {
192
+ const deadline2 = Date.now() + READY_TIMEOUT_MS;
193
+ while (Date.now() < deadline2) {
194
+ await new Promise((r) => setTimeout(r, READY_POLL_MS));
195
+ conn = await tryConnectExisting();
196
+ if (conn) {
197
+ shared = conn;
198
+ return conn;
199
+ }
200
+ }
201
+ throw new Error(`daemon pid ${existing.pid} is alive but pipe is unreachable`);
202
+ }
203
+ clearDaemonMeta();
204
+ spawnDaemonProcess();
205
+ const deadline = Date.now() + READY_TIMEOUT_MS;
206
+ while (Date.now() < deadline) {
207
+ await new Promise((r) => setTimeout(r, READY_POLL_MS));
208
+ if (!fs.existsSync(daemonMetaPath())) continue;
209
+ conn = await tryConnectExisting();
210
+ if (conn) {
211
+ shared = conn;
212
+ return conn;
213
+ }
214
+ }
215
+ throw new Error("failed to start or connect sfmc daemon");
216
+ }
217
+ async function callDaemon(method, params) {
218
+ const conn = await ensureDaemon();
219
+ return conn.call(method, params);
220
+ }
221
+ async function ensureDaemonSubscription() {
222
+ const conn = await ensureDaemon();
223
+ await conn.subscribe();
224
+ return () => {
225
+ conn.disconnect();
226
+ };
227
+ }
228
+ async function shutdownDaemon() {
229
+ try {
230
+ const conn = await ensureDaemon();
231
+ await conn.call("shutdown");
232
+ conn.disconnect();
233
+ } catch (e) {
234
+ clearDaemonMeta();
235
+ return e instanceof Error ? e.message : String(e);
236
+ }
237
+ const deadline = Date.now() + 5e3;
238
+ while (Date.now() < deadline) {
239
+ if (!readDaemonMeta()) break;
240
+ await new Promise((r) => setTimeout(r, 100));
241
+ }
242
+ clearDaemonMeta();
243
+ return "";
244
+ }
245
+ async function queryRuntimeViaDaemon() {
246
+ const result = await callDaemon("status");
247
+ if (result.kind === "status") return result.rows;
248
+ return [];
249
+ }
250
+ async function statusTextViaDaemon() {
251
+ const result = await callDaemon("status");
252
+ if (result.kind === "status") return result.text;
253
+ if (result.kind === "text") return result.text;
254
+ return "";
255
+ }
256
+ async function textViaDaemon(method, params) {
257
+ const result = await callDaemon(method, params);
258
+ if (result.kind === "text") return result.text;
259
+ if (result.kind === "status") return result.text;
260
+ return "";
261
+ }
262
+ export {
263
+ callDaemon,
264
+ ensureDaemon,
265
+ ensureDaemonSubscription,
266
+ onRemoteServiceStateChange,
267
+ queryRuntimeViaDaemon,
268
+ shutdownDaemon,
269
+ statusTextViaDaemon,
270
+ textViaDaemon
271
+ };
272
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/daemon/client.ts"],
4
+ "sourcesContent": ["/**\n * client.ts \u2014 CLI \u4FA7\u5B88\u62A4\u8FDB\u7A0B\u5BA2\u6237\u7AEF\uFF1AensureDaemon + RPC + \u65E5\u5FD7\u8BA2\u9605\n *\n * \u82E5 `.sfmc/daemon.json` \u5BF9\u5E94\u8FDB\u7A0B\u4E0D\u5728\u6216\u7BA1\u9053\u4E0D\u53EF\u8FDE\uFF0C\u5219 spawn `sfmc --daemon`\n *\uFF08detached + windowsHide\uFF09\uFF0C\u518D\u8F6E\u8BE2\u76F4\u5230 ping \u6210\u529F\u3002\n */\n\nimport { isProcessAlive } from \"@sfmc-bds/bds-tools/process-probe\";\nimport { logsDir } from \"@sfmc-bds/sdk/node/config\";\nimport { spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport net from \"node:net\";\nimport process from \"node:process\";\nimport { EventEmitter } from \"node:events\";\nimport { ingestRemoteLog } from \"../logs.js\";\nimport { ROOT } from \"../runtime.js\";\nimport type { ServiceStateEvent, ServiceStatus } from \"../services.js\";\nimport {\n clearDaemonMeta,\n daemonLogPath,\n daemonMetaPath,\n readDaemonMeta,\n type DaemonMeta,\n} from \"./paths.js\";\nimport type {\n DaemonEvent,\n DaemonFrame,\n DaemonMethod,\n DaemonParams,\n DaemonRequest,\n DaemonResult,\n} from \"./protocol.js\";\nimport { isLogPayload } from \"./protocol.js\";\nimport { isDaemonServer } from \"./role.js\";\n\n/** \u5355\u6B21 RPC \u9ED8\u8BA4\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09 */\nconst CALL_TIMEOUT_MS = 120_000;\n/** ensureDaemon \u7B49\u5F85\u5B88\u62A4\u8FDB\u7A0B\u5C31\u7EEA\u7684\u603B\u65F6\u957F */\nconst READY_TIMEOUT_MS = 15_000;\n/** \u8F6E\u8BE2\u95F4\u9694 */\nconst READY_POLL_MS = 150;\n\n/** \u5171\u4EAB\u8FDE\u63A5\uFF08\u540C\u4E00 CLI \u8FDB\u7A0B\u590D\u7528\uFF09 */\nlet shared: DaemonConnection | null = null;\n\n/** \u672C\u5730\u72B6\u6001\u4E8B\u4EF6\u603B\u7EBF\uFF1A\u628A daemon \u63A8\u9001\u7684 state \u8F6C\u7ED9 CLI \u4FA7 onServiceStateChange \u8BA2\u9605\u8005 */\nconst remoteStateBus = new EventEmitter();\n\n/**\n * \u8BA2\u9605\u5B88\u62A4\u8FDB\u7A0B\u63A8\u9001\u7684\u670D\u52A1\u542F\u505C\u4E8B\u4EF6\uFF08CLI \u8FDB\u7A0B\u7528\uFF09\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1AREPL \u6839\u636E running \u96C6\u5408\u5237\u65B0\u53D1\u9001\u76EE\u6807 / \u670D\u52A1\u7A97\uFF0C\u65E0\u9700\u672C\u5730\u6301\u6709 ChildProcess\u3002\n */\nexport function onRemoteServiceStateChange(fn: (ev: ServiceStateEvent) => void): () => void {\n remoteStateBus.on(\"state\", fn);\n return () => {\n remoteStateBus.off(\"state\", fn);\n };\n}\n\n/** \u5355\u6761\u8FDE\u63A5\uFF1A\u8BF7\u6C42/\u54CD\u5E94\u5339\u914D + \u53EF\u9009\u4E8B\u4EF6\u8BA2\u9605 */\nclass DaemonConnection {\n private sock: net.Socket;\n private meta: DaemonMeta;\n private buf = \"\";\n private pending = new Map<\n string,\n { resolve: (r: DaemonResult) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }\n >();\n private subscribed = false;\n closed = false;\n\n constructor(sock: net.Socket, meta: DaemonMeta) {\n this.sock = sock;\n this.meta = meta;\n sock.setEncoding(\"utf8\");\n sock.on(\"data\", (chunk: string) => this.onData(chunk));\n sock.on(\"close\", () => this.onClose(new Error(\"daemon connection closed\")));\n sock.on(\"error\", (e) => this.onClose(e instanceof Error ? e : new Error(String(e))));\n }\n\n private onData(chunk: string): void {\n this.buf += chunk;\n let idx: number;\n while ((idx = this.buf.indexOf(\"\\n\")) >= 0) {\n const line = this.buf.slice(0, idx).trim();\n this.buf = this.buf.slice(idx + 1);\n if (!line) continue;\n let frame: DaemonFrame;\n try {\n frame = JSON.parse(line) as DaemonFrame;\n } catch {\n continue;\n }\n if (frame.type === \"event\") {\n this.handleEvent(frame);\n continue;\n }\n if (frame.type === \"res\") {\n const wait = this.pending.get(frame.id);\n if (!wait) continue;\n this.pending.delete(frame.id);\n clearTimeout(wait.timer);\n if (frame.ok && frame.result) wait.resolve(frame.result);\n else wait.reject(new Error(frame.error ?? \"daemon error\"));\n }\n }\n }\n\n private handleEvent(ev: DaemonEvent): void {\n if (ev.event === \"log\" && isLogPayload(ev.payload)) {\n ingestRemoteLog(ev.payload.text, ev.payload.source, ev.payload.level);\n return;\n }\n if (ev.event === \"state\") {\n remoteStateBus.emit(\"state\", ev.payload as ServiceStateEvent);\n }\n }\n\n private onClose(err: Error): void {\n if (this.closed) return;\n this.closed = true;\n for (const [, wait] of this.pending) {\n clearTimeout(wait.timer);\n wait.reject(err);\n }\n this.pending.clear();\n if (shared === this) shared = null;\n }\n\n /** \u53D1\u9001 RPC \u5E76\u7B49\u5F85\u5339\u914D id \u7684\u54CD\u5E94 */\n call(method: DaemonMethod, params?: DaemonParams, timeoutMs = CALL_TIMEOUT_MS): Promise<DaemonResult> {\n if (this.closed || this.sock.destroyed) {\n return Promise.reject(new Error(\"daemon not connected\"));\n }\n const id = randomBytes(8).toString(\"hex\");\n const req: DaemonRequest = params\n ? { id, token: this.meta.token, method, params }\n : { id, token: this.meta.token, method };\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`daemon call timeout: ${method}`));\n }, timeoutMs);\n this.pending.set(id, { resolve, reject, timer });\n try {\n this.sock.write(JSON.stringify(req) + \"\\n\");\n } catch (e) {\n this.pending.delete(id);\n clearTimeout(timer);\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }\n\n /** \u8BA2\u9605\u65E5\u5FD7\u4E0E\u72B6\u6001\u4E8B\u4EF6\uFF08\u540C\u4E00\u8FDE\u63A5\u4E0A\u7EE7\u7EED\u53D1 RPC\uFF09 */\n async subscribe(): Promise<void> {\n if (this.subscribed) return;\n await this.call(\"subscribe\");\n this.subscribed = true;\n }\n\n /** \u65AD\u5F00\u8FDE\u63A5\uFF08\u4E0D\u505C\u5B88\u62A4\u8FDB\u7A0B\uFF09 */\n disconnect(): void {\n this.closed = true;\n try {\n this.sock.end();\n } catch {\n /* ignore */\n }\n if (shared === this) shared = null;\n }\n}\n\nfunction connectSocket(pipe: string, timeoutMs = 3000): Promise<net.Socket> {\n return new Promise((resolve, reject) => {\n const sock = net.createConnection(pipe);\n const timer = setTimeout(() => {\n sock.destroy();\n reject(new Error(\"connect timeout\"));\n }, timeoutMs);\n sock.once(\"connect\", () => {\n clearTimeout(timer);\n resolve(sock);\n });\n sock.once(\"error\", (e) => {\n clearTimeout(timer);\n reject(e);\n });\n });\n}\n\n/**\n * \u5C1D\u8BD5\u8FDE\u63A5\u5DF2\u6709 daemon\uFF1B\u6210\u529F\u5219\u8FD4\u56DE\u8FDE\u63A5\uFF0C\u5931\u8D25\u8FD4\u56DE null\u3002\n */\nasync function tryConnectExisting(): Promise<DaemonConnection | null> {\n const meta = readDaemonMeta();\n if (!meta) return null;\n if (!(await isProcessAlive(meta.pid))) {\n clearDaemonMeta();\n return null;\n }\n try {\n const sock = await connectSocket(meta.pipe);\n const conn = new DaemonConnection(sock, meta);\n await conn.call(\"ping\", undefined, 3000);\n return conn;\n } catch {\n return null;\n }\n}\n\n/**\n * \u62C9\u8D77\u5B88\u62A4\u8FDB\u7A0B\u5B50\u8FDB\u7A0B\uFF08detached\uFF0C\u65E5\u5FD7\u5199\u5165 daemon.log\uFF09\u3002\n * \u5165\u53E3\u811A\u672C\u590D\u7528\u5F53\u524D CLI \u7684 argv[1]\uFF08\u5373 sfmc \u4E3B\u5165\u53E3\uFF09\u3002\n */\nfunction spawnDaemonProcess(): void {\n const entry = process.argv[1];\n if (!entry) throw new Error(\"cannot resolve sfmc entry for daemon spawn\");\n fs.mkdirSync(logsDir(ROOT), { recursive: true });\n const logFd = fs.openSync(daemonLogPath(), \"a\");\n const child = spawn(process.execPath, [entry, \"--daemon\"], {\n detached: true,\n windowsHide: true,\n stdio: [\"ignore\", logFd, logFd],\n env: { ...process.env, SFMC_ROOT: ROOT },\n cwd: ROOT,\n });\n child.unref();\n try {\n fs.closeSync(logFd);\n } catch {\n /* ignore */\n }\n}\n\n/**\n * \u786E\u4FDD\u5B88\u62A4\u8FDB\u7A0B\u53EF\u7528\u5E76\u8FD4\u56DE\u5171\u4EAB\u8FDE\u63A5\u3002\n * \u5B88\u62A4\u8FDB\u7A0B\u8FDB\u7A0B\u5185\u8C03\u7528\u4F1A\u629B\u9519\uFF08\u5E94\u76F4\u63A5\u8D70\u672C\u5730 cmd*\uFF09\u3002\n */\nexport async function ensureDaemon(): Promise<DaemonConnection> {\n if (isDaemonServer()) {\n throw new Error(\"ensureDaemon must not run inside daemon server\");\n }\n if (shared && !shared.closed) return shared;\n\n let conn = await tryConnectExisting();\n if (conn) {\n shared = conn;\n return conn;\n }\n\n const existing = readDaemonMeta();\n if (existing && (await isProcessAlive(existing.pid))) {\n /* PID \u4ECD\u5728\u4F46\u6682\u65F6\u8FDE\u4E0D\u4E0A\uFF1A\u77ED\u6682\u91CD\u8BD5\uFF0C\u7981\u6B62\u518D spawn \u7B2C\u4E8C\u5957\u5B88\u62A4\u8FDB\u7A0B */\n const deadline = Date.now() + READY_TIMEOUT_MS;\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, READY_POLL_MS));\n conn = await tryConnectExisting();\n if (conn) {\n shared = conn;\n return conn;\n }\n }\n throw new Error(`daemon pid ${existing.pid} is alive but pipe is unreachable`);\n }\n\n clearDaemonMeta();\n spawnDaemonProcess();\n const deadline = Date.now() + READY_TIMEOUT_MS;\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, READY_POLL_MS));\n if (!fs.existsSync(daemonMetaPath())) continue;\n conn = await tryConnectExisting();\n if (conn) {\n shared = conn;\n return conn;\n }\n }\n throw new Error(\"failed to start or connect sfmc daemon\");\n}\n\n/**\n * \u8C03\u7528\u5B88\u62A4\u8FDB\u7A0B\u65B9\u6CD5\uFF1B\u81EA\u52A8 ensureDaemon\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1ACLI \u4FA7 status/start/stop/send/update \u7B49\u8FD0\u7EF4\u547D\u4EE4\u7684\u7EDF\u4E00\u51FA\u53E3\u3002\n */\nexport async function callDaemon(method: DaemonMethod, params?: DaemonParams): Promise<DaemonResult> {\n const conn = await ensureDaemon();\n return conn.call(method, params);\n}\n\n/**\n * \u786E\u4FDD\u5DF2\u8FDE\u63A5\u5E76\u8BA2\u9605\u8FDC\u7A0B\u65E5\u5FD7/\u72B6\u6001\uFF1B\u8FD4\u56DE\u65AD\u5F00\u51FD\u6570\uFF08\u4E0D\u505C\u5B88\u62A4\u8FDB\u7A0B\uFF09\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1AREPL \u542F\u52A8\u65F6\u6302\u4E0A\u5B9E\u65F6\u65E5\u5FD7\u6D41\uFF0C\u9000\u51FA\u65F6\u53EA disconnect\u3002\n */\nexport async function ensureDaemonSubscription(): Promise<() => void> {\n const conn = await ensureDaemon();\n await conn.subscribe();\n return () => {\n conn.disconnect();\n };\n}\n\n/**\n * \u8BF7\u6C42\u5B88\u62A4\u8FDB\u7A0B stopAll \u540E\u9000\u51FA\uFF08`sfmc daemon stop`\uFF09\u3002\n */\nexport async function shutdownDaemon(): Promise<string> {\n try {\n const conn = await ensureDaemon();\n await conn.call(\"shutdown\");\n conn.disconnect();\n } catch (e) {\n /* \u5B88\u62A4\u8FDB\u7A0B\u53EF\u80FD\u5DF2\u4E0D\u5728 */\n clearDaemonMeta();\n return e instanceof Error ? e.message : String(e);\n }\n /* \u77ED\u6682\u7B49\u5F85 meta \u6E05\u7406 */\n const deadline = Date.now() + 5000;\n while (Date.now() < deadline) {\n if (!readDaemonMeta()) break;\n await new Promise((r) => setTimeout(r, 100));\n }\n clearDaemonMeta();\n return \"\";\n}\n\n/**\n * \u4ECE\u5B88\u62A4\u8FDB\u7A0B\u62C9\u53D6\u8FD0\u884C\u6001\u884C\uFF1B\u5931\u8D25\u65F6\u8FD4\u56DE\u7A7A\u6570\u7EC4\u7531\u8C03\u7528\u65B9\u964D\u7EA7\u3002\n */\nexport async function queryRuntimeViaDaemon(): Promise<ServiceStatus[]> {\n const result = await callDaemon(\"status\");\n if (result.kind === \"status\") return result.rows;\n return [];\n}\n\n/**\n * \u4ECE\u5B88\u62A4\u8FDB\u7A0B\u62C9\u53D6\u683C\u5F0F\u5316 status \u6587\u672C\u3002\n */\nexport async function statusTextViaDaemon(): Promise<string> {\n const result = await callDaemon(\"status\");\n if (result.kind === \"status\") return result.text;\n if (result.kind === \"text\") return result.text;\n return \"\";\n}\n\n/**\n * \u901A\u7528\u300C\u8FD4\u56DE text\u300D\u7C7B RPC \u8F85\u52A9\u3002\n */\nexport async function textViaDaemon(method: DaemonMethod, params?: DaemonParams): Promise<string> {\n const result = await callDaemon(method, params);\n if (result.kind === \"text\") return result.text;\n if (result.kind === \"status\") return result.text;\n return \"\";\n}\n"],
5
+ "mappings": "AAOA,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,OAAO,QAAQ;AACf,OAAO,SAAS;AAChB,OAAO,aAAa;AACpB,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAChC,SAAS,YAAY;AAErB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AASP,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAG/B,MAAM,kBAAkB;AAExB,MAAM,mBAAmB;AAEzB,MAAM,gBAAgB;AAGtB,IAAI,SAAkC;AAGtC,MAAM,iBAAiB,IAAI,aAAa;AAMjC,SAAS,2BAA2B,IAAiD;AAC1F,iBAAe,GAAG,SAAS,EAAE;AAC7B,SAAO,MAAM;AACX,mBAAe,IAAI,SAAS,EAAE;AAAA,EAChC;AACF;AAGA,MAAM,iBAAiB;AAAA,EACb;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN,UAAU,oBAAI,IAGpB;AAAA,EACM,aAAa;AAAA,EACrB,SAAS;AAAA,EAET,YAAY,MAAkB,MAAkB;AAC9C,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,YAAY,MAAM;AACvB,SAAK,GAAG,QAAQ,CAAC,UAAkB,KAAK,OAAO,KAAK,CAAC;AACrD,SAAK,GAAG,SAAS,MAAM,KAAK,QAAQ,IAAI,MAAM,0BAA0B,CAAC,CAAC;AAC1E,SAAK,GAAG,SAAS,CAAC,MAAM,KAAK,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;AAAA,EACrF;AAAA,EAEQ,OAAO,OAAqB;AAClC,SAAK,OAAO;AACZ,QAAI;AACJ,YAAQ,MAAM,KAAK,IAAI,QAAQ,IAAI,MAAM,GAAG;AAC1C,YAAM,OAAO,KAAK,IAAI,MAAM,GAAG,GAAG,EAAE,KAAK;AACzC,WAAK,MAAM,KAAK,IAAI,MAAM,MAAM,CAAC;AACjC,UAAI,CAAC,KAAM;AACX,UAAI;AACJ,UAAI;AACF,gBAAQ,KAAK,MAAM,IAAI;AAAA,MACzB,QAAQ;AACN;AAAA,MACF;AACA,UAAI,MAAM,SAAS,SAAS;AAC1B,aAAK,YAAY,KAAK;AACtB;AAAA,MACF;AACA,UAAI,MAAM,SAAS,OAAO;AACxB,cAAM,OAAO,KAAK,QAAQ,IAAI,MAAM,EAAE;AACtC,YAAI,CAAC,KAAM;AACX,aAAK,QAAQ,OAAO,MAAM,EAAE;AAC5B,qBAAa,KAAK,KAAK;AACvB,YAAI,MAAM,MAAM,MAAM,OAAQ,MAAK,QAAQ,MAAM,MAAM;AAAA,YAClD,MAAK,OAAO,IAAI,MAAM,MAAM,SAAS,cAAc,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YAAY,IAAuB;AACzC,QAAI,GAAG,UAAU,SAAS,aAAa,GAAG,OAAO,GAAG;AAClD,sBAAgB,GAAG,QAAQ,MAAM,GAAG,QAAQ,QAAQ,GAAG,QAAQ,KAAK;AACpE;AAAA,IACF;AACA,QAAI,GAAG,UAAU,SAAS;AACxB,qBAAe,KAAK,SAAS,GAAG,OAA4B;AAAA,IAC9D;AAAA,EACF;AAAA,EAEQ,QAAQ,KAAkB;AAChC,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,eAAW,CAAC,EAAE,IAAI,KAAK,KAAK,SAAS;AACnC,mBAAa,KAAK,KAAK;AACvB,WAAK,OAAO,GAAG;AAAA,IACjB;AACA,SAAK,QAAQ,MAAM;AACnB,QAAI,WAAW,KAAM,UAAS;AAAA,EAChC;AAAA;AAAA,EAGA,KAAK,QAAsB,QAAuB,YAAY,iBAAwC;AACpG,QAAI,KAAK,UAAU,KAAK,KAAK,WAAW;AACtC,aAAO,QAAQ,OAAO,IAAI,MAAM,sBAAsB,CAAC;AAAA,IACzD;AACA,UAAM,KAAK,YAAY,CAAC,EAAE,SAAS,KAAK;AACxC,UAAM,MAAqB,SACvB,EAAE,IAAI,OAAO,KAAK,KAAK,OAAO,QAAQ,OAAO,IAC7C,EAAE,IAAI,OAAO,KAAK,KAAK,OAAO,OAAO;AACzC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,QAAQ,OAAO,EAAE;AACtB,eAAO,IAAI,MAAM,wBAAwB,MAAM,EAAE,CAAC;AAAA,MACpD,GAAG,SAAS;AACZ,WAAK,QAAQ,IAAI,IAAI,EAAE,SAAS,QAAQ,MAAM,CAAC;AAC/C,UAAI;AACF,aAAK,KAAK,MAAM,KAAK,UAAU,GAAG,IAAI,IAAI;AAAA,MAC5C,SAAS,GAAG;AACV,aAAK,QAAQ,OAAO,EAAE;AACtB,qBAAa,KAAK;AAClB,eAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC;AAAA,MACtD;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,YAA2B;AAC/B,QAAI,KAAK,WAAY;AACrB,UAAM,KAAK,KAAK,WAAW;AAC3B,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAGA,aAAmB;AACjB,SAAK,SAAS;AACd,QAAI;AACF,WAAK,KAAK,IAAI;AAAA,IAChB,QAAQ;AAAA,IAER;AACA,QAAI,WAAW,KAAM,UAAS;AAAA,EAChC;AACF;AAEA,SAAS,cAAc,MAAc,YAAY,KAA2B;AAC1E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,OAAO,IAAI,iBAAiB,IAAI;AACtC,UAAM,QAAQ,WAAW,MAAM;AAC7B,WAAK,QAAQ;AACb,aAAO,IAAI,MAAM,iBAAiB,CAAC;AAAA,IACrC,GAAG,SAAS;AACZ,SAAK,KAAK,WAAW,MAAM;AACzB,mBAAa,KAAK;AAClB,cAAQ,IAAI;AAAA,IACd,CAAC;AACD,SAAK,KAAK,SAAS,CAAC,MAAM;AACxB,mBAAa,KAAK;AAClB,aAAO,CAAC;AAAA,IACV,CAAC;AAAA,EACH,CAAC;AACH;AAKA,eAAe,qBAAuD;AACpE,QAAM,OAAO,eAAe;AAC5B,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,CAAE,MAAM,eAAe,KAAK,GAAG,GAAI;AACrC,oBAAgB;AAChB,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,OAAO,MAAM,cAAc,KAAK,IAAI;AAC1C,UAAM,OAAO,IAAI,iBAAiB,MAAM,IAAI;AAC5C,UAAM,KAAK,KAAK,QAAQ,QAAW,GAAI;AACvC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,qBAA2B;AAClC,QAAM,QAAQ,QAAQ,KAAK,CAAC;AAC5B,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,4CAA4C;AACxE,KAAG,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,QAAM,QAAQ,GAAG,SAAS,cAAc,GAAG,GAAG;AAC9C,QAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,OAAO,UAAU,GAAG;AAAA,IACzD,UAAU;AAAA,IACV,aAAa;AAAA,IACb,OAAO,CAAC,UAAU,OAAO,KAAK;AAAA,IAC9B,KAAK,EAAE,GAAG,QAAQ,KAAK,WAAW,KAAK;AAAA,IACvC,KAAK;AAAA,EACP,CAAC;AACD,QAAM,MAAM;AACZ,MAAI;AACF,OAAG,UAAU,KAAK;AAAA,EACpB,QAAQ;AAAA,EAER;AACF;AAMA,eAAsB,eAA0C;AAC9D,MAAI,eAAe,GAAG;AACpB,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,MAAI,UAAU,CAAC,OAAO,OAAQ,QAAO;AAErC,MAAI,OAAO,MAAM,mBAAmB;AACpC,MAAI,MAAM;AACR,aAAS;AACT,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,eAAe;AAChC,MAAI,YAAa,MAAM,eAAe,SAAS,GAAG,GAAI;AAEpD,UAAMA,YAAW,KAAK,IAAI,IAAI;AAC9B,WAAO,KAAK,IAAI,IAAIA,WAAU;AAC5B,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AACrD,aAAO,MAAM,mBAAmB;AAChC,UAAI,MAAM;AACR,iBAAS;AACT,eAAO;AAAA,MACT;AAAA,IACF;AACA,UAAM,IAAI,MAAM,cAAc,SAAS,GAAG,mCAAmC;AAAA,EAC/E;AAEA,kBAAgB;AAChB,qBAAmB;AACnB,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AACrD,QAAI,CAAC,GAAG,WAAW,eAAe,CAAC,EAAG;AACtC,WAAO,MAAM,mBAAmB;AAChC,QAAI,MAAM;AACR,eAAS;AACT,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,IAAI,MAAM,wCAAwC;AAC1D;AAMA,eAAsB,WAAW,QAAsB,QAA8C;AACnG,QAAM,OAAO,MAAM,aAAa;AAChC,SAAO,KAAK,KAAK,QAAQ,MAAM;AACjC;AAMA,eAAsB,2BAAgD;AACpE,QAAM,OAAO,MAAM,aAAa;AAChC,QAAM,KAAK,UAAU;AACrB,SAAO,MAAM;AACX,SAAK,WAAW;AAAA,EAClB;AACF;AAKA,eAAsB,iBAAkC;AACtD,MAAI;AACF,UAAM,OAAO,MAAM,aAAa;AAChC,UAAM,KAAK,KAAK,UAAU;AAC1B,SAAK,WAAW;AAAA,EAClB,SAAS,GAAG;AAEV,oBAAgB;AAChB,WAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AAAA,EAClD;AAEA,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI,CAAC,eAAe,EAAG;AACvB,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,EAC7C;AACA,kBAAgB;AAChB,SAAO;AACT;AAKA,eAAsB,wBAAkD;AACtE,QAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,MAAI,OAAO,SAAS,SAAU,QAAO,OAAO;AAC5C,SAAO,CAAC;AACV;AAKA,eAAsB,sBAAuC;AAC3D,QAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,MAAI,OAAO,SAAS,SAAU,QAAO,OAAO;AAC5C,MAAI,OAAO,SAAS,OAAQ,QAAO,OAAO;AAC1C,SAAO;AACT;AAKA,eAAsB,cAAc,QAAsB,QAAwC;AAChG,QAAM,SAAS,MAAM,WAAW,QAAQ,MAAM;AAC9C,MAAI,OAAO,SAAS,OAAQ,QAAO,OAAO;AAC1C,MAAI,OAAO,SAAS,SAAU,QAAO,OAAO;AAC5C,SAAO;AACT;",
6
+ "names": ["deadline"]
7
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * paths.ts — 守护进程连接信息与管道路径
3
+ *
4
+ * 权威落盘:`<SFMC_ROOT>/.sfmc/daemon.json`(与 stateDir 一致)。
5
+ * 同一 ROOT 只允许一个守护进程;CLI 通过本文件解析管道并校验 token。
6
+ */
7
+ /** 守护进程元数据:PID、管道路径、鉴权 token */
8
+ export interface DaemonMeta {
9
+ /** 守护进程 PID */
10
+ pid: number;
11
+ /** 命名管道(Windows)或 Unix socket 绝对路径 */
12
+ pipe: string;
13
+ /** 连接鉴权 token(随机) */
14
+ token: string;
15
+ /** ISO 启动时间 */
16
+ startedAt: string;
17
+ }
18
+ /** 守护进程元数据文件路径 */
19
+ export declare function daemonMetaPath(root?: string): string;
20
+ /** 守护进程自身日志(stdout/stderr 追加) */
21
+ export declare function daemonLogPath(root?: string): string;
22
+ /**
23
+ * 为本机 ROOT 生成唯一管道名。
24
+ * Windows:`\\.\pipe\sfmc-<hash>`;POSIX:`.sfmc/daemon.sock`
25
+ */
26
+ export declare function daemonPipePath(root?: string): string;
27
+ /** 生成连接鉴权 token */
28
+ export declare function createDaemonToken(): string;
29
+ /** 读取守护进程元数据;文件缺失或损坏时返回 null */
30
+ export declare function readDaemonMeta(root?: string): DaemonMeta | null;
31
+ /** 写入守护进程元数据(启动 listen 成功后调用) */
32
+ export declare function writeDaemonMeta(meta: DaemonMeta, root?: string): void;
33
+ /** 清除守护进程元数据与 POSIX socket 文件 */
34
+ export declare function clearDaemonMeta(root?: string): void;
35
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/daemon/paths.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,gCAAgC;AAChC,MAAM,WAAW,UAAU;IACzB,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,kBAAkB;AAClB,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAa,GAAG,MAAM,CAE1D;AAED,iCAAiC;AACjC,wBAAgB,aAAa,CAAC,IAAI,GAAE,MAAa,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAa,GAAG,MAAM,CAM1D;AAED,mBAAmB;AACnB,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,gCAAgC;AAChC,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAa,GAAG,UAAU,GAAG,IAAI,CAuBrE;AAED,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAE,MAAa,GAAG,IAAI,CAK3E;AAED,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,IAAI,GAAE,MAAa,GAAG,IAAI,CAezD"}
@@ -0,0 +1,69 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { logsDir, stateDir } from "@sfmc-bds/sdk/node/config";
5
+ import { ROOT } from "../runtime.js";
6
+ function daemonMetaPath(root = ROOT) {
7
+ return path.join(stateDir(root), "daemon.json");
8
+ }
9
+ function daemonLogPath(root = ROOT) {
10
+ return path.join(logsDir(root), "daemon.log");
11
+ }
12
+ function daemonPipePath(root = ROOT) {
13
+ const hash = createHash("sha256").update(path.resolve(root)).digest("hex").slice(0, 16);
14
+ if (process.platform === "win32") {
15
+ return `\\\\.\\pipe\\sfmc-${hash}`;
16
+ }
17
+ return path.join(stateDir(root), "daemon.sock");
18
+ }
19
+ function createDaemonToken() {
20
+ return randomBytes(24).toString("hex");
21
+ }
22
+ function readDaemonMeta(root = ROOT) {
23
+ const file = daemonMetaPath(root);
24
+ try {
25
+ if (!fs.existsSync(file)) return null;
26
+ const raw = JSON.parse(fs.readFileSync(file, "utf-8"));
27
+ if (typeof raw.pid !== "number" || typeof raw.pipe !== "string" || typeof raw.token !== "string" || !raw.pipe || !raw.token) {
28
+ return null;
29
+ }
30
+ return {
31
+ pid: raw.pid,
32
+ pipe: raw.pipe,
33
+ token: raw.token,
34
+ startedAt: typeof raw.startedAt === "string" ? raw.startedAt : ""
35
+ };
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function writeDaemonMeta(meta, root = ROOT) {
41
+ const dir = stateDir(root);
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ fs.mkdirSync(logsDir(root), { recursive: true });
44
+ fs.writeFileSync(daemonMetaPath(root), JSON.stringify(meta, null, 2) + "\n", "utf-8");
45
+ }
46
+ function clearDaemonMeta(root = ROOT) {
47
+ const meta = readDaemonMeta(root);
48
+ try {
49
+ fs.unlinkSync(daemonMetaPath(root));
50
+ } catch {
51
+ }
52
+ if (process.platform !== "win32") {
53
+ const sock = meta?.pipe ?? daemonPipePath(root);
54
+ try {
55
+ if (fs.existsSync(sock)) fs.unlinkSync(sock);
56
+ } catch {
57
+ }
58
+ }
59
+ }
60
+ export {
61
+ clearDaemonMeta,
62
+ createDaemonToken,
63
+ daemonLogPath,
64
+ daemonMetaPath,
65
+ daemonPipePath,
66
+ readDaemonMeta,
67
+ writeDaemonMeta
68
+ };
69
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/daemon/paths.ts"],
4
+ "sourcesContent": ["/**\n * paths.ts \u2014 \u5B88\u62A4\u8FDB\u7A0B\u8FDE\u63A5\u4FE1\u606F\u4E0E\u7BA1\u9053\u8DEF\u5F84\n *\n * \u6743\u5A01\u843D\u76D8\uFF1A`<SFMC_ROOT>/.sfmc/daemon.json`\uFF08\u4E0E stateDir \u4E00\u81F4\uFF09\u3002\n * \u540C\u4E00 ROOT \u53EA\u5141\u8BB8\u4E00\u4E2A\u5B88\u62A4\u8FDB\u7A0B\uFF1BCLI \u901A\u8FC7\u672C\u6587\u4EF6\u89E3\u6790\u7BA1\u9053\u5E76\u6821\u9A8C token\u3002\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { logsDir, stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { ROOT } from \"../runtime.js\";\n\n/** \u5B88\u62A4\u8FDB\u7A0B\u5143\u6570\u636E\uFF1APID\u3001\u7BA1\u9053\u8DEF\u5F84\u3001\u9274\u6743 token */\nexport interface DaemonMeta {\n /** \u5B88\u62A4\u8FDB\u7A0B PID */\n pid: number;\n /** \u547D\u540D\u7BA1\u9053\uFF08Windows\uFF09\u6216 Unix socket \u7EDD\u5BF9\u8DEF\u5F84 */\n pipe: string;\n /** \u8FDE\u63A5\u9274\u6743 token\uFF08\u968F\u673A\uFF09 */\n token: string;\n /** ISO \u542F\u52A8\u65F6\u95F4 */\n startedAt: string;\n}\n\n/** \u5B88\u62A4\u8FDB\u7A0B\u5143\u6570\u636E\u6587\u4EF6\u8DEF\u5F84 */\nexport function daemonMetaPath(root: string = ROOT): string {\n return path.join(stateDir(root), \"daemon.json\");\n}\n\n/** \u5B88\u62A4\u8FDB\u7A0B\u81EA\u8EAB\u65E5\u5FD7\uFF08stdout/stderr \u8FFD\u52A0\uFF09 */\nexport function daemonLogPath(root: string = ROOT): string {\n return path.join(logsDir(root), \"daemon.log\");\n}\n\n/**\n * \u4E3A\u672C\u673A ROOT \u751F\u6210\u552F\u4E00\u7BA1\u9053\u540D\u3002\n * Windows\uFF1A`\\\\.\\pipe\\sfmc-<hash>`\uFF1BPOSIX\uFF1A`.sfmc/daemon.sock`\n */\nexport function daemonPipePath(root: string = ROOT): string {\n const hash = createHash(\"sha256\").update(path.resolve(root)).digest(\"hex\").slice(0, 16);\n if (process.platform === \"win32\") {\n return `\\\\\\\\.\\\\pipe\\\\sfmc-${hash}`;\n }\n return path.join(stateDir(root), \"daemon.sock\");\n}\n\n/** \u751F\u6210\u8FDE\u63A5\u9274\u6743 token */\nexport function createDaemonToken(): string {\n return randomBytes(24).toString(\"hex\");\n}\n\n/** \u8BFB\u53D6\u5B88\u62A4\u8FDB\u7A0B\u5143\u6570\u636E\uFF1B\u6587\u4EF6\u7F3A\u5931\u6216\u635F\u574F\u65F6\u8FD4\u56DE null */\nexport function readDaemonMeta(root: string = ROOT): DaemonMeta | null {\n const file = daemonMetaPath(root);\n try {\n if (!fs.existsSync(file)) return null;\n const raw = JSON.parse(fs.readFileSync(file, \"utf-8\")) as Partial<DaemonMeta>;\n if (\n typeof raw.pid !== \"number\" ||\n typeof raw.pipe !== \"string\" ||\n typeof raw.token !== \"string\" ||\n !raw.pipe ||\n !raw.token\n ) {\n return null;\n }\n return {\n pid: raw.pid,\n pipe: raw.pipe,\n token: raw.token,\n startedAt: typeof raw.startedAt === \"string\" ? raw.startedAt : \"\",\n };\n } catch {\n return null;\n }\n}\n\n/** \u5199\u5165\u5B88\u62A4\u8FDB\u7A0B\u5143\u6570\u636E\uFF08\u542F\u52A8 listen \u6210\u529F\u540E\u8C03\u7528\uFF09 */\nexport function writeDaemonMeta(meta: DaemonMeta, root: string = ROOT): void {\n const dir = stateDir(root);\n fs.mkdirSync(dir, { recursive: true });\n fs.mkdirSync(logsDir(root), { recursive: true });\n fs.writeFileSync(daemonMetaPath(root), JSON.stringify(meta, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/** \u6E05\u9664\u5B88\u62A4\u8FDB\u7A0B\u5143\u6570\u636E\u4E0E POSIX socket \u6587\u4EF6 */\nexport function clearDaemonMeta(root: string = ROOT): void {\n const meta = readDaemonMeta(root);\n try {\n fs.unlinkSync(daemonMetaPath(root));\n } catch {\n /* ignore */\n }\n if (process.platform !== \"win32\") {\n const sock = meta?.pipe ?? daemonPipePath(root);\n try {\n if (fs.existsSync(sock)) fs.unlinkSync(sock);\n } catch {\n /* ignore */\n }\n }\n}\n"],
5
+ "mappings": "AAOA,SAAS,YAAY,mBAAmB;AACxC,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,SAAS,gBAAgB;AAClC,SAAS,YAAY;AAed,SAAS,eAAe,OAAe,MAAc;AAC1D,SAAO,KAAK,KAAK,SAAS,IAAI,GAAG,aAAa;AAChD;AAGO,SAAS,cAAc,OAAe,MAAc;AACzD,SAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,YAAY;AAC9C;AAMO,SAAS,eAAe,OAAe,MAAc;AAC1D,QAAM,OAAO,WAAW,QAAQ,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACtF,MAAI,QAAQ,aAAa,SAAS;AAChC,WAAO,qBAAqB,IAAI;AAAA,EAClC;AACA,SAAO,KAAK,KAAK,SAAS,IAAI,GAAG,aAAa;AAChD;AAGO,SAAS,oBAA4B;AAC1C,SAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AACvC;AAGO,SAAS,eAAe,OAAe,MAAyB;AACrE,QAAM,OAAO,eAAe,IAAI;AAChC,MAAI;AACF,QAAI,CAAC,GAAG,WAAW,IAAI,EAAG,QAAO;AACjC,UAAM,MAAM,KAAK,MAAM,GAAG,aAAa,MAAM,OAAO,CAAC;AACrD,QACE,OAAO,IAAI,QAAQ,YACnB,OAAO,IAAI,SAAS,YACpB,OAAO,IAAI,UAAU,YACrB,CAAC,IAAI,QACL,CAAC,IAAI,OACL;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,KAAK,IAAI;AAAA,MACT,MAAM,IAAI;AAAA,MACV,OAAO,IAAI;AAAA,MACX,WAAW,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY;AAAA,IACjE;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,gBAAgB,MAAkB,OAAe,MAAY;AAC3E,QAAM,MAAM,SAAS,IAAI;AACzB,KAAG,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACrC,KAAG,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC/C,KAAG,cAAc,eAAe,IAAI,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,OAAO;AACtF;AAGO,SAAS,gBAAgB,OAAe,MAAY;AACzD,QAAM,OAAO,eAAe,IAAI;AAChC,MAAI;AACF,OAAG,WAAW,eAAe,IAAI,CAAC;AAAA,EACpC,QAAQ;AAAA,EAER;AACA,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAM,OAAO,MAAM,QAAQ,eAAe,IAAI;AAC9C,QAAI;AACF,UAAI,GAAG,WAAW,IAAI,EAAG,IAAG,WAAW,IAAI;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * protocol.ts — CLI ↔ 守护进程 换行分隔 JSON 协议类型
3
+ *
4
+ * 请求/响应成对匹配 id;subscribe 后服务端可推送 event 帧。
5
+ * 所有请求须携带与 daemon.json 一致的 token。
6
+ */
7
+ import type { UnifiedLog, LogLevel, LogSource } from "../logs.js";
8
+ import type { ServiceName, ServiceStateEvent, ServiceStatus } from "../services.js";
9
+ /** 协议方法名(固定集合,OCP:新增能力加此处与 server 分发) */
10
+ export type DaemonMethod = "ping" | "status" | "start" | "stop" | "restart" | "startAll" | "stopAll" | "send" | "update" | "subscribe" | "shutdown";
11
+ /** 客户端 → 服务端请求帧 */
12
+ export interface DaemonRequest {
13
+ id: string;
14
+ token: string;
15
+ method: DaemonMethod;
16
+ params?: DaemonParams;
17
+ }
18
+ /** 各方法参数(按 method 选用) */
19
+ export type DaemonParams = {
20
+ name?: string;
21
+ message?: string;
22
+ args?: string[];
23
+ };
24
+ /** 服务端 → 客户端响应帧 */
25
+ export interface DaemonResponse {
26
+ type: "res";
27
+ id: string;
28
+ ok: boolean;
29
+ result?: DaemonResult;
30
+ error?: string;
31
+ }
32
+ /** 服务端推送事件(subscribe 之后) */
33
+ export interface DaemonEvent {
34
+ type: "event";
35
+ event: "log" | "state";
36
+ payload: DaemonLogPayload | ServiceStateEvent;
37
+ }
38
+ /** 日志事件载荷(与 UnifiedLog 对齐,便于 CLI 侧 pushLog) */
39
+ export interface DaemonLogPayload {
40
+ text: string;
41
+ source: LogSource;
42
+ level: LogLevel;
43
+ time?: string;
44
+ }
45
+ /** status 等方法的结构化结果 */
46
+ export type DaemonResult = {
47
+ kind: "pong";
48
+ } | {
49
+ kind: "text";
50
+ text: string;
51
+ } | {
52
+ kind: "status";
53
+ text: string;
54
+ rows: ServiceStatus[];
55
+ } | {
56
+ kind: "subscribed";
57
+ } | {
58
+ kind: "shutdown";
59
+ };
60
+ export type DaemonFrame = DaemonResponse | DaemonEvent;
61
+ /** 类型守卫:日志事件载荷 */
62
+ export declare function isLogPayload(p: DaemonEvent["payload"]): p is DaemonLogPayload;
63
+ /** 从 UnifiedLog 构造可序列化日志载荷 */
64
+ export declare function toLogPayload(log: UnifiedLog): DaemonLogPayload;
65
+ export type { ServiceName, ServiceStateEvent, ServiceStatus };
66
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/daemon/protocol.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpF,yCAAyC;AACzC,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,QAAQ,GACR,OAAO,GACP,MAAM,GACN,SAAS,GACT,UAAU,GACV,SAAS,GACT,MAAM,GACN,QAAQ,GACR,WAAW,GACX,UAAU,CAAC;AAEf,mBAAmB;AACnB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,yBAAyB;AACzB,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,mBAAmB;AACnB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;CAC/C;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,QAAQ,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,uBAAuB;AACvB,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,aAAa,EAAE,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzB,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,WAAW,CAAC;AAEvD,kBAAkB;AAClB,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAE7E;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,CAO9D;AAED,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,16 @@
1
+ function isLogPayload(p) {
2
+ return typeof p.text === "string" && typeof p.source === "string";
3
+ }
4
+ function toLogPayload(log) {
5
+ return {
6
+ text: log.text,
7
+ source: log.source,
8
+ level: log.level,
9
+ time: log.time instanceof Date ? log.time.toISOString() : String(log.time ?? "")
10
+ };
11
+ }
12
+ export {
13
+ isLogPayload,
14
+ toLogPayload
15
+ };
16
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/daemon/protocol.ts"],
4
+ "sourcesContent": ["/**\n * protocol.ts \u2014 CLI \u2194 \u5B88\u62A4\u8FDB\u7A0B \u6362\u884C\u5206\u9694 JSON \u534F\u8BAE\u7C7B\u578B\n *\n * \u8BF7\u6C42/\u54CD\u5E94\u6210\u5BF9\u5339\u914D id\uFF1Bsubscribe \u540E\u670D\u52A1\u7AEF\u53EF\u63A8\u9001 event \u5E27\u3002\n * \u6240\u6709\u8BF7\u6C42\u987B\u643A\u5E26\u4E0E daemon.json \u4E00\u81F4\u7684 token\u3002\n */\n\nimport type { UnifiedLog, LogLevel, LogSource } from \"../logs.js\";\nimport type { ServiceName, ServiceStateEvent, ServiceStatus } from \"../services.js\";\n\n/** \u534F\u8BAE\u65B9\u6CD5\u540D\uFF08\u56FA\u5B9A\u96C6\u5408\uFF0COCP\uFF1A\u65B0\u589E\u80FD\u529B\u52A0\u6B64\u5904\u4E0E server \u5206\u53D1\uFF09 */\nexport type DaemonMethod =\n | \"ping\"\n | \"status\"\n | \"start\"\n | \"stop\"\n | \"restart\"\n | \"startAll\"\n | \"stopAll\"\n | \"send\"\n | \"update\"\n | \"subscribe\"\n | \"shutdown\";\n\n/** \u5BA2\u6237\u7AEF \u2192 \u670D\u52A1\u7AEF\u8BF7\u6C42\u5E27 */\nexport interface DaemonRequest {\n id: string;\n token: string;\n method: DaemonMethod;\n params?: DaemonParams;\n}\n\n/** \u5404\u65B9\u6CD5\u53C2\u6570\uFF08\u6309 method \u9009\u7528\uFF09 */\nexport type DaemonParams = {\n name?: string;\n message?: string;\n args?: string[];\n};\n\n/** \u670D\u52A1\u7AEF \u2192 \u5BA2\u6237\u7AEF\u54CD\u5E94\u5E27 */\nexport interface DaemonResponse {\n type: \"res\";\n id: string;\n ok: boolean;\n result?: DaemonResult;\n error?: string;\n}\n\n/** \u670D\u52A1\u7AEF\u63A8\u9001\u4E8B\u4EF6\uFF08subscribe \u4E4B\u540E\uFF09 */\nexport interface DaemonEvent {\n type: \"event\";\n event: \"log\" | \"state\";\n payload: DaemonLogPayload | ServiceStateEvent;\n}\n\n/** \u65E5\u5FD7\u4E8B\u4EF6\u8F7D\u8377\uFF08\u4E0E UnifiedLog \u5BF9\u9F50\uFF0C\u4FBF\u4E8E CLI \u4FA7 pushLog\uFF09 */\nexport interface DaemonLogPayload {\n text: string;\n source: LogSource;\n level: LogLevel;\n time?: string;\n}\n\n/** status \u7B49\u65B9\u6CD5\u7684\u7ED3\u6784\u5316\u7ED3\u679C */\nexport type DaemonResult =\n | { kind: \"pong\" }\n | { kind: \"text\"; text: string }\n | { kind: \"status\"; text: string; rows: ServiceStatus[] }\n | { kind: \"subscribed\" }\n | { kind: \"shutdown\" };\n\nexport type DaemonFrame = DaemonResponse | DaemonEvent;\n\n/** \u7C7B\u578B\u5B88\u536B\uFF1A\u65E5\u5FD7\u4E8B\u4EF6\u8F7D\u8377 */\nexport function isLogPayload(p: DaemonEvent[\"payload\"]): p is DaemonLogPayload {\n return typeof (p as DaemonLogPayload).text === \"string\" && typeof (p as DaemonLogPayload).source === \"string\";\n}\n\n/** \u4ECE UnifiedLog \u6784\u9020\u53EF\u5E8F\u5217\u5316\u65E5\u5FD7\u8F7D\u8377 */\nexport function toLogPayload(log: UnifiedLog): DaemonLogPayload {\n return {\n text: log.text,\n source: log.source,\n level: log.level,\n time: log.time instanceof Date ? log.time.toISOString() : String(log.time ?? \"\"),\n };\n}\n\nexport type { ServiceName, ServiceStateEvent, ServiceStatus };\n"],
5
+ "mappings": "AA0EO,SAAS,aAAa,GAAkD;AAC7E,SAAO,OAAQ,EAAuB,SAAS,YAAY,OAAQ,EAAuB,WAAW;AACvG;AAGO,SAAS,aAAa,KAAmC;AAC9D,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI;AAAA,IACX,MAAM,IAAI,gBAAgB,OAAO,IAAI,KAAK,YAAY,IAAI,OAAO,IAAI,QAAQ,EAAE;AAAA,EACjF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * role.ts — 标识当前进程是守护进程服务端还是 CLI 客户端
3
+ *
4
+ * 使用场景:commands / services 在导出入口处分流——守护进程内直接操作
5
+ * Service 子进程;CLI 进程则经本机管道 RPC 转发到守护进程。
6
+ */
7
+ /** 进程角色:cli 为默认客户端;daemon 为持有监管权的服务端 */
8
+ export type DaemonRole = "cli" | "daemon";
9
+ /** 将当前进程标记为守护进程服务端(仅 `--daemon` 入口调用一次) */
10
+ export declare function markDaemonServer(): void;
11
+ /** 当前是否为守护进程服务端(持有 ChildProcess 与自动重启定时器) */
12
+ export declare function isDaemonServer(): boolean;
13
+ //# sourceMappingURL=role.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role.d.ts","sourceRoot":"","sources":["../../src/daemon/role.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wCAAwC;AACxC,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,QAAQ,CAAC;AAI1C,2CAA2C;AAC3C,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,IAAI,OAAO,CAExC"}
@@ -0,0 +1,12 @@
1
+ let role = "cli";
2
+ function markDaemonServer() {
3
+ role = "daemon";
4
+ }
5
+ function isDaemonServer() {
6
+ return role === "daemon";
7
+ }
8
+ export {
9
+ isDaemonServer,
10
+ markDaemonServer
11
+ };
12
+ //# sourceMappingURL=role.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/daemon/role.ts"],
4
+ "sourcesContent": ["/**\n * role.ts \u2014 \u6807\u8BC6\u5F53\u524D\u8FDB\u7A0B\u662F\u5B88\u62A4\u8FDB\u7A0B\u670D\u52A1\u7AEF\u8FD8\u662F CLI \u5BA2\u6237\u7AEF\n *\n * \u4F7F\u7528\u573A\u666F\uFF1Acommands / services \u5728\u5BFC\u51FA\u5165\u53E3\u5904\u5206\u6D41\u2014\u2014\u5B88\u62A4\u8FDB\u7A0B\u5185\u76F4\u63A5\u64CD\u4F5C\n * Service \u5B50\u8FDB\u7A0B\uFF1BCLI \u8FDB\u7A0B\u5219\u7ECF\u672C\u673A\u7BA1\u9053 RPC \u8F6C\u53D1\u5230\u5B88\u62A4\u8FDB\u7A0B\u3002\n */\n\n/** \u8FDB\u7A0B\u89D2\u8272\uFF1Acli \u4E3A\u9ED8\u8BA4\u5BA2\u6237\u7AEF\uFF1Bdaemon \u4E3A\u6301\u6709\u76D1\u7BA1\u6743\u7684\u670D\u52A1\u7AEF */\nexport type DaemonRole = \"cli\" | \"daemon\";\n\nlet role: DaemonRole = \"cli\";\n\n/** \u5C06\u5F53\u524D\u8FDB\u7A0B\u6807\u8BB0\u4E3A\u5B88\u62A4\u8FDB\u7A0B\u670D\u52A1\u7AEF\uFF08\u4EC5 `--daemon` \u5165\u53E3\u8C03\u7528\u4E00\u6B21\uFF09 */\nexport function markDaemonServer(): void {\n role = \"daemon\";\n}\n\n/** \u5F53\u524D\u662F\u5426\u4E3A\u5B88\u62A4\u8FDB\u7A0B\u670D\u52A1\u7AEF\uFF08\u6301\u6709 ChildProcess \u4E0E\u81EA\u52A8\u91CD\u542F\u5B9A\u65F6\u5668\uFF09 */\nexport function isDaemonServer(): boolean {\n return role === \"daemon\";\n}\n"],
5
+ "mappings": "AAUA,IAAI,OAAmB;AAGhB,SAAS,mBAAyB;AACvC,SAAO;AACT;AAGO,SAAS,iBAA0B;AACxC,SAAO,SAAS;AAClB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * server.ts — 守护进程服务端:本机管道 listen + 将 RPC 分发给现有监管逻辑
3
+ *
4
+ * 仅在 `sfmc --daemon` 进程内运行;持有 Service 子进程、自动重启与 BDS stdin。
5
+ * CLI 退出不断开本进程;`shutdown` 方法会先 stopAll 再退出。
6
+ */
7
+ /**
8
+ * 启动守护进程:标记角色、listen 管道、落盘 daemon.json,并转发日志/状态事件。
9
+ * 本函数在成功 listen 后永不返回(进程常驻)。
10
+ */
11
+ export declare function runDaemonServer(): Promise<void>;
12
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyKH;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAsFrD"}