@sfmc-bds/cli 0.2.2 → 0.2.4

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 (70) hide show
  1. package/dist/command-palette.d.ts.map +1 -1
  2. package/dist/command-palette.js +11 -2
  3. package/dist/command-palette.js.map +2 -2
  4. package/dist/command-surface.d.ts +14 -2
  5. package/dist/command-surface.d.ts.map +1 -1
  6. package/dist/command-surface.js +120 -6
  7. package/dist/command-surface.js.map +2 -2
  8. package/dist/commands.d.ts.map +1 -1
  9. package/dist/commands.js +45 -38
  10. package/dist/commands.js.map +2 -2
  11. package/dist/daemon/client.d.ts +67 -0
  12. package/dist/daemon/client.d.ts.map +1 -0
  13. package/dist/daemon/client.js +272 -0
  14. package/dist/daemon/client.js.map +7 -0
  15. package/dist/daemon/paths.d.ts +35 -0
  16. package/dist/daemon/paths.d.ts.map +1 -0
  17. package/dist/daemon/paths.js +69 -0
  18. package/dist/daemon/paths.js.map +7 -0
  19. package/dist/daemon/protocol.d.ts +66 -0
  20. package/dist/daemon/protocol.d.ts.map +1 -0
  21. package/dist/daemon/protocol.js +16 -0
  22. package/dist/daemon/protocol.js.map +7 -0
  23. package/dist/daemon/role.d.ts +13 -0
  24. package/dist/daemon/role.d.ts.map +1 -0
  25. package/dist/daemon/role.js +12 -0
  26. package/dist/daemon/role.js.map +7 -0
  27. package/dist/daemon/server.d.ts +12 -0
  28. package/dist/daemon/server.d.ts.map +1 -0
  29. package/dist/daemon/server.js +221 -0
  30. package/dist/daemon/server.js.map +7 -0
  31. package/dist/help-text.d.ts.map +1 -1
  32. package/dist/help-text.js +3 -1
  33. package/dist/help-text.js.map +2 -2
  34. package/dist/i18n/locales/en.d.ts +25 -3
  35. package/dist/i18n/locales/en.d.ts.map +1 -1
  36. package/dist/i18n/locales/en.js +26 -3
  37. package/dist/i18n/locales/en.js.map +2 -2
  38. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  39. package/dist/i18n/locales/zh-CN.js +26 -3
  40. package/dist/i18n/locales/zh-CN.js.map +2 -2
  41. package/dist/logs.d.ts +11 -1
  42. package/dist/logs.d.ts.map +1 -1
  43. package/dist/logs.js +8 -0
  44. package/dist/logs.js.map +2 -2
  45. package/dist/main.js +27 -4
  46. package/dist/main.js.map +2 -2
  47. package/dist/module-commands.d.ts.map +1 -1
  48. package/dist/module-commands.js +9 -5
  49. package/dist/module-commands.js.map +2 -2
  50. package/dist/module-fingerprint.d.ts +5 -0
  51. package/dist/module-fingerprint.d.ts.map +1 -1
  52. package/dist/module-fingerprint.js +13 -3
  53. package/dist/module-fingerprint.js.map +2 -2
  54. package/dist/module-sidecars.d.ts +16 -0
  55. package/dist/module-sidecars.d.ts.map +1 -0
  56. package/dist/module-sidecars.js +116 -0
  57. package/dist/module-sidecars.js.map +7 -0
  58. package/dist/repl.d.ts.map +1 -1
  59. package/dist/repl.js +51 -32
  60. package/dist/repl.js.map +3 -3
  61. package/dist/runtime.d.ts +1 -0
  62. package/dist/runtime.d.ts.map +1 -1
  63. package/dist/runtime.js +2 -2
  64. package/dist/runtime.js.map +2 -2
  65. package/dist/services.d.ts +3 -1
  66. package/dist/services.d.ts.map +1 -1
  67. package/dist/services.js +22 -10
  68. package/dist/services.js.map +2 -2
  69. package/dist/world-packs.js.map +1 -1
  70. package/package.json +4 -4
@@ -1,6 +1,10 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ function shouldSkipModuleEntry(parentDirName, name) {
5
+ if (name.startsWith(".") || name === "node_modules" || name === "dist") return true;
6
+ return name === "data" && parentDirName === "map-server";
7
+ }
4
8
  async function dirFingerprint(rootDir) {
5
9
  const hash = createHash("sha256");
6
10
  const entries = [];
@@ -9,7 +13,7 @@ async function dirFingerprint(rootDir) {
9
13
  const items = await fs.readdir(full, { withFileTypes: true });
10
14
  const sorted = items.sort((a, b) => a.name.localeCompare(b.name));
11
15
  for (const it of sorted) {
12
- if (it.name.startsWith(".") || it.name === "node_modules" || it.name === "dist") continue;
16
+ if (shouldSkipModuleEntry(path.posix.basename(rel), it.name)) continue;
13
17
  const child = rel ? `${rel}/${it.name}` : it.name;
14
18
  if (it.isDirectory()) await walk(child);
15
19
  else if (it.isFile()) entries.push(child);
@@ -18,7 +22,12 @@ async function dirFingerprint(rootDir) {
18
22
  await walk("");
19
23
  entries.sort();
20
24
  for (const rel of entries) {
21
- const data = await fs.readFile(path.join(rootDir, rel));
25
+ let data;
26
+ try {
27
+ data = await fs.readFile(path.join(rootDir, rel));
28
+ } catch {
29
+ continue;
30
+ }
22
31
  hash.update(rel.replaceAll("\\", "/"));
23
32
  hash.update("\n");
24
33
  hash.update(data);
@@ -27,6 +36,7 @@ async function dirFingerprint(rootDir) {
27
36
  return hash.digest("hex");
28
37
  }
29
38
  export {
30
- dirFingerprint
39
+ dirFingerprint,
40
+ shouldSkipModuleEntry
31
41
  };
32
42
  //# sourceMappingURL=module-fingerprint.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/module-fingerprint.ts"],
4
- "sourcesContent": ["/**\n * module-fingerprint.ts \u2014 \u6A21\u5757\u76EE\u5F55 canonical SHA-256\n * (\u4E0E\u539F\u5148 module-commands \u5185 dirFingerprint \u7B97\u6CD5\u4E00\u81F4,\u4F9B catalog / verify \u5171\u7528)\u3002\n */\n\nimport { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/** Compute the canonical SHA-256 of a module directory (POSIX `find ... | sha256sum` compatible). */\nexport async function dirFingerprint(rootDir: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const entries: string[] = [];\n async function walk(rel: string): Promise<void> {\n const full = path.join(rootDir, rel);\n const items = await fs.readdir(full, { withFileTypes: true });\n const sorted = items.sort((a, b) => a.name.localeCompare(b.name));\n for (const it of sorted) {\n if (it.name.startsWith(\".\") || it.name === \"node_modules\" || it.name === \"dist\") continue;\n const child = rel ? `${rel}/${it.name}` : it.name;\n if (it.isDirectory()) await walk(child);\n else if (it.isFile()) entries.push(child);\n }\n }\n await walk(\"\");\n entries.sort();\n for (const rel of entries) {\n const data = await fs.readFile(path.join(rootDir, rel));\n hash.update(rel.replaceAll(\"\\\\\", \"/\"));\n hash.update(\"\\n\");\n hash.update(data);\n hash.update(\"\\n\");\n }\n return hash.digest(\"hex\");\n}\n"],
5
- "mappings": "AAKA,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,eAAsB,eAAe,SAAkC;AACrE,QAAM,OAAO,WAAW,QAAQ;AAChC,QAAM,UAAoB,CAAC;AAC3B,iBAAe,KAAK,KAA4B;AAC9C,UAAM,OAAO,KAAK,KAAK,SAAS,GAAG;AACnC,UAAM,QAAQ,MAAM,GAAG,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AAC5D,UAAM,SAAS,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChE,eAAW,MAAM,QAAQ;AACvB,UAAI,GAAG,KAAK,WAAW,GAAG,KAAK,GAAG,SAAS,kBAAkB,GAAG,SAAS,OAAQ;AACjF,YAAM,QAAQ,MAAM,GAAG,GAAG,IAAI,GAAG,IAAI,KAAK,GAAG;AAC7C,UAAI,GAAG,YAAY,EAAG,OAAM,KAAK,KAAK;AAAA,eAC7B,GAAG,OAAO,EAAG,SAAQ,KAAK,KAAK;AAAA,IAC1C;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACb,UAAQ,KAAK;AACb,aAAW,OAAO,SAAS;AACzB,UAAM,OAAO,MAAM,GAAG,SAAS,KAAK,KAAK,SAAS,GAAG,CAAC;AACtD,SAAK,OAAO,IAAI,WAAW,MAAM,GAAG,CAAC;AACrC,SAAK,OAAO,IAAI;AAChB,SAAK,OAAO,IAAI;AAChB,SAAK,OAAO,IAAI;AAAA,EAClB;AACA,SAAO,KAAK,OAAO,KAAK;AAC1B;",
4
+ "sourcesContent": ["/**\n * module-fingerprint.ts \u2014 \u6A21\u5757\u76EE\u5F55 canonical SHA-256\n * (\u4E0E\u539F\u5148 module-commands \u5185 dirFingerprint \u7B97\u6CD5\u4E00\u81F4,\u4F9B catalog / verify \u5171\u7528)\u3002\n */\n\nimport { createHash } from \"node:crypto\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * \u6A21\u5757\u76EE\u5F55\u626B\u63CF\u65F6\u8981\u8DF3\u8FC7\u7684\u540D\u5B57\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u6307\u7EB9\u548C\u4F53\u79EF\u7EDF\u8BA1\u5171\u7528\u3002map-server/data \u662F\u5730\u56FE\u5FEB\u7167\uFF0C\u626B\u8FDB\u53BB\u4F1A\u8BA9 CLI \u542F\u52A8\u5361\u6B7B\u3002\n */\nexport function shouldSkipModuleEntry(parentDirName: string, name: string): boolean {\n if (name.startsWith(\".\") || name === \"node_modules\" || name === \"dist\") return true;\n return name === \"data\" && parentDirName === \"map-server\";\n}\n\n/** Compute the canonical SHA-256 of a module directory (POSIX `find ... | sha256sum` compatible). */\nexport async function dirFingerprint(rootDir: string): Promise<string> {\n const hash = createHash(\"sha256\");\n const entries: string[] = [];\n async function walk(rel: string): Promise<void> {\n const full = path.join(rootDir, rel);\n const items = await fs.readdir(full, { withFileTypes: true });\n const sorted = items.sort((a, b) => a.name.localeCompare(b.name));\n for (const it of sorted) {\n if (shouldSkipModuleEntry(path.posix.basename(rel), it.name)) continue;\n const child = rel ? `${rel}/${it.name}` : it.name;\n if (it.isDirectory()) await walk(child);\n else if (it.isFile()) entries.push(child);\n }\n }\n await walk(\"\");\n entries.sort();\n for (const rel of entries) {\n let data: Buffer;\n try {\n data = await fs.readFile(path.join(rootDir, rel));\n } catch {\n // \u5355\u4E2A\u6587\u4EF6\u6253\u4E0D\u5F00\u65F6\u8DF3\u8FC7\uFF0C\u907F\u514D\u6574\u6B21\u542F\u52A8\u5931\u8D25\n continue;\n }\n hash.update(rel.replaceAll(\"\\\\\", \"/\"));\n hash.update(\"\\n\");\n hash.update(data);\n hash.update(\"\\n\");\n }\n return hash.digest(\"hex\");\n}\n"],
5
+ "mappings": "AAKA,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAO,UAAU;AAMV,SAAS,sBAAsB,eAAuB,MAAuB;AAClF,MAAI,KAAK,WAAW,GAAG,KAAK,SAAS,kBAAkB,SAAS,OAAQ,QAAO;AAC/E,SAAO,SAAS,UAAU,kBAAkB;AAC9C;AAGA,eAAsB,eAAe,SAAkC;AACrE,QAAM,OAAO,WAAW,QAAQ;AAChC,QAAM,UAAoB,CAAC;AAC3B,iBAAe,KAAK,KAA4B;AAC9C,UAAM,OAAO,KAAK,KAAK,SAAS,GAAG;AACnC,UAAM,QAAQ,MAAM,GAAG,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;AAC5D,UAAM,SAAS,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChE,eAAW,MAAM,QAAQ;AACvB,UAAI,sBAAsB,KAAK,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,EAAG;AAC9D,YAAM,QAAQ,MAAM,GAAG,GAAG,IAAI,GAAG,IAAI,KAAK,GAAG;AAC7C,UAAI,GAAG,YAAY,EAAG,OAAM,KAAK,KAAK;AAAA,eAC7B,GAAG,OAAO,EAAG,SAAQ,KAAK,KAAK;AAAA,IAC1C;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACb,UAAQ,KAAK;AACb,aAAW,OAAO,SAAS;AACzB,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,GAAG,SAAS,KAAK,KAAK,SAAS,GAAG,CAAC;AAAA,IAClD,QAAQ;AAEN;AAAA,IACF;AACA,SAAK,OAAO,IAAI,WAAW,MAAM,GAAG,CAAC;AACrC,SAAK,OAAO,IAAI;AAChB,SAAK,OAAO,IAAI;AAChB,SAAK,OAAO,IAAI;AAAA,EAClB;AACA,SAAO,KAAK,OAAO,KAAK;AAC1B;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 模块随 BDS 启动的附属进程。
3
+ * 使用场景:开服前扫描 modules/packages/<id>/sidecar.json。
4
+ * 目前 Bluemap 用它拉起地图进程;日志正文由脚本打进 BDS,这里只报告启动和退出。
5
+ */
6
+ /**
7
+ * 在 BDS 进程拉起之前启动附属进程。
8
+ * 使用场景:services.ts 里 bds 的 start,在 beforeStart 之后调用。
9
+ */
10
+ export declare function startModuleSidecars(root: string, log: (line: string, stream: "stdout" | "stderr") => void): Promise<void>;
11
+ /**
12
+ * 结束本轮由开服拉起的附属进程。
13
+ * 使用场景:BDS 服务 cleanup 时调用。用户自己另开的地图进程不会被关掉。
14
+ */
15
+ export declare function stopModuleSidecars(): void;
16
+ //# sourceMappingURL=module-sidecars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-sidecars.d.ts","sourceRoot":"","sources":["../src/module-sidecars.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA+FH;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,KAAK,IAAI,GACvD,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CASzC"}
@@ -0,0 +1,116 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import net from "node:net";
4
+ import path from "node:path";
5
+ const children = /* @__PURE__ */ new Map();
6
+ function readJson(file) {
7
+ try {
8
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
9
+ return parsed && typeof parsed === "object" ? parsed : null;
10
+ } catch {
11
+ return null;
12
+ }
13
+ }
14
+ function readPath(source, dotted) {
15
+ let current = source;
16
+ for (const part of dotted.split(".")) {
17
+ if (!current || typeof current !== "object") return void 0;
18
+ current = current[part];
19
+ }
20
+ return current;
21
+ }
22
+ function isEnabled(root, spec) {
23
+ if (!spec.when || !spec.configFile) return true;
24
+ const config = readJson(path.join(root, spec.configFile));
25
+ if (!config) return true;
26
+ const flag = readPath(config, spec.when);
27
+ return flag !== false;
28
+ }
29
+ function configuredPort(root, spec) {
30
+ if (!spec.configFile) return 0;
31
+ const config = readJson(path.join(root, spec.configFile));
32
+ const port = config?.port;
33
+ return typeof port === "number" && port > 0 && port < 65536 ? port : 0;
34
+ }
35
+ function portOpen(port) {
36
+ if (!port) return Promise.resolve(false);
37
+ return new Promise((resolve) => {
38
+ const socket = net.connect({ host: "127.0.0.1", port });
39
+ const done = (open) => {
40
+ socket.removeAllListeners();
41
+ socket.destroy();
42
+ resolve(open);
43
+ };
44
+ socket.once("connect", () => done(true));
45
+ socket.once("error", () => done(false));
46
+ socket.setTimeout(500, () => done(false));
47
+ });
48
+ }
49
+ function loadSpecs(root) {
50
+ const packagesDir = path.join(root, "modules", "packages");
51
+ if (!fs.existsSync(packagesDir)) return [];
52
+ const found = [];
53
+ for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {
54
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
55
+ const dir = path.join(packagesDir, entry.name);
56
+ const file = path.join(dir, "sidecar.json");
57
+ if (!fs.existsSync(file)) continue;
58
+ const raw = readJson(file);
59
+ if (!raw || typeof raw.id !== "string" || typeof raw.entry !== "string") continue;
60
+ const lock = readJson(path.join(root, "modules", "module-lock.json"));
61
+ const modules = lock?.modules && typeof lock.modules === "object" ? lock.modules : {};
62
+ const row = modules[raw.id];
63
+ const enabled = !row || typeof row !== "object" || row.enabled !== false;
64
+ if (!enabled) continue;
65
+ const spec = { id: raw.id, entry: raw.entry };
66
+ if (typeof raw.configFile === "string") spec.configFile = raw.configFile;
67
+ if (typeof raw.when === "string") spec.when = raw.when;
68
+ found.push({ spec, dir });
69
+ }
70
+ return found;
71
+ }
72
+ async function startModuleSidecars(root, log) {
73
+ for (const { spec, dir } of loadSpecs(root)) {
74
+ if (children.has(spec.id)) continue;
75
+ if (!isEnabled(root, spec)) continue;
76
+ const entry = path.join(dir, spec.entry);
77
+ if (!fs.existsSync(entry)) {
78
+ log(`[${spec.id}] \u672A\u627E\u5230\u9644\u5C5E\u8FDB\u7A0B\u5165\u53E3: ${entry}`, "stderr");
79
+ continue;
80
+ }
81
+ const port = configuredPort(root, spec);
82
+ if (await portOpen(port)) {
83
+ log(`[${spec.id}] \u7AEF\u53E3 ${port} \u5DF2\u5728\u76D1\u542C\uFF0C\u8DF3\u8FC7\u542F\u52A8\u3002`, "stdout");
84
+ continue;
85
+ }
86
+ const child = spawn(process.execPath, [entry], {
87
+ cwd: dir,
88
+ env: { ...process.env, SFMC_ROOT: root },
89
+ windowsHide: true,
90
+ stdio: ["ignore", "pipe", "pipe"]
91
+ });
92
+ children.set(spec.id, child);
93
+ log(`[${spec.id}] \u5DF2\u968F\u670D\u52A1\u5668\u542F\u52A8\u9644\u5C5E\u8FDB\u7A0B (PID ${child.pid ?? 0})`, "stdout");
94
+ child.on("exit", (code) => {
95
+ if (children.get(spec.id) === child) children.delete(spec.id);
96
+ log(`[${spec.id}] \u9644\u5C5E\u8FDB\u7A0B\u9000\u51FA (code ${code ?? "?"})`, code === 0 ? "stdout" : "stderr");
97
+ });
98
+ child.on("error", (err) => {
99
+ log(`[${spec.id}] \u9644\u5C5E\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${err.message}`, "stderr");
100
+ });
101
+ }
102
+ }
103
+ function stopModuleSidecars() {
104
+ for (const [id, child] of children) {
105
+ children.delete(id);
106
+ try {
107
+ child.kill();
108
+ } catch {
109
+ }
110
+ }
111
+ }
112
+ export {
113
+ startModuleSidecars,
114
+ stopModuleSidecars
115
+ };
116
+ //# sourceMappingURL=module-sidecars.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/module-sidecars.ts"],
4
+ "sourcesContent": ["/**\n * \u6A21\u5757\u968F BDS \u542F\u52A8\u7684\u9644\u5C5E\u8FDB\u7A0B\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1A\u5F00\u670D\u524D\u626B\u63CF modules/packages/<id>/sidecar.json\u3002\n * \u76EE\u524D Bluemap \u7528\u5B83\u62C9\u8D77\u5730\u56FE\u8FDB\u7A0B\uFF1B\u65E5\u5FD7\u6B63\u6587\u7531\u811A\u672C\u6253\u8FDB BDS\uFF0C\u8FD9\u91CC\u53EA\u62A5\u544A\u542F\u52A8\u548C\u9000\u51FA\u3002\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport net from \"node:net\";\nimport path from \"node:path\";\n\n/** \u6A21\u5757\u5305\u6839\u76EE\u5F55\u91CC\u7684 sidecar.json\u3002 */\ninterface SidecarSpec {\n id: string;\n /** \u76F8\u5BF9\u6A21\u5757\u5305\u6839\u76EE\u5F55\u7684 Node \u5165\u53E3\u3002 */\n entry: string;\n /** \u76F8\u5BF9 SFMC \u6839\u76EE\u5F55\u7684\u914D\u7F6E\u6587\u4EF6\u3002\u7F3A\u7701\u4E0D\u5224\u65AD\u5F00\u5173\u3002 */\n configFile?: string;\n /** \u914D\u7F6E\u91CC\u7684\u70B9\u8DEF\u5F84\u3002\u503C\u4E3A false \u65F6\u4E0D\u542F\u52A8\u3002\u7F3A\u7701\u89C6\u4E3A\u5F00\u542F\u3002 */\n when?: string;\n}\n\n/** \u672C\u8F6E BDS \u62C9\u8D77\u7684\u9644\u5C5E\u8FDB\u7A0B\u3002\u505C\u670D\u65F6\u53EA\u7ED3\u675F\u8FD9\u4E9B\uFF0C\u4E0D\u78B0\u7528\u6237\u53E6\u5916\u5F00\u7684\u8FDB\u7A0B\u3002 */\nconst children = new Map<string, ChildProcess>();\n\nfunction readJson(file: string): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(fs.readFileSync(file, \"utf8\")) as unknown;\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n}\n\n/** \u6CBF\u70B9\u8DEF\u5F84\u53D6\u503C\u3002\u4E2D\u9014\u4E0D\u662F\u5BF9\u8C61\u5219\u8FD4\u56DE undefined\u3002 */\nfunction readPath(source: Record<string, unknown>, dotted: string): unknown {\n let current: unknown = source;\n for (const part of dotted.split(\".\")) {\n if (!current || typeof current !== \"object\") return undefined;\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n}\n\nfunction isEnabled(root: string, spec: SidecarSpec): boolean {\n if (!spec.when || !spec.configFile) return true;\n const config = readJson(path.join(root, spec.configFile));\n if (!config) return true;\n const flag = readPath(config, spec.when);\n return flag !== false;\n}\n\nfunction configuredPort(root: string, spec: SidecarSpec): number {\n if (!spec.configFile) return 0;\n const config = readJson(path.join(root, spec.configFile));\n const port = config?.port;\n return typeof port === \"number\" && port > 0 && port < 65536 ? port : 0;\n}\n\n/** \u7AEF\u53E3\u5DF2\u6709\u8FDB\u7A0B\u76D1\u542C\u65F6\u4E0D\u518D\u542F\u52A8\u7B2C\u4E8C\u4E2A\u3002 */\nfunction portOpen(port: number): Promise<boolean> {\n if (!port) return Promise.resolve(false);\n return new Promise((resolve) => {\n const socket = net.connect({ host: \"127.0.0.1\", port });\n const done = (open: boolean) => {\n socket.removeAllListeners();\n socket.destroy();\n resolve(open);\n };\n socket.once(\"connect\", () => done(true));\n socket.once(\"error\", () => done(false));\n socket.setTimeout(500, () => done(false));\n });\n}\n\nfunction loadSpecs(root: string): Array<{ spec: SidecarSpec; dir: string }> {\n const packagesDir = path.join(root, \"modules\", \"packages\");\n if (!fs.existsSync(packagesDir)) return [];\n const found: Array<{ spec: SidecarSpec; dir: string }> = [];\n for (const entry of fs.readdirSync(packagesDir, { withFileTypes: true })) {\n if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;\n const dir = path.join(packagesDir, entry.name);\n const file = path.join(dir, \"sidecar.json\");\n if (!fs.existsSync(file)) continue;\n const raw = readJson(file);\n if (!raw || typeof raw.id !== \"string\" || typeof raw.entry !== \"string\") continue;\n const lock = readJson(path.join(root, \"modules\", \"module-lock.json\"));\n const modules = lock?.modules && typeof lock.modules === \"object\" ? (lock.modules as Record<string, unknown>) : {};\n const row = modules[raw.id];\n const enabled = !row || typeof row !== \"object\" || (row as { enabled?: boolean }).enabled !== false;\n if (!enabled) continue;\n const spec: SidecarSpec = { id: raw.id, entry: raw.entry };\n if (typeof raw.configFile === \"string\") spec.configFile = raw.configFile;\n if (typeof raw.when === \"string\") spec.when = raw.when;\n found.push({ spec, dir });\n }\n return found;\n}\n\n/**\n * \u5728 BDS \u8FDB\u7A0B\u62C9\u8D77\u4E4B\u524D\u542F\u52A8\u9644\u5C5E\u8FDB\u7A0B\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1Aservices.ts \u91CC bds \u7684 start\uFF0C\u5728 beforeStart \u4E4B\u540E\u8C03\u7528\u3002\n */\nexport async function startModuleSidecars(\n root: string,\n log: (line: string, stream: \"stdout\" | \"stderr\") => void\n): Promise<void> {\n for (const { spec, dir } of loadSpecs(root)) {\n if (children.has(spec.id)) continue;\n if (!isEnabled(root, spec)) continue;\n const entry = path.join(dir, spec.entry);\n if (!fs.existsSync(entry)) {\n log(`[${spec.id}] \u672A\u627E\u5230\u9644\u5C5E\u8FDB\u7A0B\u5165\u53E3: ${entry}`, \"stderr\");\n continue;\n }\n const port = configuredPort(root, spec);\n if (await portOpen(port)) {\n log(`[${spec.id}] \u7AEF\u53E3 ${port} \u5DF2\u5728\u76D1\u542C\uFF0C\u8DF3\u8FC7\u542F\u52A8\u3002`, \"stdout\");\n continue;\n }\n const child = spawn(process.execPath, [entry], {\n cwd: dir,\n env: { ...process.env, SFMC_ROOT: root },\n windowsHide: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.set(spec.id, child);\n log(`[${spec.id}] \u5DF2\u968F\u670D\u52A1\u5668\u542F\u52A8\u9644\u5C5E\u8FDB\u7A0B (PID ${child.pid ?? 0})`, \"stdout\");\n child.on(\"exit\", (code) => {\n if (children.get(spec.id) === child) children.delete(spec.id);\n log(`[${spec.id}] \u9644\u5C5E\u8FDB\u7A0B\u9000\u51FA (code ${code ?? \"?\"})`, code === 0 ? \"stdout\" : \"stderr\");\n });\n child.on(\"error\", (err) => {\n log(`[${spec.id}] \u9644\u5C5E\u8FDB\u7A0B\u542F\u52A8\u5931\u8D25: ${err.message}`, \"stderr\");\n });\n }\n}\n\n/**\n * \u7ED3\u675F\u672C\u8F6E\u7531\u5F00\u670D\u62C9\u8D77\u7684\u9644\u5C5E\u8FDB\u7A0B\u3002\n * \u4F7F\u7528\u573A\u666F\uFF1ABDS \u670D\u52A1 cleanup \u65F6\u8C03\u7528\u3002\u7528\u6237\u81EA\u5DF1\u53E6\u5F00\u7684\u5730\u56FE\u8FDB\u7A0B\u4E0D\u4F1A\u88AB\u5173\u6389\u3002\n */\nexport function stopModuleSidecars(): void {\n for (const [id, child] of children) {\n children.delete(id);\n try {\n child.kill();\n } catch {\n /* \u8FDB\u7A0B\u5DF2\u7ECF\u7ED3\u675F\u65F6\u5FFD\u7565\u3002 */\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAcA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAgN9B,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiD1D;AAcD,wBAAgB,OAAO,CAAC,IAAI,GAAE,WAAoB,GAAG,MAAM,CAE1D;AAofD,mCAAmC;AACnC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAG9D,CAAC;AAmBJ,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAiS/C"}
1
+ {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAcA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AA+M9B,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAiD1D;AAcD,wBAAgB,OAAO,CAAC,IAAI,GAAE,WAAoB,GAAG,MAAM,CAE1D;AAufD,mCAAmC;AACnC,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAG9D,CAAC;AAmBJ,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAkS/C"}
package/dist/repl.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  resolvePaletteView
14
14
  } from "./command-palette.js";
15
15
  import {
16
+ listNextPaletteTokens,
16
17
  listVisiblePacksSubs,
17
18
  listVisibleTopLevelNames,
18
19
  resolveModuleTopShorthand
@@ -24,7 +25,6 @@ import { t } from "./i18n/index.js";
24
25
  import { cmdLocale } from "./locale-command.js";
25
26
  import {
26
27
  formatLog,
27
- getAllLogs,
28
28
  logPrefixWidth,
29
29
  onLog,
30
30
  readDiskLogs,
@@ -47,7 +47,7 @@ import {
47
47
  WindowHost
48
48
  } from "./repl-windows/index.js";
49
49
  import { SEND_TARGET_ORDER, paintSendPrompt, paintSfmcPrompt, plainPrompt } from "./send-target.js";
50
- import { forceStopAll, onServiceStateChange, queryServicesRuntime, SERVICE_NAMES, stopAll } from "./services.js";
50
+ import { onServiceStateChange, queryServicesRuntime, SERVICE_NAMES } from "./services.js";
51
51
  import { c, T } from "./theme.js";
52
52
  import { dispatchPacksCommand, isPacksCommand } from "./world-packs.js";
53
53
  const REPL_MODE = "repl";
@@ -264,6 +264,8 @@ function getCompletions(parsed) {
264
264
  const cmds = getCommands().map((n) => n.startsWith("/") ? n : n);
265
265
  return ["/", ...cmds].filter(sw);
266
266
  }
267
+ const paletteNext = listNextPaletteTokens([cmd, ...words.slice(0, argIndex)], REPL_MODE);
268
+ if (paletteNext && paletteNext.length > 0) return paletteNext.filter(sw);
267
269
  switch (cmd) {
268
270
  case "logs":
269
271
  case "log":
@@ -277,7 +279,7 @@ function getCompletions(parsed) {
277
279
  if (argIndex === 0) return SERVICE_NAMES.filter(sw);
278
280
  return [];
279
281
  case "update":
280
- return ["--check-only", "--force", "--channel=release", "--channel=preview"].filter(sw);
282
+ return ["--check-only", "--force", "--no-start", "--channel=release", "--channel=preview"].filter(sw);
281
283
  case "packs":
282
284
  case "addon":
283
285
  if (argIndex === 0) return [...listVisiblePacksSubs(REPL_MODE)].filter(sw);
@@ -648,9 +650,11 @@ function writeLogLineToTty(wrapped) {
648
650
  }
649
651
  async function startRepl() {
650
652
  let stopping = false;
653
+ let disconnectDaemon = null;
651
654
  const onSigint = () => {
652
- stdout.write(c.yellow("\n" + t("repl.forceStop") + "\n"));
653
- forceStopAll();
655
+ stdout.write(c.yellow("\n" + t("repl.detach") + "\n"));
656
+ disconnectDaemon?.();
657
+ disconnectDaemon = null;
654
658
  process.exit(130);
655
659
  };
656
660
  const shutdown = async () => {
@@ -658,8 +662,9 @@ async function startRepl() {
658
662
  stopping = true;
659
663
  process.off("SIGINT", onSigint);
660
664
  openLogsWindowHook = null;
661
- stdout.write(c.dim(t("repl.stopping") + "\n"));
662
- await stopAll();
665
+ stdout.write(c.dim(t("repl.detach") + "\n"));
666
+ disconnectDaemon?.();
667
+ disconnectDaemon = null;
663
668
  try {
664
669
  setRaw(false);
665
670
  stdin.pause();
@@ -669,6 +674,13 @@ async function startRepl() {
669
674
  process.exit(0);
670
675
  };
671
676
  process.on("SIGINT", onSigint);
677
+ try {
678
+ const { ensureDaemonSubscription } = await import("./daemon/client.js");
679
+ disconnectDaemon = await ensureDaemonSubscription();
680
+ } catch (e) {
681
+ stdout.write(c.red(t("daemon.connectFailed", { message: e.message })) + "\n");
682
+ process.exit(1);
683
+ }
672
684
  if (!stdin.isTTY) {
673
685
  console.log(c.dim(t("repl.nonInteractive")));
674
686
  for await (const line of (await import("node:readline/promises")).createInterface({
@@ -705,24 +717,17 @@ async function startRepl() {
705
717
  stdout.write("\x1B[3J");
706
718
  }
707
719
  function seedWindowBuffer(w) {
720
+ const f = w.getReplayFilter?.() ?? { levels: [], sources: [] };
721
+ const disk = readDiskLogs({
722
+ levels: f.levels,
723
+ sources: f.sources,
724
+ limit: 5e3
725
+ });
708
726
  const lines = [];
709
- if (w.id === "logs") {
710
- const f = w.getReplayFilter?.() ?? { levels: [], sources: [] };
711
- const disk = readDiskLogs({
712
- levels: f.levels,
713
- sources: f.sources
714
- });
715
- for (const log of disk) {
716
- if (!w.acceptLog(log)) continue;
717
- const { text, indent } = w.formatLogLine(log);
718
- lines.push(wrapLogLine(text, indent));
719
- }
720
- } else {
721
- for (const log of getAllLogs()) {
722
- if (!w.acceptLog(log)) continue;
723
- const { text, indent } = w.formatLogLine(log);
724
- lines.push(wrapLogLine(text, indent));
725
- }
727
+ for (const log of disk) {
728
+ if (!w.acceptLog(log)) continue;
729
+ const { text, indent } = w.formatLogLine(log);
730
+ lines.push(wrapLogLine(text, indent));
726
731
  }
727
732
  host.setBuffer(w.id, lines);
728
733
  return lines;
@@ -734,14 +739,8 @@ async function startRepl() {
734
739
  currentRedraw?.();
735
740
  return;
736
741
  }
737
- let lines;
738
- if (w.id === "logs") {
739
- if (opts?.reseed) host.invalidateBuffer(w.id);
740
- lines = host.hasBuffer(w.id) ? host.getBuffer(w.id) : seedWindowBuffer(w);
741
- } else {
742
- host.invalidateBuffer(w.id);
743
- lines = seedWindowBuffer(w);
744
- }
742
+ if (opts?.reseed) host.invalidateBuffer(w.id);
743
+ const lines = host.hasBuffer(w.id) ? host.getBuffer(w.id) : seedWindowBuffer(w);
745
744
  for (const l of lines) stdout.write(l + "\n");
746
745
  currentRedraw?.();
747
746
  }
@@ -967,6 +966,26 @@ async function execCmd(parts) {
967
966
  stdout.write(await cmdUi(args, { block: false }) + "\n");
968
967
  break;
969
968
  }
969
+ case "daemon": {
970
+ const sub = args[0];
971
+ if (sub === "stop") {
972
+ const { shutdownDaemon } = await import("./daemon/client.js");
973
+ const err = await shutdownDaemon();
974
+ stdout.write((err ? c.red(err) : c.dim(t("daemon.stopped"))) + "\n");
975
+ } else if (sub === "status" || !sub) {
976
+ const { readDaemonMeta } = await import("./daemon/paths.js");
977
+ const { isProcessAlive } = await import("@sfmc-bds/bds-tools/process-probe");
978
+ const meta2 = readDaemonMeta();
979
+ if (!meta2 || !await isProcessAlive(meta2.pid)) {
980
+ stdout.write(c.dim(t("daemon.notRunning")) + "\n");
981
+ } else {
982
+ stdout.write(c.green(t("daemon.running", { pid: String(meta2.pid) })) + "\n");
983
+ }
984
+ } else {
985
+ stdout.write(c.yellow(t("daemon.usage") + "\n"));
986
+ }
987
+ break;
988
+ }
970
989
  case "init": {
971
990
  const wasRaw = stdin.isRaw ?? false;
972
991
  setRaw(false);