@sfmc-bds/bds-tools 0.2.0-beta.8 → 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 (114) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +1 -1
  3. package/dist/bds-manager.d.ts +16 -6
  4. package/dist/bds-manager.d.ts.map +1 -1
  5. package/dist/bds-manager.js +59 -66
  6. package/dist/bds-manager.js.map +2 -2
  7. package/dist/changelog.d.ts +11 -7
  8. package/dist/changelog.d.ts.map +1 -1
  9. package/dist/changelog.js +60 -34
  10. package/dist/changelog.js.map +2 -2
  11. package/dist/check-update.d.ts.map +1 -1
  12. package/dist/check-update.js +59 -23
  13. package/dist/check-update.js.map +2 -2
  14. package/dist/cli-pack-manager.js +11 -1
  15. package/dist/cli-pack-manager.js.map +2 -2
  16. package/dist/dependency-negotiator.d.ts +73 -0
  17. package/dist/dependency-negotiator.d.ts.map +1 -0
  18. package/dist/dependency-negotiator.js +151 -0
  19. package/dist/dependency-negotiator.js.map +7 -0
  20. package/dist/fsx.d.ts +17 -14
  21. package/dist/fsx.d.ts.map +1 -1
  22. package/dist/fsx.js +67 -19
  23. package/dist/fsx.js.map +2 -2
  24. package/dist/host-platform.d.ts +25 -0
  25. package/dist/host-platform.d.ts.map +1 -0
  26. package/dist/host-platform.js +50 -0
  27. package/dist/host-platform.js.map +7 -0
  28. package/dist/http.d.ts +49 -11
  29. package/dist/http.d.ts.map +1 -1
  30. package/dist/http.js.map +2 -2
  31. package/dist/level-dat.d.ts +105 -0
  32. package/dist/level-dat.d.ts.map +1 -0
  33. package/dist/level-dat.js +490 -0
  34. package/dist/level-dat.js.map +7 -0
  35. package/dist/pack-manager.d.ts +41 -34
  36. package/dist/pack-manager.d.ts.map +1 -1
  37. package/dist/pack-manager.js +79 -25
  38. package/dist/pack-manager.js.map +2 -2
  39. package/dist/pack-update/bindings.d.ts +16 -0
  40. package/dist/pack-update/bindings.d.ts.map +1 -0
  41. package/dist/pack-update/bindings.js +69 -0
  42. package/dist/pack-update/bindings.js.map +7 -0
  43. package/dist/pack-update/config.d.ts +16 -0
  44. package/dist/pack-update/config.d.ts.map +1 -0
  45. package/dist/pack-update/config.js +146 -0
  46. package/dist/pack-update/config.js.map +7 -0
  47. package/dist/pack-update/deps.d.ts +28 -0
  48. package/dist/pack-update/deps.d.ts.map +1 -0
  49. package/dist/pack-update/deps.js +20 -0
  50. package/dist/pack-update/deps.js.map +7 -0
  51. package/dist/pack-update/index.d.ts +55 -0
  52. package/dist/pack-update/index.d.ts.map +1 -0
  53. package/dist/pack-update/index.js +72 -0
  54. package/dist/pack-update/index.js.map +7 -0
  55. package/dist/pack-update/providers/curseforge.d.ts +23 -0
  56. package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
  57. package/dist/pack-update/providers/curseforge.js +229 -0
  58. package/dist/pack-update/providers/curseforge.js.map +7 -0
  59. package/dist/pack-update/providers/index.d.ts +16 -0
  60. package/dist/pack-update/providers/index.d.ts.map +1 -0
  61. package/dist/pack-update/providers/index.js +37 -0
  62. package/dist/pack-update/providers/index.js.map +7 -0
  63. package/dist/pack-update/service.d.ts +28 -0
  64. package/dist/pack-update/service.d.ts.map +1 -0
  65. package/dist/pack-update/service.js +570 -0
  66. package/dist/pack-update/service.js.map +7 -0
  67. package/dist/pack-update/types.d.ts +126 -0
  68. package/dist/pack-update/types.d.ts.map +1 -0
  69. package/dist/pack-update/types.js +8 -0
  70. package/dist/pack-update/types.js.map +7 -0
  71. package/dist/pack-update/version-policy.d.ts +81 -0
  72. package/dist/pack-update/version-policy.d.ts.map +1 -0
  73. package/dist/pack-update/version-policy.js +210 -0
  74. package/dist/pack-update/version-policy.js.map +7 -0
  75. package/dist/paths.d.ts +1 -1
  76. package/dist/paths.d.ts.map +1 -1
  77. package/dist/paths.js +3 -5
  78. package/dist/paths.js.map +2 -2
  79. package/dist/process-probe.d.ts +34 -0
  80. package/dist/process-probe.d.ts.map +1 -0
  81. package/dist/process-probe.js +152 -0
  82. package/dist/process-probe.js.map +7 -0
  83. package/dist/qq-events-notify.d.ts +16 -0
  84. package/dist/qq-events-notify.d.ts.map +1 -0
  85. package/dist/qq-events-notify.js +70 -0
  86. package/dist/qq-events-notify.js.map +7 -0
  87. package/dist/qqutil.d.ts +10 -5
  88. package/dist/qqutil.d.ts.map +1 -1
  89. package/dist/qqutil.js +53 -10
  90. package/dist/qqutil.js.map +2 -2
  91. package/dist/rollback.d.ts.map +1 -1
  92. package/dist/rollback.js +2 -2
  93. package/dist/rollback.js.map +2 -2
  94. package/dist/server-properties-i18n.d.ts +17 -0
  95. package/dist/server-properties-i18n.d.ts.map +1 -0
  96. package/dist/server-properties-i18n.js +201 -0
  97. package/dist/server-properties-i18n.js.map +7 -0
  98. package/dist/server-properties.d.ts +25 -0
  99. package/dist/server-properties.d.ts.map +1 -1
  100. package/dist/server-properties.js +141 -1
  101. package/dist/server-properties.js.map +2 -2
  102. package/dist/types.d.ts +1 -0
  103. package/dist/types.d.ts.map +1 -1
  104. package/dist/upstream.d.ts +18 -9
  105. package/dist/upstream.d.ts.map +1 -1
  106. package/dist/upstream.js +8 -7
  107. package/dist/upstream.js.map +2 -2
  108. package/dist/version.js +2 -2
  109. package/dist/version.js.map +2 -2
  110. package/dist/world-packs.d.ts +108 -8
  111. package/dist/world-packs.d.ts.map +1 -1
  112. package/dist/world-packs.js +245 -12
  113. package/dist/world-packs.js.map +3 -3
  114. package/package.json +24 -17
@@ -0,0 +1,152 @@
1
+ import { exec, execSync } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { resolveRuntimeRoot, stateDir } from "@sfmc-bds/sdk/node/config";
7
+ import { bdsExeName } from "./host-platform.js";
8
+ const execAsync = promisify(exec);
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const DEFAULT_ROOT_DIR = resolveRuntimeRoot(__dirname);
12
+ let execImpl = async (cmd, opts) => {
13
+ const result = await execAsync(cmd, opts);
14
+ return { stdout: String(result.stdout), stderr: String(result.stderr) };
15
+ };
16
+ function setExecForTesting(fn) {
17
+ execImpl = fn ?? (async (cmd, opts) => {
18
+ const result = await execAsync(cmd, opts);
19
+ return { stdout: String(result.stdout), stderr: String(result.stderr) };
20
+ });
21
+ }
22
+ function resolveRoot(rootDir) {
23
+ return rootDir ?? DEFAULT_ROOT_DIR;
24
+ }
25
+ function pidFilePath(rootDir) {
26
+ return path.join(stateDir(resolveRoot(rootDir)), "bds.pid");
27
+ }
28
+ function ensureStateDirForRoot(rootDir) {
29
+ fs.mkdirSync(stateDir(resolveRoot(rootDir)), { recursive: true });
30
+ }
31
+ function readBdsPidFile(rootDir) {
32
+ try {
33
+ return parseInt(fs.readFileSync(pidFilePath(rootDir), "utf-8").trim(), 10) || 0;
34
+ } catch {
35
+ return 0;
36
+ }
37
+ }
38
+ function writeBdsPidFile(pid, rootDir) {
39
+ try {
40
+ ensureStateDirForRoot(rootDir);
41
+ fs.writeFileSync(pidFilePath(rootDir), String(pid));
42
+ } catch {
43
+ }
44
+ }
45
+ function clearBdsPidFile(rootDir) {
46
+ try {
47
+ const file = pidFilePath(rootDir);
48
+ if (fs.existsSync(file)) fs.unlinkSync(file);
49
+ } catch {
50
+ }
51
+ }
52
+ let aliveOverride = null;
53
+ function setIsAliveForTesting(fn) {
54
+ aliveOverride = fn;
55
+ }
56
+ async function isProcessAlive(pid) {
57
+ if (!pid) return false;
58
+ if (aliveOverride) return aliveOverride(pid);
59
+ try {
60
+ if (process.platform === "win32") {
61
+ const { stdout } = await execImpl(`tasklist /fi "PID eq ${pid}" /nh`, { windowsHide: true });
62
+ return stdout.includes(String(pid));
63
+ }
64
+ process.kill(pid, 0);
65
+ return true;
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+ function isProcessAliveSync(pid) {
71
+ if (!pid) return false;
72
+ if (aliveOverride) return aliveOverride(pid);
73
+ try {
74
+ if (process.platform === "win32") {
75
+ const stdout = execSync(`tasklist /fi "PID eq ${pid}" /nh`, {
76
+ windowsHide: true,
77
+ encoding: "utf8"
78
+ });
79
+ return String(stdout).includes(String(pid));
80
+ }
81
+ process.kill(pid, 0);
82
+ return true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ }
87
+ async function findBedrockServerPids() {
88
+ try {
89
+ if (process.platform === "win32") {
90
+ const image = bdsExeName("windows");
91
+ const { stdout: stdout2 } = await execImpl(`tasklist /fi "IMAGENAME eq ${image}" /fo csv /nh`, {
92
+ windowsHide: true
93
+ });
94
+ const pids = [];
95
+ const re = new RegExp(`"${image.replace(".", "\\.")}","(\\d+)"`, "i");
96
+ for (const line of stdout2.split(/\r?\n/)) {
97
+ const m = line.match(re);
98
+ if (m) {
99
+ const pid = parseInt(m[1] ?? "0", 10);
100
+ if (pid > 0) pids.push(pid);
101
+ }
102
+ }
103
+ return pids;
104
+ }
105
+ const { stdout } = await execImpl("pgrep -x bedrock_server", { windowsHide: true });
106
+ return stdout.split(/\r?\n/).map((s) => parseInt(s.trim(), 10)).filter((n) => n > 0);
107
+ } catch {
108
+ return [];
109
+ }
110
+ }
111
+ async function killBedrockServerByImage() {
112
+ try {
113
+ if (process.platform === "win32") {
114
+ await execImpl(`taskkill /f /im ${bdsExeName("windows")}`, { windowsHide: true });
115
+ } else {
116
+ await execImpl("pkill -x bedrock_server", { windowsHide: true });
117
+ }
118
+ } catch {
119
+ }
120
+ }
121
+ async function probeBdsStatus(opts) {
122
+ const managedPid = opts.managedPid ?? 0;
123
+ const hasStdin = opts.hasStdin ?? false;
124
+ if (managedPid > 0 && hasStdin && await isProcessAlive(managedPid)) {
125
+ return { state: "managed", pid: managedPid };
126
+ }
127
+ const filePid = readBdsPidFile(opts.rootDir);
128
+ if (filePid > 0 && await isProcessAlive(filePid)) {
129
+ return { state: "external", pid: filePid };
130
+ }
131
+ const imagePids = await findBedrockServerPids();
132
+ if (imagePids.length > 0) {
133
+ return { state: "external", pid: imagePids[0] ?? 0 };
134
+ }
135
+ if (filePid > 0) {
136
+ clearBdsPidFile(opts.rootDir);
137
+ }
138
+ return { state: "stopped", pid: 0 };
139
+ }
140
+ export {
141
+ clearBdsPidFile,
142
+ findBedrockServerPids,
143
+ isProcessAlive,
144
+ isProcessAliveSync,
145
+ killBedrockServerByImage,
146
+ probeBdsStatus,
147
+ readBdsPidFile,
148
+ setExecForTesting,
149
+ setIsAliveForTesting,
150
+ writeBdsPidFile
151
+ };
152
+ //# sourceMappingURL=process-probe.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/process-probe.ts"],
4
+ "sourcesContent": ["/**\n * process-probe.ts \u2014 OS \u7EA7 BDS \u8FDB\u7A0B\u63A2\u6D4B\u4E0E PID \u6587\u4EF6\u7BA1\u7406\n *\n * \u4F9B bds-manager / sfmc \u5171\u7528\uFF0C\u907F\u514D\u91CD\u590D tasklist / taskkill \u903B\u8F91\u3002\n */\n\nimport { exec, execSync } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolveRuntimeRoot, stateDir } from \"@sfmc-bds/sdk/node/config\";\nimport { bdsExeName } from \"./host-platform.js\";\n\nconst execAsync = promisify(exec);\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/** \u4E0E paths.ROOT_DIR \u4E00\u81F4\uFF1ASFMC_ROOT > walk-up monorepo */\nconst DEFAULT_ROOT_DIR = resolveRuntimeRoot(__dirname);\n\nexport type ExecFn = (\n cmd: string,\n opts?: { windowsHide?: boolean }\n) => Promise<{ stdout: string; stderr: string }>;\n\nlet execImpl: ExecFn = async (cmd, opts) => {\n const result = await execAsync(cmd, opts);\n return { stdout: String(result.stdout), stderr: String(result.stderr) };\n};\n\n/** \u6D4B\u8BD5\u6CE8\u5165 exec\uFF0C\u4F20 null \u6062\u590D\u9ED8\u8BA4 */\nexport function setExecForTesting(fn: ExecFn | null): void {\n execImpl =\n fn ??\n (async (cmd, opts) => {\n const result = await execAsync(cmd, opts);\n return { stdout: String(result.stdout), stderr: String(result.stderr) };\n });\n}\n\nfunction resolveRoot(rootDir?: string): string {\n return rootDir ?? DEFAULT_ROOT_DIR;\n}\n\nfunction pidFilePath(rootDir?: string): string {\n return path.join(stateDir(resolveRoot(rootDir)), \"bds.pid\");\n}\n\nfunction ensureStateDirForRoot(rootDir?: string): void {\n fs.mkdirSync(stateDir(resolveRoot(rootDir)), { recursive: true });\n}\n\nexport function readBdsPidFile(rootDir?: string): number {\n try {\n return parseInt(fs.readFileSync(pidFilePath(rootDir), \"utf-8\").trim(), 10) || 0;\n } catch {\n return 0;\n }\n}\n\nexport function writeBdsPidFile(pid: number, rootDir?: string): void {\n try {\n ensureStateDirForRoot(rootDir);\n fs.writeFileSync(pidFilePath(rootDir), String(pid));\n } catch {\n /* ignore */\n }\n}\n\nexport function clearBdsPidFile(rootDir?: string): void {\n try {\n const file = pidFilePath(rootDir);\n if (fs.existsSync(file)) fs.unlinkSync(file);\n } catch {\n /* ignore */\n }\n}\n\nlet aliveOverride: ((pid: number) => boolean) | null = null;\n\n/** \u6D4B\u8BD5\u6CE8\u5165\u63A2\u6D3B\uFF0C\u4F20 null \u6062\u590D\u9ED8\u8BA4\uFF08\u907F\u514D Linux \u4E0A process.kill \u78B0\u5230\u771F\u5B9E PID\uFF09 */\nexport function setIsAliveForTesting(fn: ((pid: number) => boolean) | null): void {\n aliveOverride = fn;\n}\n\nexport async function isProcessAlive(pid: number): Promise<boolean> {\n if (!pid) return false;\n if (aliveOverride) return aliveOverride(pid);\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execImpl(`tasklist /fi \"PID eq ${pid}\" /nh`, { windowsHide: true });\n return stdout.includes(String(pid));\n }\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** \u540C\u6B65\u63A2\u6D3B\uFF08\u4F9B sync \u7684 sendCommand \u7B49\u8DEF\u5F84\uFF1B\u907F\u514D Promise \u88AB\u5F53 truthy\uFF09 */\nexport function isProcessAliveSync(pid: number): boolean {\n if (!pid) return false;\n if (aliveOverride) return aliveOverride(pid);\n try {\n if (process.platform === \"win32\") {\n const stdout = execSync(`tasklist /fi \"PID eq ${pid}\" /nh`, {\n windowsHide: true,\n encoding: \"utf8\",\n });\n return String(stdout).includes(String(pid));\n }\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function findBedrockServerPids(): Promise<number[]> {\n try {\n if (process.platform === \"win32\") {\n const image = bdsExeName(\"windows\");\n const { stdout } = await execImpl(`tasklist /fi \"IMAGENAME eq ${image}\" /fo csv /nh`, {\n windowsHide: true,\n });\n const pids: number[] = [];\n const re = new RegExp(`\"${image.replace(\".\", \"\\\\.\")}\",\"(\\\\d+)\"`, \"i\");\n for (const line of stdout.split(/\\r?\\n/)) {\n const m = line.match(re);\n if (m) {\n const pid = parseInt(m[1] ?? \"0\", 10);\n if (pid > 0) pids.push(pid);\n }\n }\n return pids;\n }\n const { stdout } = await execImpl(\"pgrep -x bedrock_server\", { windowsHide: true });\n return stdout\n .split(/\\r?\\n/)\n .map((s) => parseInt(s.trim(), 10))\n .filter((n) => n > 0);\n } catch {\n return [];\n }\n}\n\nexport async function killBedrockServerByImage(): Promise<void> {\n try {\n if (process.platform === \"win32\") {\n await execImpl(`taskkill /f /im ${bdsExeName(\"windows\")}`, { windowsHide: true });\n } else {\n await execImpl(\"pkill -x bedrock_server\", { windowsHide: true });\n }\n } catch {\n /* ignore \u2014 \u65E0\u8FDB\u7A0B\u65F6 taskkill/pkill \u4F1A\u975E\u96F6\u9000\u51FA */\n }\n}\n\nexport type BdsProbeState = \"stopped\" | \"managed\" | \"external\";\n\nexport interface BdsProbeResult {\n state: BdsProbeState;\n pid: number;\n}\n\nexport async function probeBdsStatus(opts: {\n managedPid?: number;\n hasStdin?: boolean;\n rootDir?: string;\n}): Promise<BdsProbeResult> {\n const managedPid = opts.managedPid ?? 0;\n const hasStdin = opts.hasStdin ?? false;\n\n if (managedPid > 0 && hasStdin && (await isProcessAlive(managedPid))) {\n return { state: \"managed\", pid: managedPid };\n }\n\n const filePid = readBdsPidFile(opts.rootDir);\n if (filePid > 0 && (await isProcessAlive(filePid))) {\n return { state: \"external\", pid: filePid };\n }\n\n const imagePids = await findBedrockServerPids();\n if (imagePids.length > 0) {\n return { state: \"external\", pid: imagePids[0] ?? 0 };\n }\n\n if (filePid > 0) {\n clearBdsPidFile(opts.rootDir);\n }\n\n return { state: \"stopped\", pid: 0 };\n}\n"],
5
+ "mappings": "AAMA,SAAS,MAAM,gBAAgB;AAC/B,SAAS,iBAAiB;AAC1B,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB,gBAAgB;AAC7C,SAAS,kBAAkB;AAE3B,MAAM,YAAY,UAAU,IAAI;AAEhC,MAAM,aAAa,cAAc,YAAY,GAAG;AAChD,MAAM,YAAY,KAAK,QAAQ,UAAU;AAGzC,MAAM,mBAAmB,mBAAmB,SAAS;AAOrD,IAAI,WAAmB,OAAO,KAAK,SAAS;AAC1C,QAAM,SAAS,MAAM,UAAU,KAAK,IAAI;AACxC,SAAO,EAAE,QAAQ,OAAO,OAAO,MAAM,GAAG,QAAQ,OAAO,OAAO,MAAM,EAAE;AACxE;AAGO,SAAS,kBAAkB,IAAyB;AACzD,aACE,OACC,OAAO,KAAK,SAAS;AACpB,UAAM,SAAS,MAAM,UAAU,KAAK,IAAI;AACxC,WAAO,EAAE,QAAQ,OAAO,OAAO,MAAM,GAAG,QAAQ,OAAO,OAAO,MAAM,EAAE;AAAA,EACxE;AACJ;AAEA,SAAS,YAAY,SAA0B;AAC7C,SAAO,WAAW;AACpB;AAEA,SAAS,YAAY,SAA0B;AAC7C,SAAO,KAAK,KAAK,SAAS,YAAY,OAAO,CAAC,GAAG,SAAS;AAC5D;AAEA,SAAS,sBAAsB,SAAwB;AACrD,KAAG,UAAU,SAAS,YAAY,OAAO,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;AAClE;AAEO,SAAS,eAAe,SAA0B;AACvD,MAAI;AACF,WAAO,SAAS,GAAG,aAAa,YAAY,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK;AAAA,EAChF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,gBAAgB,KAAa,SAAwB;AACnE,MAAI;AACF,0BAAsB,OAAO;AAC7B,OAAG,cAAc,YAAY,OAAO,GAAG,OAAO,GAAG,CAAC;AAAA,EACpD,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,gBAAgB,SAAwB;AACtD,MAAI;AACF,UAAM,OAAO,YAAY,OAAO;AAChC,QAAI,GAAG,WAAW,IAAI,EAAG,IAAG,WAAW,IAAI;AAAA,EAC7C,QAAQ;AAAA,EAER;AACF;AAEA,IAAI,gBAAmD;AAGhD,SAAS,qBAAqB,IAA6C;AAChF,kBAAgB;AAClB;AAEA,eAAsB,eAAe,KAA+B;AAClE,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,cAAe,QAAO,cAAc,GAAG;AAC3C,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM,EAAE,OAAO,IAAI,MAAM,SAAS,wBAAwB,GAAG,SAAS,EAAE,aAAa,KAAK,CAAC;AAC3F,aAAO,OAAO,SAAS,OAAO,GAAG,CAAC;AAAA,IACpC;AACA,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,mBAAmB,KAAsB;AACvD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,cAAe,QAAO,cAAc,GAAG;AAC3C,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM,SAAS,SAAS,wBAAwB,GAAG,SAAS;AAAA,QAC1D,aAAa;AAAA,QACb,UAAU;AAAA,MACZ,CAAC;AACD,aAAO,OAAO,MAAM,EAAE,SAAS,OAAO,GAAG,CAAC;AAAA,IAC5C;AACA,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,wBAA2C;AAC/D,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM,QAAQ,WAAW,SAAS;AAClC,YAAM,EAAE,QAAAA,QAAO,IAAI,MAAM,SAAS,8BAA8B,KAAK,iBAAiB;AAAA,QACpF,aAAa;AAAA,MACf,CAAC;AACD,YAAM,OAAiB,CAAC;AACxB,YAAM,KAAK,IAAI,OAAO,IAAI,MAAM,QAAQ,KAAK,KAAK,CAAC,cAAc,GAAG;AACpE,iBAAW,QAAQA,QAAO,MAAM,OAAO,GAAG;AACxC,cAAM,IAAI,KAAK,MAAM,EAAE;AACvB,YAAI,GAAG;AACL,gBAAM,MAAM,SAAS,EAAE,CAAC,KAAK,KAAK,EAAE;AACpC,cAAI,MAAM,EAAG,MAAK,KAAK,GAAG;AAAA,QAC5B;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,EAAE,OAAO,IAAI,MAAM,SAAS,2BAA2B,EAAE,aAAa,KAAK,CAAC;AAClF,WAAO,OACJ,MAAM,OAAO,EACb,IAAI,CAAC,MAAM,SAAS,EAAE,KAAK,GAAG,EAAE,CAAC,EACjC,OAAO,CAAC,MAAM,IAAI,CAAC;AAAA,EACxB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAsB,2BAA0C;AAC9D,MAAI;AACF,QAAI,QAAQ,aAAa,SAAS;AAChC,YAAM,SAAS,mBAAmB,WAAW,SAAS,CAAC,IAAI,EAAE,aAAa,KAAK,CAAC;AAAA,IAClF,OAAO;AACL,YAAM,SAAS,2BAA2B,EAAE,aAAa,KAAK,CAAC;AAAA,IACjE;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AASA,eAAsB,eAAe,MAIT;AAC1B,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,WAAW,KAAK,YAAY;AAElC,MAAI,aAAa,KAAK,YAAa,MAAM,eAAe,UAAU,GAAI;AACpE,WAAO,EAAE,OAAO,WAAW,KAAK,WAAW;AAAA,EAC7C;AAEA,QAAM,UAAU,eAAe,KAAK,OAAO;AAC3C,MAAI,UAAU,KAAM,MAAM,eAAe,OAAO,GAAI;AAClD,WAAO,EAAE,OAAO,YAAY,KAAK,QAAQ;AAAA,EAC3C;AAEA,QAAM,YAAY,MAAM,sBAAsB;AAC9C,MAAI,UAAU,SAAS,GAAG;AACxB,WAAO,EAAE,OAAO,YAAY,KAAK,UAAU,CAAC,KAAK,EAAE;AAAA,EACrD;AAEA,MAAI,UAAU,GAAG;AACf,oBAAgB,KAAK,OAAO;AAAA,EAC9B;AAEA,SAAO,EAAE,OAAO,WAAW,KAAK,EAAE;AACpC;",
6
+ "names": ["stdout"]
7
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * qq-events-notify.ts — 向 db-server 上报 BDS 启停事件
3
+ *
4
+ * 只 POST 结构化事件;群 openid / 频控由 db-server 处理(DIP)。
5
+ * 失败仅 warn,不抛错。
6
+ */
7
+ export type BdsLifecycleEventType = "crash" | "start" | "stop";
8
+ /**
9
+ * POST /api/sfmc/qq/events;超时/非 2xx 不抛。
10
+ */
11
+ export declare function postBdsLifecycleEvent(type: BdsLifecycleEventType, detail?: string, opts?: {
12
+ host?: string;
13
+ port?: number;
14
+ timeoutMs?: number;
15
+ }): Promise<void>;
16
+ //# sourceMappingURL=qq-events-notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-events-notify.d.ts","sourceRoot":"","sources":["../src/qq-events-notify.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAiB/D;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,qBAAqB,EAC3B,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
@@ -0,0 +1,70 @@
1
+ import {
2
+ DEFAULT_DB_CONFIG,
3
+ loadEnsuredConfig
4
+ } from "@sfmc-bds/sdk/node/config";
5
+ import http from "node:http";
6
+ import { log } from "./log.js";
7
+ import { ROOT_DIR } from "./paths.js";
8
+ function resolveDbPort() {
9
+ try {
10
+ const cfg = loadEnsuredConfig(
11
+ ROOT_DIR,
12
+ "db_config.json",
13
+ "db_config",
14
+ { ...DEFAULT_DB_CONFIG }
15
+ );
16
+ const n = Number(cfg.db_port ?? 3001);
17
+ return Number.isFinite(n) && n > 0 ? n : 3001;
18
+ } catch {
19
+ return 3001;
20
+ }
21
+ }
22
+ function postBdsLifecycleEvent(type, detail, opts) {
23
+ const host = opts?.host ?? "127.0.0.1";
24
+ const port = opts?.port ?? resolveDbPort();
25
+ const timeoutMs = opts?.timeoutMs ?? 3e3;
26
+ const payload = JSON.stringify({
27
+ type,
28
+ detail: detail ?? void 0
29
+ });
30
+ return new Promise((resolve) => {
31
+ const req = http.request(
32
+ {
33
+ hostname: host,
34
+ port,
35
+ path: "/api/sfmc/qq/events",
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ "Content-Length": Buffer.byteLength(payload)
40
+ },
41
+ timeout: timeoutMs
42
+ },
43
+ (res) => {
44
+ res.resume();
45
+ res.on("end", () => {
46
+ const code = res.statusCode ?? 0;
47
+ if (code < 200 || code >= 300) {
48
+ log.warn(`[QQ\u4E8B\u4EF6] db-server \u8FD4\u56DE HTTP ${code} (type=${type})`);
49
+ }
50
+ resolve();
51
+ });
52
+ }
53
+ );
54
+ req.on("timeout", () => {
55
+ req.destroy();
56
+ log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u8D85\u65F6 ${timeoutMs}ms (type=${type})`);
57
+ resolve();
58
+ });
59
+ req.on("error", (err) => {
60
+ log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u5931\u8D25: ${err.message} (type=${type})`);
61
+ resolve();
62
+ });
63
+ req.write(payload);
64
+ req.end();
65
+ });
66
+ }
67
+ export {
68
+ postBdsLifecycleEvent
69
+ };
70
+ //# sourceMappingURL=qq-events-notify.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/qq-events-notify.ts"],
4
+ "sourcesContent": ["/**\n * qq-events-notify.ts \u2014 \u5411 db-server \u4E0A\u62A5 BDS \u542F\u505C\u4E8B\u4EF6\n *\n * \u53EA POST \u7ED3\u6784\u5316\u4E8B\u4EF6\uFF1B\u7FA4 openid / \u9891\u63A7\u7531 db-server \u5904\u7406\uFF08DIP\uFF09\u3002\n * \u5931\u8D25\u4EC5 warn\uFF0C\u4E0D\u629B\u9519\u3002\n */\n\nimport {\n DEFAULT_DB_CONFIG,\n loadEnsuredConfig,\n type DBConfig,\n} from \"@sfmc-bds/sdk/node/config\";\nimport http from \"node:http\";\nimport { log } from \"./log.js\";\nimport { ROOT_DIR } from \"./paths.js\";\n\nexport type BdsLifecycleEventType = \"crash\" | \"start\" | \"stop\";\n\nfunction resolveDbPort(): number {\n try {\n const cfg = loadEnsuredConfig(\n ROOT_DIR,\n \"db_config.json\",\n \"db_config\",\n { ...DEFAULT_DB_CONFIG } as Record<string, unknown>\n ) as DBConfig;\n const n = Number(cfg.db_port ?? 3001);\n return Number.isFinite(n) && n > 0 ? n : 3001;\n } catch {\n return 3001;\n }\n}\n\n/**\n * POST /api/sfmc/qq/events\uFF1B\u8D85\u65F6/\u975E 2xx \u4E0D\u629B\u3002\n */\nexport function postBdsLifecycleEvent(\n type: BdsLifecycleEventType,\n detail?: string,\n opts?: { host?: string; port?: number; timeoutMs?: number }\n): Promise<void> {\n const host = opts?.host ?? \"127.0.0.1\";\n const port = opts?.port ?? resolveDbPort();\n const timeoutMs = opts?.timeoutMs ?? 3_000;\n const payload = JSON.stringify({\n type,\n detail: detail ?? undefined,\n });\n\n return new Promise((resolve) => {\n const req = http.request(\n {\n hostname: host,\n port,\n path: \"/api/sfmc/qq/events\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(payload),\n },\n timeout: timeoutMs,\n },\n (res) => {\n res.resume();\n res.on(\"end\", () => {\n const code = res.statusCode ?? 0;\n if (code < 200 || code >= 300) {\n log.warn(`[QQ\u4E8B\u4EF6] db-server \u8FD4\u56DE HTTP ${code} (type=${type})`);\n }\n resolve();\n });\n }\n );\n req.on(\"timeout\", () => {\n req.destroy();\n log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u8D85\u65F6 ${timeoutMs}ms (type=${type})`);\n resolve();\n });\n req.on(\"error\", (err) => {\n log.warn(`[QQ\u4E8B\u4EF6] \u4E0A\u62A5\u5931\u8D25: ${err.message} (type=${type})`);\n resolve();\n });\n req.write(payload);\n req.end();\n });\n}\n"],
5
+ "mappings": "AAOA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,OAAO,UAAU;AACjB,SAAS,WAAW;AACpB,SAAS,gBAAgB;AAIzB,SAAS,gBAAwB;AAC/B,MAAI;AACF,UAAM,MAAM;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA,EAAE,GAAG,kBAAkB;AAAA,IACzB;AACA,UAAM,IAAI,OAAO,IAAI,WAAW,IAAI;AACpC,WAAO,OAAO,SAAS,CAAC,KAAK,IAAI,IAAI,IAAI;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,sBACd,MACA,QACA,MACe;AACf,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,MAAM,QAAQ,cAAc;AACzC,QAAM,YAAY,MAAM,aAAa;AACrC,QAAM,UAAU,KAAK,UAAU;AAAA,IAC7B;AAAA,IACA,QAAQ,UAAU;AAAA,EACpB,CAAC;AAED,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,MAAM,KAAK;AAAA,MACf;AAAA,QACE,UAAU;AAAA,QACV;AAAA,QACA,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,kBAAkB,OAAO,WAAW,OAAO;AAAA,QAC7C;AAAA,QACA,SAAS;AAAA,MACX;AAAA,MACA,CAAC,QAAQ;AACP,YAAI,OAAO;AACX,YAAI,GAAG,OAAO,MAAM;AAClB,gBAAM,OAAO,IAAI,cAAc;AAC/B,cAAI,OAAO,OAAO,QAAQ,KAAK;AAC7B,gBAAI,KAAK,gDAA4B,IAAI,UAAU,IAAI,GAAG;AAAA,UAC5D;AACA,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,GAAG,WAAW,MAAM;AACtB,UAAI,QAAQ;AACZ,UAAI,KAAK,6CAAe,SAAS,YAAY,IAAI,GAAG;AACpD,cAAQ;AAAA,IACV,CAAC;AACD,QAAI,GAAG,SAAS,CAAC,QAAQ;AACvB,UAAI,KAAK,8CAAgB,IAAI,OAAO,UAAU,IAAI,GAAG;AACrD,cAAQ;AAAA,IACV,CAAC;AACD,QAAI,MAAM,OAAO;AACjB,QAAI,IAAI;AAAA,EACV,CAAC;AACH;",
6
+ "names": []
7
+ }
package/dist/qqutil.d.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  /**
2
- * qqutil.ts — QQ 通知工具 (基于 LLBot HTTP OneBot 11)
2
+ * qqutil.ts — BDS 运维与更新通知推送工具(支持官方 Bot 与 LLBot 双后端)
3
3
  *
4
- * 改进:
5
- * - sendTimeout 提供总超时,避免通知发送挂死主流程
6
- * - 静默模式 (失败不抛出),保证主流程不被通知干扰
4
+ * 核心机制:
5
+ * - 双后端适配:根据 `qq_backend` 配置自动分发至官方开放平台发群接口或 LLBot OneBot HTTP 接口
6
+ * - 故障隔离:通知发送失败仅记录告警日志并不中断主流程,确保主服务运维正常执行
7
+ * - 发送超时熔断:设置请求超时时间,防止外部网络响应延迟堵塞 BDS 启动与更新
8
+ */
9
+ /**
10
+ * 检查平台中 `qq-bridge` 模块是否处于启用状态。
11
+ *
12
+ * @returns 若启用返回 `true`,否则返回 `false`。
7
13
  */
8
- /** 检查 qq-bridge 模块是否启用 */
9
14
  export declare function isQqBridgeEnabled(): boolean;
10
15
  export declare function sendText(text: string): Promise<void>;
11
16
  export declare function sendMixed(segments: unknown[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"qqutil.d.ts","sourceRoot":"","sources":["../src/qqutil.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuBH,0BAA0B;AAC1B,wBAAgB,iBAAiB,IAAI,OAAO,CAO3C;AA6CD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAY1D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAYlE;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMlF"}
1
+ {"version":3,"file":"qqutil.d.ts","sourceRoot":"","sources":["../src/qqutil.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAwBH;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAO3C;AAgED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1D;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgClE;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMlF"}
package/dist/qqutil.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  modulePath,
4
4
  readJson
5
5
  } from "@sfmc-bds/sdk/node/config";
6
+ import { sendGroupTextMessage } from "@sfmc-bds/sdk/node/qq-official";
6
7
  import http from "node:http";
7
8
  import { log } from "./log.js";
8
9
  import { ROOT_DIR } from "./paths.js";
@@ -19,15 +20,18 @@ function isQqBridgeEnabled() {
19
20
  const mod = catalog.modules?.find((m) => m.id === "qq-bridge" || m.configKey === "qq_bridge");
20
21
  return mod ? lock.modules?.[mod.id ?? ""]?.enabled === true : false;
21
22
  }
23
+ function isOfficialBackend(cfg) {
24
+ return (cfg.qq_backend ?? "official") === "official";
25
+ }
22
26
  function sendToLLBot(payload, timeoutMs = 5e3) {
23
27
  const cfg = getConfig();
24
- const url = new URL(cfg.llbot_http || "http://127.0.0.1:3000");
28
+ const url = new URL(cfg.llbot?.http || `http://${cfg.llbot?.host || "127.0.0.1"}:${cfg.llbot?.port || 3004}`);
25
29
  const data = JSON.stringify(payload);
26
30
  return new Promise((resolve, reject) => {
27
31
  const req = http.request(
28
32
  {
29
33
  hostname: url.hostname,
30
- port: url.port || 3e3,
34
+ port: url.port || 3004,
31
35
  path: "/send_group_msg",
32
36
  method: "POST",
33
37
  headers: {
@@ -60,30 +64,69 @@ async function safeSend(label, fn) {
60
64
  log.warn(`[QQ] ${label} \u5931\u8D25: ${e.message}`);
61
65
  }
62
66
  }
67
+ async function sendOfficialText(text) {
68
+ const cfg = getConfig();
69
+ const appId = String(cfg.official?.app_id ?? "");
70
+ const appSecret = String(cfg.official?.app_secret ?? "");
71
+ const groupOpenid = String(cfg.official?.group_openid ?? "");
72
+ if (!appId || !appSecret || !groupOpenid) {
73
+ throw new Error("\u5B98\u65B9\u540E\u7AEF\u7F3A\u5C11 official.app_id / official.app_secret / official.group_openid");
74
+ }
75
+ const result = await sendGroupTextMessage(
76
+ { appId, appSecret, sandbox: cfg.official?.sandbox === true },
77
+ { groupOpenid, content: text }
78
+ );
79
+ if (!result.ok) throw new Error(result.error);
80
+ }
63
81
  async function sendText(text) {
64
82
  const cfg = getConfig();
65
- if (!isQqBridgeEnabled() || !cfg.qq_group_id) {
66
- log.warn("[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931");
83
+ if (!isQqBridgeEnabled()) {
84
+ log.warn("[QQ] qq-bridge \u672A\u542F\u7528");
85
+ return;
86
+ }
87
+ if (isOfficialBackend(cfg)) {
88
+ await safeSend("sendText(official)", () => sendOfficialText(text));
89
+ return;
90
+ }
91
+ const groupId = cfg.llbot?.group_id;
92
+ if (!groupId) {
93
+ log.warn("[QQ] llbot.group_id \u7F3A\u5931");
67
94
  return;
68
95
  }
69
96
  await safeSend(
70
- "sendText",
97
+ "sendText(llbot)",
71
98
  () => sendToLLBot({
72
- group_id: parseInt(cfg.qq_group_id ?? "0", 10),
99
+ group_id: parseInt(groupId, 10),
73
100
  message: [{ type: "text", data: { text } }]
74
101
  })
75
102
  );
76
103
  }
77
104
  async function sendMixed(segments) {
78
105
  const cfg = getConfig();
79
- if (!isQqBridgeEnabled() || !cfg.qq_group_id) {
80
- log.warn("[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931");
106
+ if (!isQqBridgeEnabled()) {
107
+ log.warn("[QQ] qq-bridge \u672A\u542F\u7528");
108
+ return;
109
+ }
110
+ if (isOfficialBackend(cfg)) {
111
+ const text = segments.map((seg) => {
112
+ const s = seg;
113
+ if (s?.type === "text") return String(s.data?.text ?? "");
114
+ if (s?.type === "image") return "[\u56FE\u7247]";
115
+ return "";
116
+ }).join("").trim();
117
+ if (!text) return;
118
+ await safeSend("sendMixed(official)", () => sendOfficialText(text));
119
+ return;
120
+ }
121
+ const groupId = cfg.llbot?.group_id;
122
+ if (!groupId) {
123
+ log.warn("[QQ] llbot.group_id \u7F3A\u5931");
81
124
  return;
82
125
  }
83
126
  await safeSend(
84
- "sendMixed",
127
+ "sendMixed(llbot)",
85
128
  () => sendToLLBot({
86
- group_id: parseInt(cfg.qq_group_id ?? "0", 10),
129
+ group_id: parseInt(groupId, 10),
87
130
  message: segments
88
131
  })
89
132
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/qqutil.ts"],
4
- "sourcesContent": ["/**\n * qqutil.ts \u2014 QQ \u901A\u77E5\u5DE5\u5177 (\u57FA\u4E8E LLBot HTTP OneBot 11)\n *\n * \u6539\u8FDB:\n * - sendTimeout \u63D0\u4F9B\u603B\u8D85\u65F6\uFF0C\u907F\u514D\u901A\u77E5\u53D1\u9001\u6302\u6B7B\u4E3B\u6D41\u7A0B\n * - \u9759\u9ED8\u6A21\u5F0F (\u5931\u8D25\u4E0D\u629B\u51FA)\uFF0C\u4FDD\u8BC1\u4E3B\u6D41\u7A0B\u4E0D\u88AB\u901A\u77E5\u5E72\u6270\n */\n\nimport {\n configPath,\n modulePath,\n readJson,\n type Catalog,\n type ModuleLock,\n type QQBridgeConfig,\n} from \"@sfmc-bds/sdk/node/config\";\nimport http from \"node:http\";\nimport { log } from \"./log.js\";\nimport { ROOT_DIR } from \"./paths.js\";\n\ntype QqConfig = Pick<QQBridgeConfig, \"llbot_http\" | \"qq_group_id\">;\n\nlet cachedCfg: QqConfig | null = null;\nfunction getConfig(): QqConfig {\n if (cachedCfg) return cachedCfg;\n cachedCfg = (readJson<QQBridgeConfig>(configPath(ROOT_DIR, \"qq_config.json\")) ?? {}) as QqConfig;\n return cachedCfg;\n}\n\n/** \u68C0\u67E5 qq-bridge \u6A21\u5757\u662F\u5426\u542F\u7528 */\nexport function isQqBridgeEnabled(): boolean {\n const catalog = readJson<Catalog>(modulePath(ROOT_DIR, \"catalog.json\"));\n const lock = readJson<ModuleLock>(modulePath(ROOT_DIR, \"module-lock.json\"));\n if (!catalog || !lock) return true; // \u6A21\u5757\u76EE\u5F55\u7F3A\u5931\u5219\u4FDD\u5B88\u89C6\u4E3A\u53EF\u7528\n const mod = catalog.modules?.find((m) => m.id === \"qq-bridge\" || m.configKey === \"qq_bridge\") as\n { id?: string } | undefined;\n return mod ? lock.modules?.[mod.id ?? \"\"]?.enabled === true : false;\n}\n\nfunction sendToLLBot(payload: unknown, timeoutMs = 5_000): Promise<void> {\n const cfg = getConfig();\n const url = new URL(cfg.llbot_http || \"http://127.0.0.1:3000\");\n const data = JSON.stringify(payload);\n return new Promise((resolve, reject) => {\n const req = http.request(\n {\n hostname: url.hostname,\n port: url.port || 3000,\n path: \"/send_group_msg\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(data),\n },\n timeout: timeoutMs,\n },\n (res) => {\n let body = \"\";\n res.on(\"data\", (c) => (body += c));\n res.on(\"end\", () => {\n if (res.statusCode === 200) resolve();\n else reject(new Error(`LLBot HTTP ${res.statusCode}: ${body.slice(0, 200)}`));\n });\n }\n );\n req.on(\"timeout\", () => {\n req.destroy(new Error(`LLBot \u8D85\u65F6 ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.write(data);\n req.end();\n });\n}\n\nasync function safeSend(label: string, fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (e) {\n log.warn(`[QQ] ${label} \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n\nexport async function sendText(text: string): Promise<void> {\n const cfg = getConfig();\n if (!isQqBridgeEnabled() || !cfg.qq_group_id) {\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931\");\n return;\n }\n await safeSend(\"sendText\", () =>\n sendToLLBot({\n group_id: parseInt(cfg.qq_group_id ?? \"0\", 10),\n message: [{ type: \"text\", data: { text } }],\n })\n );\n}\n\nexport async function sendMixed(segments: unknown[]): Promise<void> {\n const cfg = getConfig();\n if (!isQqBridgeEnabled() || !cfg.qq_group_id) {\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\u6216 qq_group_id \u7F3A\u5931\");\n return;\n }\n await safeSend(\"sendMixed\", () =>\n sendToLLBot({\n group_id: parseInt(cfg.qq_group_id ?? \"0\", 10),\n message: segments,\n })\n );\n}\n\nexport async function sendWithImage(text: string, base64Img: string): Promise<void> {\n const segments: unknown[] = [];\n if (text) segments.push({ type: \"text\", data: { text } });\n if (base64Img) segments.push({ type: \"image\", data: { file: `base64://${base64Img}` } });\n if (segments.length === 0) return;\n await sendMixed(segments);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * qqutil.ts \u2014 BDS \u8FD0\u7EF4\u4E0E\u66F4\u65B0\u901A\u77E5\u63A8\u9001\u5DE5\u5177\uFF08\u652F\u6301\u5B98\u65B9 Bot \u4E0E LLBot \u53CC\u540E\u7AEF\uFF09\n *\n * \u6838\u5FC3\u673A\u5236\uFF1A\n * - \u53CC\u540E\u7AEF\u9002\u914D\uFF1A\u6839\u636E `qq_backend` \u914D\u7F6E\u81EA\u52A8\u5206\u53D1\u81F3\u5B98\u65B9\u5F00\u653E\u5E73\u53F0\u53D1\u7FA4\u63A5\u53E3\u6216 LLBot OneBot HTTP \u63A5\u53E3\n * - \u6545\u969C\u9694\u79BB\uFF1A\u901A\u77E5\u53D1\u9001\u5931\u8D25\u4EC5\u8BB0\u5F55\u544A\u8B66\u65E5\u5FD7\u5E76\u4E0D\u4E2D\u65AD\u4E3B\u6D41\u7A0B\uFF0C\u786E\u4FDD\u4E3B\u670D\u52A1\u8FD0\u7EF4\u6B63\u5E38\u6267\u884C\n * - \u53D1\u9001\u8D85\u65F6\u7194\u65AD\uFF1A\u8BBE\u7F6E\u8BF7\u6C42\u8D85\u65F6\u65F6\u95F4\uFF0C\u9632\u6B62\u5916\u90E8\u7F51\u7EDC\u54CD\u5E94\u5EF6\u8FDF\u5835\u585E BDS \u542F\u52A8\u4E0E\u66F4\u65B0\n */\n\nimport {\n configPath,\n modulePath,\n readJson,\n type Catalog,\n type ModuleLock,\n type QQBridgeConfig,\n} from \"@sfmc-bds/sdk/node/config\";\nimport { sendGroupTextMessage } from \"@sfmc-bds/sdk/node/qq-official\";\nimport http from \"node:http\";\nimport { log } from \"./log.js\";\nimport { ROOT_DIR } from \"./paths.js\";\n\ntype QqConfig = Pick<QQBridgeConfig, \"qq_backend\" | \"official\" | \"llbot\">;\n\nlet cachedCfg: QqConfig | null = null;\nfunction getConfig(): QqConfig {\n if (cachedCfg) return cachedCfg;\n cachedCfg = (readJson<QQBridgeConfig>(configPath(ROOT_DIR, \"qq_config.json\")) ?? {}) as QqConfig;\n return cachedCfg;\n}\n\n/**\n * \u68C0\u67E5\u5E73\u53F0\u4E2D `qq-bridge` \u6A21\u5757\u662F\u5426\u5904\u4E8E\u542F\u7528\u72B6\u6001\u3002\n *\n * @returns \u82E5\u542F\u7528\u8FD4\u56DE `true`\uFF0C\u5426\u5219\u8FD4\u56DE `false`\u3002\n */\nexport function isQqBridgeEnabled(): boolean {\n\n const catalog = readJson<Catalog>(modulePath(ROOT_DIR, \"catalog.json\"));\n const lock = readJson<ModuleLock>(modulePath(ROOT_DIR, \"module-lock.json\"));\n if (!catalog || !lock) return true; // \u6A21\u5757\u76EE\u5F55\u7F3A\u5931\u5219\u4FDD\u5B88\u89C6\u4E3A\u53EF\u7528\n const mod = catalog.modules?.find((m) => m.id === \"qq-bridge\" || m.configKey === \"qq_bridge\");\n return mod ? lock.modules?.[mod.id ?? \"\"]?.enabled === true : false;\n}\n\nfunction isOfficialBackend(cfg: QqConfig): boolean {\n return (cfg.qq_backend ?? \"official\") === \"official\";\n}\n\nfunction sendToLLBot(payload: unknown, timeoutMs = 5_000): Promise<void> {\n const cfg = getConfig();\n const url = new URL(cfg.llbot?.http || `http://${cfg.llbot?.host || \"127.0.0.1\"}:${cfg.llbot?.port || 3004}`);\n const data = JSON.stringify(payload);\n return new Promise((resolve, reject) => {\n const req = http.request(\n {\n hostname: url.hostname,\n port: url.port || 3004,\n path: \"/send_group_msg\",\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Length\": Buffer.byteLength(data),\n },\n timeout: timeoutMs,\n },\n (res) => {\n let body = \"\";\n res.on(\"data\", (c) => (body += c));\n res.on(\"end\", () => {\n if (res.statusCode === 200) resolve();\n else reject(new Error(`LLBot HTTP ${res.statusCode}: ${body.slice(0, 200)}`));\n });\n }\n );\n req.on(\"timeout\", () => {\n req.destroy(new Error(`LLBot \u8D85\u65F6 ${timeoutMs}ms`));\n });\n req.on(\"error\", reject);\n req.write(data);\n req.end();\n });\n}\n\nasync function safeSend(label: string, fn: () => Promise<void>): Promise<void> {\n try {\n await fn();\n } catch (e) {\n log.warn(`[QQ] ${label} \u5931\u8D25: ${(e as Error).message}`);\n }\n}\n\nasync function sendOfficialText(text: string): Promise<void> {\n const cfg = getConfig();\n const appId = String(cfg.official?.app_id ?? \"\");\n const appSecret = String(cfg.official?.app_secret ?? \"\");\n const groupOpenid = String(cfg.official?.group_openid ?? \"\");\n if (!appId || !appSecret || !groupOpenid) {\n throw new Error(\"\u5B98\u65B9\u540E\u7AEF\u7F3A\u5C11 official.app_id / official.app_secret / official.group_openid\");\n }\n const result = await sendGroupTextMessage(\n { appId, appSecret, sandbox: cfg.official?.sandbox === true },\n { groupOpenid, content: text }\n );\n if (!result.ok) throw new Error(result.error);\n}\n\nexport async function sendText(text: string): Promise<void> {\n const cfg = getConfig();\n if (!isQqBridgeEnabled()) {\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\");\n return;\n }\n if (isOfficialBackend(cfg)) {\n await safeSend(\"sendText(official)\", () => sendOfficialText(text));\n return;\n }\n const groupId = cfg.llbot?.group_id;\n if (!groupId) {\n log.warn(\"[QQ] llbot.group_id \u7F3A\u5931\");\n return;\n }\n await safeSend(\"sendText(llbot)\", () =>\n sendToLLBot({\n group_id: parseInt(groupId, 10),\n message: [{ type: \"text\", data: { text } }],\n })\n );\n}\n\nexport async function sendMixed(segments: unknown[]): Promise<void> {\n const cfg = getConfig();\n if (!isQqBridgeEnabled()) {\n log.warn(\"[QQ] qq-bridge \u672A\u542F\u7528\");\n return;\n }\n if (isOfficialBackend(cfg)) {\n // \u5B98\u65B9\u8DEF\u5F84\u4EC5\u62FC\u7EAF\u6587\u672C\uFF1B\u56FE\u7247\u6BB5\u964D\u7EA7\u4E3A [\u56FE\u7247]\n const text = segments\n .map((seg) => {\n const s = seg as { type?: string; data?: { text?: string } };\n if (s?.type === \"text\") return String(s.data?.text ?? \"\");\n if (s?.type === \"image\") return \"[\u56FE\u7247]\";\n return \"\";\n })\n .join(\"\")\n .trim();\n if (!text) return;\n await safeSend(\"sendMixed(official)\", () => sendOfficialText(text));\n return;\n }\n const groupId = cfg.llbot?.group_id;\n if (!groupId) {\n log.warn(\"[QQ] llbot.group_id \u7F3A\u5931\");\n return;\n }\n await safeSend(\"sendMixed(llbot)\", () =>\n sendToLLBot({\n group_id: parseInt(groupId, 10),\n message: segments,\n })\n );\n}\n\nexport async function sendWithImage(text: string, base64Img: string): Promise<void> {\n const segments: unknown[] = [];\n if (text) segments.push({ type: \"text\", data: { text } });\n if (base64Img) segments.push({ type: \"image\", data: { file: `base64://${base64Img}` } });\n if (segments.length === 0) return;\n await sendMixed(segments);\n}\n\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../src/rollback.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAAe,UAAU,EAAgB,MAAM,UAAU,CAAC;AAGjE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,yBAAyB;AACzB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAOhE;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAM1C;AAED,wBAAgB,kBAAkB,IAAI,cAAc,GAAG,IAAI,CAE1D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA8B3F;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAMzG;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"rollback.d.ts","sourceRoot":"","sources":["../src/rollback.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAAiC,UAAU,EAAgB,MAAM,UAAU,CAAC;AAGnF,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,yBAAyB;AACzB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAOhE;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAM1C;AAED,wBAAgB,kBAAkB,IAAI,cAAc,GAAG,IAAI,CAE1D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA8B3F;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAMzG;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
package/dist/rollback.js CHANGED
@@ -2,7 +2,7 @@ import { readJson, writeJson } from "@sfmc-bds/sdk/node/config";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { ROLLBACK_MARKER } from "./paths.js";
5
- import { copyDirSync, getDirSize, emptyDirSync } from "./fsx.js";
5
+ import { copyDirSync, copyFileSyncSafe, getDirSize, emptyDirSync } from "./fsx.js";
6
6
  import { log } from "./log.js";
7
7
  function writeRollbackMarker(marker) {
8
8
  try {
@@ -41,7 +41,7 @@ function rollbackFromBackup(marker) {
41
41
  if (fs.existsSync(dest)) emptyDirSync(dest);
42
42
  copyDirSync(src, dest);
43
43
  } else {
44
- fs.copyFileSync(src, dest);
44
+ copyFileSyncSafe(src, dest);
45
45
  }
46
46
  log.info(`[\u56DE\u6EDA] \u5DF2\u6062\u590D: ${item}`);
47
47
  } catch (e) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/rollback.ts"],
4
- "sourcesContent": ["/**\n * rollback.ts \u2014 \u56DE\u6EDA\u652F\u6301\n *\n * \u5728\u66F4\u65B0\u5F00\u59CB\u524D\u8BB0\u5F55: \u5F53\u524D BDS \u76EE\u5F55 + \u5907\u4EFD\u76EE\u5F55\u3002\n * \u4E3B\u6D41\u7A0B\u4EFB\u610F\u9636\u6BB5\u5931\u8D25\u65F6\uFF0C\u5C1D\u8BD5\u53CD\u5411\u6062\u590D preserves \u5230 BDS_PATH\uFF0C\n * \u7136\u540E\u4ECE\u5907\u4EFD\u91CD\u65B0\u653E\u56DE preserve \u5185\u5BB9\uFF0C\u6062\u590D\u65E7\u914D\u7F6E\u3002\n *\n * \u6301\u4E45\u5316: <SFMC_ROOT>/.sfmc/last_update_rollback.json \u7528\u4E8E\u8DE8\u8FDB\u7A0B\u8BB0\u5F55\u3002\n */\n\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { ROLLBACK_MARKER } from \"./paths.js\";\nimport { copyDirSync, getDirSize, emptyDirSync } from \"./fsx.js\";\nimport { log } from \"./log.js\";\n\nexport interface RollbackMarker {\n timestamp: number;\n bds_path: string;\n backup_dir: string;\n preserve: string[];\n previous_version: string;\n}\n\n/** \u5728\u5907\u4EFD\u5B8C\u6210\u540E\u8C03\u7528: \u8BB0\u5F55\u5FEB\u7167\u6307\u793A\u7B26\u3002 */\nexport function writeRollbackMarker(marker: RollbackMarker): void {\n try {\n writeJson(ROLLBACK_MARKER, marker);\n log.info(`[\u56DE\u6EDA] \u5DF2\u8BB0\u5F55\u56DE\u6EDA\u6807\u8BB0 -> ${ROLLBACK_MARKER}`);\n } catch (e) {\n log.warn(`[\u56DE\u6EDA] \u65E0\u6CD5\u5199\u5165\u6807\u8BB0: ${(e as Error).message}`);\n }\n}\n\nexport function clearRollbackMarker(): void {\n try {\n if (fs.existsSync(ROLLBACK_MARKER)) fs.unlinkSync(ROLLBACK_MARKER);\n } catch {\n /* ignore */\n }\n}\n\nexport function readRollbackMarker(): RollbackMarker | null {\n return readJson<RollbackMarker>(ROLLBACK_MARKER) ?? null;\n}\n\n/**\n * \u7D27\u6025\u56DE\u6EDA: \u628A preserve \u9879\u4ECE backup_dir \u62F7\u56DE bds_path\u3002\n * \u7528\u6CD5: \u5728 catch \u5206\u652F\u8C03\u7528\uFF0C\u5982\u679C\u4E3B\u6D41\u7A0B\u5931\u8D25\u5BFC\u81F4 BDS \u76EE\u5F55\u7834\u574F\u3002\n */\nexport function rollbackFromBackup(marker: RollbackMarker): { ok: boolean; reason?: string } {\n const { bds_path, backup_dir, preserve } = marker;\n if (!fs.existsSync(backup_dir)) {\n return { ok: false, reason: `\u5907\u4EFD\u76EE\u5F55\u4E0D\u5B58\u5728: ${backup_dir}` };\n }\n log.warn(`[\u56DE\u6EDA] \u5F00\u59CB\u6062\u590D ${preserve.length} \u9879\u5230 ${bds_path}`);\n for (const item of preserve) {\n const src = path.join(backup_dir, item);\n const dest = path.join(bds_path, item);\n if (!fs.existsSync(src)) {\n log.warn(`[\u56DE\u6EDA] \u8DF3\u8FC7 (\u5907\u4EFD\u4E2D\u4E0D\u5B58\u5728): ${item}`);\n continue;\n }\n try {\n // \u5148\u786E\u4FDD\u76EE\u6807\u76EE\u5F55\u5B58\u5728 (BDS \u76EE\u5F55\u53EF\u80FD\u5DF2\u88AB\u89E3\u538B\u8986\u76D6\u4E3A\u7A7A)\n const destDir = path.dirname(dest);\n if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });\n if (fs.statSync(src).isDirectory()) {\n // \u5148\u5220\u9664\u76EE\u6807\uFF0C\u518D\u590D\u5236\u76EE\u5F55\n if (fs.existsSync(dest)) emptyDirSync(dest);\n copyDirSync(src, dest);\n } else {\n fs.copyFileSync(src, dest);\n }\n log.info(`[\u56DE\u6EDA] \u5DF2\u6062\u590D: ${item}`);\n } catch (e) {\n log.warn(`[\u56DE\u6EDA] \u6062\u590D\u5931\u8D25 ${item}: ${(e as Error).message}`);\n }\n }\n return { ok: true };\n}\n\n/** \u68C0\u67E5 BDS_PATH \u4E2D\u5173\u952E\u6587\u4EF6\u662F\u5426\u90FD\u8FD8\u5728 (\u66F4\u65B0\u540E\u5B8C\u6574\u6027 sanity check) */\nexport function verifyBdsInstall(bds_path: string, expected: string[]): { ok: boolean; missing: string[] } {\n const missing: string[] = [];\n for (const f of expected) {\n if (!fs.existsSync(path.join(bds_path, f))) missing.push(f);\n }\n return { ok: missing.length === 0, missing };\n}\n\nexport { getDirSize };\n"],
5
- "mappings": "AAUA,SAAS,UAAU,iBAAiB;AACpC,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAChC,SAAS,aAAa,YAAY,oBAAoB;AACtD,SAAS,WAAW;AAWb,SAAS,oBAAoB,QAA8B;AAChE,MAAI;AACF,cAAU,iBAAiB,MAAM;AACjC,QAAI,KAAK,gEAAmB,eAAe,EAAE;AAAA,EAC/C,SAAS,GAAG;AACV,QAAI,KAAK,wDAAiB,EAAY,OAAO,EAAE;AAAA,EACjD;AACF;AAEO,SAAS,sBAA4B;AAC1C,MAAI;AACF,QAAI,GAAG,WAAW,eAAe,EAAG,IAAG,WAAW,eAAe;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,qBAA4C;AAC1D,SAAO,SAAyB,eAAe,KAAK;AACtD;AAMO,SAAS,mBAAmB,QAA0D;AAC3F,QAAM,EAAE,UAAU,YAAY,SAAS,IAAI;AAC3C,MAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAC9B,WAAO,EAAE,IAAI,OAAO,QAAQ,+CAAY,UAAU,GAAG;AAAA,EACvD;AACA,MAAI,KAAK,2CAAa,SAAS,MAAM,iBAAO,QAAQ,EAAE;AACtD,aAAW,QAAQ,UAAU;AAC3B,UAAM,MAAM,KAAK,KAAK,YAAY,IAAI;AACtC,UAAM,OAAO,KAAK,KAAK,UAAU,IAAI;AACrC,QAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvB,UAAI,KAAK,uEAAqB,IAAI,EAAE;AACpC;AAAA,IACF;AACA,QAAI;AAEF,YAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,UAAI,CAAC,GAAG,WAAW,OAAO,EAAG,IAAG,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACtE,UAAI,GAAG,SAAS,GAAG,EAAE,YAAY,GAAG;AAElC,YAAI,GAAG,WAAW,IAAI,EAAG,cAAa,IAAI;AAC1C,oBAAY,KAAK,IAAI;AAAA,MACvB,OAAO;AACL,WAAG,aAAa,KAAK,IAAI;AAAA,MAC3B;AACA,UAAI,KAAK,sCAAa,IAAI,EAAE;AAAA,IAC9B,SAAS,GAAG;AACV,UAAI,KAAK,2CAAa,IAAI,KAAM,EAAY,OAAO,EAAE;AAAA,IACvD;AAAA,EACF;AACA,SAAO,EAAE,IAAI,KAAK;AACpB;AAGO,SAAS,iBAAiB,UAAkB,UAAwD;AACzG,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,GAAG,WAAW,KAAK,KAAK,UAAU,CAAC,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,EAC5D;AACA,SAAO,EAAE,IAAI,QAAQ,WAAW,GAAG,QAAQ;AAC7C;",
4
+ "sourcesContent": ["/**\n * rollback.ts \u2014 \u56DE\u6EDA\u652F\u6301\n *\n * \u5728\u66F4\u65B0\u5F00\u59CB\u524D\u8BB0\u5F55: \u5F53\u524D BDS \u76EE\u5F55 + \u5907\u4EFD\u76EE\u5F55\u3002\n * \u4E3B\u6D41\u7A0B\u4EFB\u610F\u9636\u6BB5\u5931\u8D25\u65F6\uFF0C\u5C1D\u8BD5\u53CD\u5411\u6062\u590D preserves \u5230 BDS_PATH\uFF0C\n * \u7136\u540E\u4ECE\u5907\u4EFD\u91CD\u65B0\u653E\u56DE preserve \u5185\u5BB9\uFF0C\u6062\u590D\u65E7\u914D\u7F6E\u3002\n *\n * \u6301\u4E45\u5316: <SFMC_ROOT>/.sfmc/last_update_rollback.json \u7528\u4E8E\u8DE8\u8FDB\u7A0B\u8BB0\u5F55\u3002\n */\n\nimport { readJson, writeJson } from \"@sfmc-bds/sdk/node/config\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { ROLLBACK_MARKER } from \"./paths.js\";\nimport { copyDirSync, copyFileSyncSafe, getDirSize, emptyDirSync } from \"./fsx.js\";\nimport { log } from \"./log.js\";\n\nexport interface RollbackMarker {\n timestamp: number;\n bds_path: string;\n backup_dir: string;\n preserve: string[];\n previous_version: string;\n}\n\n/** \u5728\u5907\u4EFD\u5B8C\u6210\u540E\u8C03\u7528: \u8BB0\u5F55\u5FEB\u7167\u6307\u793A\u7B26\u3002 */\nexport function writeRollbackMarker(marker: RollbackMarker): void {\n try {\n writeJson(ROLLBACK_MARKER, marker);\n log.info(`[\u56DE\u6EDA] \u5DF2\u8BB0\u5F55\u56DE\u6EDA\u6807\u8BB0 -> ${ROLLBACK_MARKER}`);\n } catch (e) {\n log.warn(`[\u56DE\u6EDA] \u65E0\u6CD5\u5199\u5165\u6807\u8BB0: ${(e as Error).message}`);\n }\n}\n\nexport function clearRollbackMarker(): void {\n try {\n if (fs.existsSync(ROLLBACK_MARKER)) fs.unlinkSync(ROLLBACK_MARKER);\n } catch {\n /* ignore */\n }\n}\n\nexport function readRollbackMarker(): RollbackMarker | null {\n return readJson<RollbackMarker>(ROLLBACK_MARKER) ?? null;\n}\n\n/**\n * \u7D27\u6025\u56DE\u6EDA: \u628A preserve \u9879\u4ECE backup_dir \u62F7\u56DE bds_path\u3002\n * \u7528\u6CD5: \u5728 catch \u5206\u652F\u8C03\u7528\uFF0C\u5982\u679C\u4E3B\u6D41\u7A0B\u5931\u8D25\u5BFC\u81F4 BDS \u76EE\u5F55\u7834\u574F\u3002\n */\nexport function rollbackFromBackup(marker: RollbackMarker): { ok: boolean; reason?: string } {\n const { bds_path, backup_dir, preserve } = marker;\n if (!fs.existsSync(backup_dir)) {\n return { ok: false, reason: `\u5907\u4EFD\u76EE\u5F55\u4E0D\u5B58\u5728: ${backup_dir}` };\n }\n log.warn(`[\u56DE\u6EDA] \u5F00\u59CB\u6062\u590D ${preserve.length} \u9879\u5230 ${bds_path}`);\n for (const item of preserve) {\n const src = path.join(backup_dir, item);\n const dest = path.join(bds_path, item);\n if (!fs.existsSync(src)) {\n log.warn(`[\u56DE\u6EDA] \u8DF3\u8FC7 (\u5907\u4EFD\u4E2D\u4E0D\u5B58\u5728): ${item}`);\n continue;\n }\n try {\n // \u5148\u786E\u4FDD\u76EE\u6807\u76EE\u5F55\u5B58\u5728 (BDS \u76EE\u5F55\u53EF\u80FD\u5DF2\u88AB\u89E3\u538B\u8986\u76D6\u4E3A\u7A7A)\n const destDir = path.dirname(dest);\n if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });\n if (fs.statSync(src).isDirectory()) {\n // \u5148\u5220\u9664\u76EE\u6807\uFF0C\u518D\u590D\u5236\u76EE\u5F55\n if (fs.existsSync(dest)) emptyDirSync(dest);\n copyDirSync(src, dest);\n } else {\n copyFileSyncSafe(src, dest);\n }\n log.info(`[\u56DE\u6EDA] \u5DF2\u6062\u590D: ${item}`);\n } catch (e) {\n log.warn(`[\u56DE\u6EDA] \u6062\u590D\u5931\u8D25 ${item}: ${(e as Error).message}`);\n }\n }\n return { ok: true };\n}\n\n/** \u68C0\u67E5 BDS_PATH \u4E2D\u5173\u952E\u6587\u4EF6\u662F\u5426\u90FD\u8FD8\u5728 (\u66F4\u65B0\u540E\u5B8C\u6574\u6027 sanity check) */\nexport function verifyBdsInstall(bds_path: string, expected: string[]): { ok: boolean; missing: string[] } {\n const missing: string[] = [];\n for (const f of expected) {\n if (!fs.existsSync(path.join(bds_path, f))) missing.push(f);\n }\n return { ok: missing.length === 0, missing };\n}\n\nexport { getDirSize };\n"],
5
+ "mappings": "AAUA,SAAS,UAAU,iBAAiB;AACpC,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAChC,SAAS,aAAa,kBAAkB,YAAY,oBAAoB;AACxE,SAAS,WAAW;AAWb,SAAS,oBAAoB,QAA8B;AAChE,MAAI;AACF,cAAU,iBAAiB,MAAM;AACjC,QAAI,KAAK,gEAAmB,eAAe,EAAE;AAAA,EAC/C,SAAS,GAAG;AACV,QAAI,KAAK,wDAAiB,EAAY,OAAO,EAAE;AAAA,EACjD;AACF;AAEO,SAAS,sBAA4B;AAC1C,MAAI;AACF,QAAI,GAAG,WAAW,eAAe,EAAG,IAAG,WAAW,eAAe;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,qBAA4C;AAC1D,SAAO,SAAyB,eAAe,KAAK;AACtD;AAMO,SAAS,mBAAmB,QAA0D;AAC3F,QAAM,EAAE,UAAU,YAAY,SAAS,IAAI;AAC3C,MAAI,CAAC,GAAG,WAAW,UAAU,GAAG;AAC9B,WAAO,EAAE,IAAI,OAAO,QAAQ,+CAAY,UAAU,GAAG;AAAA,EACvD;AACA,MAAI,KAAK,2CAAa,SAAS,MAAM,iBAAO,QAAQ,EAAE;AACtD,aAAW,QAAQ,UAAU;AAC3B,UAAM,MAAM,KAAK,KAAK,YAAY,IAAI;AACtC,UAAM,OAAO,KAAK,KAAK,UAAU,IAAI;AACrC,QAAI,CAAC,GAAG,WAAW,GAAG,GAAG;AACvB,UAAI,KAAK,uEAAqB,IAAI,EAAE;AACpC;AAAA,IACF;AACA,QAAI;AAEF,YAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,UAAI,CAAC,GAAG,WAAW,OAAO,EAAG,IAAG,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;AACtE,UAAI,GAAG,SAAS,GAAG,EAAE,YAAY,GAAG;AAElC,YAAI,GAAG,WAAW,IAAI,EAAG,cAAa,IAAI;AAC1C,oBAAY,KAAK,IAAI;AAAA,MACvB,OAAO;AACL,yBAAiB,KAAK,IAAI;AAAA,MAC5B;AACA,UAAI,KAAK,sCAAa,IAAI,EAAE;AAAA,IAC9B,SAAS,GAAG;AACV,UAAI,KAAK,2CAAa,IAAI,KAAM,EAAY,OAAO,EAAE;AAAA,IACvD;AAAA,EACF;AACA,SAAO,EAAE,IAAI,KAAK;AACpB;AAGO,SAAS,iBAAiB,UAAkB,UAAwD;AACzG,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,GAAG,WAAW,KAAK,KAAK,UAAU,CAAC,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,EAC5D;AACA,SAAO,EAAE,IAAI,QAAQ,WAAW,GAAG,QAAQ;AAC7C;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * server-properties-i18n.ts — BDS server.properties 多语言注释字典
3
+ */
4
+ export interface ServerPropertyDoc {
5
+ /** 中文功能概述 */
6
+ desc: string;
7
+ /** 合法取值与枚举说明 */
8
+ values?: string;
9
+ /** 避坑、性能或安全性提示 */
10
+ tip?: string;
11
+ }
12
+ export declare const SERVER_PROPERTIES_DOCS_ZH_CN: Record<string, ServerPropertyDoc>;
13
+ /**
14
+ * 根据属性 key 和目标语言获取说明文档
15
+ */
16
+ export declare function getServerPropertyDoc(key: string, locale?: string): ServerPropertyDoc | undefined;
17
+ //# sourceMappingURL=server-properties-i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-properties-i18n.d.ts","sourceRoot":"","sources":["../src/server-properties-i18n.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,iBAAiB;IAChC,aAAa;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kBAAkB;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CA2L1E,CAAC;AAEF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAgB,GAAG,iBAAiB,GAAG,SAAS,CAQzG"}