@sfmc-bds/cli 0.3.2 → 0.4.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.
- package/dist/command-surface.d.ts.map +1 -1
- package/dist/command-surface.js +5 -6
- package/dist/command-surface.js.map +2 -2
- package/dist/daemon/client.d.ts +3 -0
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +38 -8
- package/dist/daemon/client.js.map +2 -2
- package/dist/daemon/protocol.d.ts +9 -3
- package/dist/daemon/protocol.d.ts.map +1 -1
- package/dist/daemon/protocol.js.map +2 -2
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +82 -2
- package/dist/daemon/server.js.map +2 -2
- package/dist/i18n/locales/en.d.ts +1 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +1 -0
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/zh-CN.js +1 -0
- package/dist/i18n/locales/zh-CN.js.map +2 -2
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +13 -1
- package/dist/logs.js.map +2 -2
- package/dist/main.js +14 -184
- package/dist/main.js.map +2 -2
- package/dist/management/config.d.ts +8 -0
- package/dist/management/config.d.ts.map +1 -0
- package/dist/management/config.js +107 -0
- package/dist/management/config.js.map +7 -0
- package/dist/management/deployment.d.ts +7 -0
- package/dist/management/deployment.d.ts.map +1 -0
- package/dist/management/deployment.js +36 -0
- package/dist/management/deployment.js.map +7 -0
- package/dist/management/domain.d.ts +65 -0
- package/dist/management/domain.d.ts.map +1 -0
- package/dist/management/domain.js +137 -0
- package/dist/management/domain.js.map +7 -0
- package/dist/management/maintenance.d.ts +18 -0
- package/dist/management/maintenance.d.ts.map +1 -0
- package/dist/management/maintenance.js +214 -0
- package/dist/management/maintenance.js.map +7 -0
- package/dist/management/platform-worker.d.ts +2 -0
- package/dist/management/platform-worker.d.ts.map +1 -0
- package/dist/management/platform-worker.js +155 -0
- package/dist/management/platform-worker.js.map +7 -0
- package/dist/management/server.d.ts +7 -0
- package/dist/management/server.d.ts.map +1 -0
- package/dist/management/server.js +291 -0
- package/dist/management/server.js.map +7 -0
- package/dist/management/startup.d.ts +6 -0
- package/dist/management/startup.d.ts.map +1 -0
- package/dist/management/startup.js +67 -0
- package/dist/management/startup.js.map +7 -0
- package/dist/management/stdio.d.ts +3 -0
- package/dist/management/stdio.d.ts.map +1 -0
- package/dist/management/stdio.js +93 -0
- package/dist/management/stdio.js.map +7 -0
- package/dist/management/tasks.d.ts +21 -0
- package/dist/management/tasks.d.ts.map +1 -0
- package/dist/management/tasks.js +103 -0
- package/dist/management/tasks.js.map +7 -0
- package/dist/module-commands.d.ts +2 -2
- package/dist/module-commands.d.ts.map +1 -1
- package/dist/module-commands.js +28 -24
- package/dist/module-commands.js.map +2 -2
- package/dist/pack-lifecycle.d.ts.map +1 -1
- package/dist/pack-lifecycle.js +2 -0
- package/dist/pack-lifecycle.js.map +2 -2
- package/dist/runtime-main.d.ts +3 -0
- package/dist/runtime-main.d.ts.map +1 -0
- package/dist/runtime-main.js +188 -0
- package/dist/runtime-main.js.map +7 -0
- package/dist/runtime.d.ts +6 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +6 -1
- package/dist/runtime.js.map +2 -2
- package/dist/services.d.ts +1 -1
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +8 -4
- package/dist/services.js.map +2 -2
- package/dist/world-packs.d.ts.map +1 -1
- package/dist/world-packs.js +7 -0
- package/dist/world-packs.js.map +2 -2
- package/package.json +8 -4
- package/scripts/module-install/.sfmc-registry-cache.json +95 -0
- package/scripts/module-install/fetch-module.mjs +5 -1
- package/scripts/module-install/lib/packages.mjs +3 -6
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { ROOT } from "../runtime.js";
|
|
6
|
+
function startupPlan() {
|
|
7
|
+
const stable = path.join(ROOT, ".sfmc", "runtime", "launcher.mjs");
|
|
8
|
+
const entry = fs.existsSync(stable) ? stable : process.env.SFMC_DAEMON_ENTRY;
|
|
9
|
+
if (!entry) throw new Error("\u65E0\u6CD5\u786E\u5B9A\u5B88\u62A4\u8FDB\u7A0B\u5165\u53E3");
|
|
10
|
+
const id = createHash("sha256").update(path.resolve(ROOT)).digest("hex").slice(0, 16);
|
|
11
|
+
let account = process.env.USERNAME ?? process.env.USER ?? "";
|
|
12
|
+
try {
|
|
13
|
+
account = os.userInfo().username;
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
const quote2 = (text) => "'" + text.replace(/'/g, "''") + "'";
|
|
18
|
+
const wrapper = path.join(ROOT, ".sfmc", "runtime", `sfmc${id}.exe`);
|
|
19
|
+
const xml = wrapper.replace(/\.exe$/, ".xml");
|
|
20
|
+
const escapeXml = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/"/g, """);
|
|
21
|
+
const body = `<service><id>sfmc${id}</id><name>SFMC ${id}</name><executable>${escapeXml(process.execPath)}</executable><startarguments>"${escapeXml(entry)}" --daemon --autostart</startarguments><stopexecutable>${escapeXml(process.execPath)}</stopexecutable><stoparguments>"${escapeXml(entry)}" daemon stop</stoparguments><workingdirectory>${escapeXml(ROOT)}</workingdirectory><env name="SFMC_ROOT" value="${escapeXml(ROOT)}"/><stoptimeout>90sec</stoptimeout><log mode="roll"/></service>`;
|
|
22
|
+
return { filename: "install-sfmc-service.ps1", script: `#Requires -RunAsAdministrator
|
|
23
|
+
$ErrorActionPreference = 'Stop'
|
|
24
|
+
$wrapper = ${quote2(wrapper)}
|
|
25
|
+
if (Get-Service -Name ${quote2(`sfmc${id}`)} -ErrorAction SilentlyContinue) { throw '\u7CFB\u7EDF\u670D\u52A1\u5DF2\u5B58\u5728\uFF0C\u4E0D\u91CD\u590D\u5B89\u88C5' }
|
|
26
|
+
Copy-Item -LiteralPath ${quote2(path.join(ROOT, ".sfmc", "runtime", "WinSW-x64.exe"))} -Destination $wrapper
|
|
27
|
+
${quote2(body)} | Set-Content -LiteralPath ${quote2(xml)} -Encoding UTF8
|
|
28
|
+
$credential = Get-Credential -UserName ${quote2(account)} -Message '\u8F93\u5165\u539F Windows \u90E8\u7F72\u8D26\u53F7\uFF1B\u5BC6\u7801\u53EA\u4EA4\u7ED9\u7CFB\u7EDF\u670D\u52A1\u7BA1\u7406\u5668'
|
|
29
|
+
& ${quote2(process.execPath)} ${quote2(entry)} daemon stop
|
|
30
|
+
if ($LASTEXITCODE -ne 0) { throw '\u539F\u5B88\u62A4\u8FDB\u7A0B\u505C\u6B62\u5931\u8D25' }
|
|
31
|
+
New-Service -Name ${quote2(`sfmc${id}`)} -BinaryPathName ('"' + $wrapper + '"') -Credential $credential -StartupType Automatic
|
|
32
|
+
Start-Service -Name ${quote2(`sfmc${id}`)}
|
|
33
|
+
` };
|
|
34
|
+
}
|
|
35
|
+
const escapeUnit = (text) => text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/%/g, "%%");
|
|
36
|
+
if (!account || /[\r\n\0]/.test(account)) throw new Error("\u65E0\u6CD5\u786E\u5B9A\u539F\u90E8\u7F72\u8D26\u53F7");
|
|
37
|
+
const unit = `[Unit]
|
|
38
|
+
Description=SFMC ${id}
|
|
39
|
+
After=network.target
|
|
40
|
+
[Service]
|
|
41
|
+
Type=simple
|
|
42
|
+
User=${account}
|
|
43
|
+
WorkingDirectory="${escapeUnit(ROOT)}"
|
|
44
|
+
Environment="SFMC_ROOT=${escapeUnit(ROOT)}"
|
|
45
|
+
ExecStart="${escapeUnit(process.execPath)}" "${escapeUnit(entry)}" --daemon --autostart
|
|
46
|
+
ExecStop="${escapeUnit(process.execPath)}" "${escapeUnit(entry)}" daemon stop
|
|
47
|
+
Restart=on-failure
|
|
48
|
+
TimeoutStopSec=90
|
|
49
|
+
[Install]
|
|
50
|
+
WantedBy=multi-user.target
|
|
51
|
+
`;
|
|
52
|
+
const quote = (text) => "'" + text.replace(/'/g, "'\\''") + "'";
|
|
53
|
+
return { filename: "install-sfmc-service.sh", script: `#!/bin/sh
|
|
54
|
+
set -eu
|
|
55
|
+
[ "$(id -u)" = 0 ] || { echo '\u8BF7\u4EE5\u7BA1\u7406\u5458\u8FD0\u884C\u6B64\u4E00\u6B21\u6027\u5B89\u88C5\u811A\u672C'; exit 1; }
|
|
56
|
+
unit=/etc/systemd/system/sfmc-${id}.service
|
|
57
|
+
[ ! -e "$unit" ] || { echo '\u7CFB\u7EDF\u670D\u52A1\u5DF2\u5B58\u5728\uFF0C\u4E0D\u91CD\u590D\u5B89\u88C5'; exit 1; }
|
|
58
|
+
${quote(process.execPath)} ${quote(entry)} daemon stop
|
|
59
|
+
printf '%s' '${Buffer.from(unit).toString("base64")}' | base64 -d > "$unit"
|
|
60
|
+
systemctl daemon-reload
|
|
61
|
+
systemctl enable --now sfmc-${id}.service
|
|
62
|
+
` };
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
startupPlan
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=startup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/management/startup.ts"],
|
|
4
|
+
"sourcesContent": ["import path from \"node:path\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport { createHash } from \"node:crypto\";\nimport { ROOT } from \"../runtime.js\";\n\n/** \u751F\u6210\u53EF\u5BA1\u9605\u7684\u7BA1\u7406\u5458\u5B89\u88C5\u5165\u53E3\uFF0C\u5BC6\u7801\u901A\u8FC7\u7CFB\u7EDF Credential API \u63D0\u4EA4\u3002 */\nexport function startupPlan() {\n const stable = path.join(ROOT, \".sfmc\", \"runtime\", \"launcher.mjs\");\n const entry = fs.existsSync(stable) ? stable : process.env.SFMC_DAEMON_ENTRY;\n if (!entry) throw new Error(\"\u65E0\u6CD5\u786E\u5B9A\u5B88\u62A4\u8FDB\u7A0B\u5165\u53E3\");\n const id = createHash(\"sha256\").update(path.resolve(ROOT)).digest(\"hex\").slice(0, 16);\n let account = process.env.USERNAME ?? process.env.USER ?? \"\";\n try { account = os.userInfo().username; } catch { /* \u6743\u9650\u53D7\u9650\u5BBF\u4E3B\u4ECD\u53EF\u751F\u6210\u7531\u7BA1\u7406\u5458\u586B\u5199\u8D26\u53F7\u7684\u5B89\u88C5\u811A\u672C */ }\n if (process.platform === \"win32\") {\n const quote = (text: string) => \"'\" + text.replace(/'/g, \"''\") + \"'\";\n const wrapper = path.join(ROOT, \".sfmc\", \"runtime\", `sfmc${id}.exe`);\n const xml = wrapper.replace(/\\.exe$/, \".xml\");\n const escapeXml = (text: string) => text.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/\"/g, \""\");\n const body = `<service><id>sfmc${id}</id><name>SFMC ${id}</name><executable>${escapeXml(process.execPath)}</executable><startarguments>"${escapeXml(entry)}" --daemon --autostart</startarguments><stopexecutable>${escapeXml(process.execPath)}</stopexecutable><stoparguments>"${escapeXml(entry)}" daemon stop</stoparguments><workingdirectory>${escapeXml(ROOT)}</workingdirectory><env name=\"SFMC_ROOT\" value=\"${escapeXml(ROOT)}\"/><stoptimeout>90sec</stoptimeout><log mode=\"roll\"/></service>`;\n return { filename: \"install-sfmc-service.ps1\", script: `#Requires -RunAsAdministrator\\n$ErrorActionPreference = 'Stop'\\n$wrapper = ${quote(wrapper)}\\nif (Get-Service -Name ${quote(`sfmc${id}`)} -ErrorAction SilentlyContinue) { throw '\u7CFB\u7EDF\u670D\u52A1\u5DF2\u5B58\u5728\uFF0C\u4E0D\u91CD\u590D\u5B89\u88C5' }\\nCopy-Item -LiteralPath ${quote(path.join(ROOT, \".sfmc\", \"runtime\", \"WinSW-x64.exe\"))} -Destination $wrapper\\n${quote(body)} | Set-Content -LiteralPath ${quote(xml)} -Encoding UTF8\\n$credential = Get-Credential -UserName ${quote(account)} -Message '\u8F93\u5165\u539F Windows \u90E8\u7F72\u8D26\u53F7\uFF1B\u5BC6\u7801\u53EA\u4EA4\u7ED9\u7CFB\u7EDF\u670D\u52A1\u7BA1\u7406\u5668'\\n& ${quote(process.execPath)} ${quote(entry)} daemon stop\\nif ($LASTEXITCODE -ne 0) { throw '\u539F\u5B88\u62A4\u8FDB\u7A0B\u505C\u6B62\u5931\u8D25' }\\nNew-Service -Name ${quote(`sfmc${id}`)} -BinaryPathName ('\"' + $wrapper + '\"') -Credential $credential -StartupType Automatic\\nStart-Service -Name ${quote(`sfmc${id}`)}\\n` };\n }\n const escapeUnit = (text: string) => text.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/%/g, \"%%\");\n if (!account || /[\\r\\n\\0]/.test(account)) throw new Error(\"\u65E0\u6CD5\u786E\u5B9A\u539F\u90E8\u7F72\u8D26\u53F7\");\n const unit = `[Unit]\\nDescription=SFMC ${id}\\nAfter=network.target\\n[Service]\\nType=simple\\nUser=${account}\\nWorkingDirectory=\"${escapeUnit(ROOT)}\"\\nEnvironment=\"SFMC_ROOT=${escapeUnit(ROOT)}\"\\nExecStart=\"${escapeUnit(process.execPath)}\" \"${escapeUnit(entry)}\" --daemon --autostart\\nExecStop=\"${escapeUnit(process.execPath)}\" \"${escapeUnit(entry)}\" daemon stop\\nRestart=on-failure\\nTimeoutStopSec=90\\n[Install]\\nWantedBy=multi-user.target\\n`;\n const quote = (text: string) => \"'\" + text.replace(/'/g, \"'\\\\''\") + \"'\";\n return { filename: \"install-sfmc-service.sh\", script: `#!/bin/sh\\nset -eu\\n[ \"$(id -u)\" = 0 ] || { echo '\u8BF7\u4EE5\u7BA1\u7406\u5458\u8FD0\u884C\u6B64\u4E00\u6B21\u6027\u5B89\u88C5\u811A\u672C'; exit 1; }\\nunit=/etc/systemd/system/sfmc-${id}.service\\n[ ! -e \"$unit\" ] || { echo '\u7CFB\u7EDF\u670D\u52A1\u5DF2\u5B58\u5728\uFF0C\u4E0D\u91CD\u590D\u5B89\u88C5'; exit 1; }\\n${quote(process.execPath)} ${quote(entry)} daemon stop\\nprintf '%s' '${Buffer.from(unit).toString(\"base64\")}' | base64 -d > \"$unit\"\\nsystemctl daemon-reload\\nsystemctl enable --now sfmc-${id}.service\\n` };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AAGd,SAAS,cAAc;AAC5B,QAAM,SAAS,KAAK,KAAK,MAAM,SAAS,WAAW,cAAc;AACjE,QAAM,QAAQ,GAAG,WAAW,MAAM,IAAI,SAAS,QAAQ,IAAI;AAC3D,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,8DAAY;AACxC,QAAM,KAAK,WAAW,QAAQ,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACpF,MAAI,UAAU,QAAQ,IAAI,YAAY,QAAQ,IAAI,QAAQ;AAC1D,MAAI;AAAE,cAAU,GAAG,SAAS,EAAE;AAAA,EAAU,QAAQ;AAAA,EAAgC;AAChF,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAMA,SAAQ,CAAC,SAAiB,MAAM,KAAK,QAAQ,MAAM,IAAI,IAAI;AACjE,UAAM,UAAU,KAAK,KAAK,MAAM,SAAS,WAAW,OAAO,EAAE,MAAM;AACnE,UAAM,MAAM,QAAQ,QAAQ,UAAU,MAAM;AAC5C,UAAM,YAAY,CAAC,SAAiB,KAAK,QAAQ,MAAM,OAAO,EAAE,QAAQ,MAAM,MAAM,EAAE,QAAQ,MAAM,QAAQ;AAC5G,UAAM,OAAO,oBAAoB,EAAE,mBAAmB,EAAE,sBAAsB,UAAU,QAAQ,QAAQ,CAAC,sCAAsC,UAAU,KAAK,CAAC,+DAA+D,UAAU,QAAQ,QAAQ,CAAC,yCAAyC,UAAU,KAAK,CAAC,uDAAuD,UAAU,IAAI,CAAC,mDAAmD,UAAU,IAAI,CAAC;AAC1b,WAAO,EAAE,UAAU,4BAA4B,QAAQ;AAAA;AAAA,aAA8EA,OAAM,OAAO,CAAC;AAAA,wBAA2BA,OAAM,OAAO,EAAE,EAAE,CAAC;AAAA,yBAAqFA,OAAM,KAAK,KAAK,MAAM,SAAS,WAAW,eAAe,CAAC,CAAC;AAAA,EAA2BA,OAAM,IAAI,CAAC,+BAA+BA,OAAM,GAAG,CAAC;AAAA,yCAA2DA,OAAM,OAAO,CAAC;AAAA,IAAgDA,OAAM,QAAQ,QAAQ,CAAC,IAAIA,OAAM,KAAK,CAAC;AAAA;AAAA,oBAAmFA,OAAM,OAAO,EAAE,EAAE,CAAC;AAAA,sBAA+GA,OAAM,OAAO,EAAE,EAAE,CAAC;AAAA,EAAK;AAAA,EAC5yB;AACA,QAAM,aAAa,CAAC,SAAiB,KAAK,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI;AACxG,MAAI,CAAC,WAAW,WAAW,KAAK,OAAO,EAAG,OAAM,IAAI,MAAM,wDAAW;AACrE,QAAM,OAAO;AAAA,mBAA4B,EAAE;AAAA;AAAA;AAAA;AAAA,OAAwD,OAAO;AAAA,oBAAuB,WAAW,IAAI,CAAC;AAAA,yBAA6B,WAAW,IAAI,CAAC;AAAA,aAAiB,WAAW,QAAQ,QAAQ,CAAC,MAAM,WAAW,KAAK,CAAC;AAAA,YAAqC,WAAW,QAAQ,QAAQ,CAAC,MAAM,WAAW,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAC1V,QAAM,QAAQ,CAAC,SAAiB,MAAM,KAAK,QAAQ,MAAM,OAAO,IAAI;AACpE,SAAO,EAAE,UAAU,2BAA2B,QAAQ;AAAA;AAAA;AAAA,gCAAgH,EAAE;AAAA;AAAA,EAAoE,MAAM,QAAQ,QAAQ,CAAC,IAAI,MAAM,KAAK,CAAC;AAAA,eAA8B,OAAO,KAAK,IAAI,EAAE,SAAS,QAAQ,CAAC;AAAA;AAAA,8BAAiF,EAAE;AAAA,EAAa;AACvb;",
|
|
6
|
+
"names": ["quote"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/management/stdio.ts"],"names":[],"mappings":"AAKA,uCAAuC;AACvC,wBAAsB,kBAAkB,kBAmEvC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { toManagementError } from "@sfmc-bds/management";
|
|
5
|
+
async function runManagementStdio() {
|
|
6
|
+
const output = process.stdout.write.bind(process.stdout);
|
|
7
|
+
for (const key of ["log", "info", "warn", "error", "debug"]) console[key] = (...args) => {
|
|
8
|
+
process.stderr.write(args.map((value) => typeof value === "string" ? value : JSON.stringify(value)).join(" ") + "\n");
|
|
9
|
+
};
|
|
10
|
+
const lines = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
11
|
+
let ready = false;
|
|
12
|
+
let dispose;
|
|
13
|
+
let forwarding;
|
|
14
|
+
let legacy = false;
|
|
15
|
+
const pending = /* @__PURE__ */ new Set();
|
|
16
|
+
const processLine = async (line) => {
|
|
17
|
+
let request = { id: "invalid", method: "invalid" };
|
|
18
|
+
try {
|
|
19
|
+
if (Buffer.byteLength(line) > 4 * 1024 * 1024) throw new Error("\u7BA1\u7406\u5E27\u8FC7\u5927");
|
|
20
|
+
request = JSON.parse(line);
|
|
21
|
+
if (!request || typeof request.id !== "string" || request.id.length > 128 || typeof request.method !== "string" || request.method.length > 128) throw new Error("\u975E\u6CD5\u7BA1\u7406\u5E27");
|
|
22
|
+
if (!ready) {
|
|
23
|
+
if (request.method !== "handshake") throw new Error("\u9996\u4E2A\u8BF7\u6C42\u5FC5\u987B\u4E3A\u63E1\u624B");
|
|
24
|
+
const params = request.params;
|
|
25
|
+
if (typeof params?.root !== "string" || !path.isAbsolute(params.root) || !fs.statSync(params.root).isDirectory()) throw new Error("\u9700\u8981\u6709\u6548\u7684\u5B9E\u4F8B\u7EDD\u5BF9\u76EE\u5F55");
|
|
26
|
+
process.env.SFMC_ROOT = path.resolve(params.root);
|
|
27
|
+
ready = true;
|
|
28
|
+
} else if (request.method === "handshake" && request.params?.root && path.resolve(request.params.root) !== process.env.SFMC_ROOT) {
|
|
29
|
+
throw new Error("\u4E00\u4E2A\u7BA1\u7406\u4F1A\u8BDD\u53EA\u80FD\u8FDE\u63A5\u4E00\u4E2A\u5B9E\u4F8B");
|
|
30
|
+
}
|
|
31
|
+
const client = await import("../daemon/client.js");
|
|
32
|
+
if (!forwarding) forwarding = client.onDaemonManagementEvent((event) => output(JSON.stringify(event) + "\n"));
|
|
33
|
+
if (!legacy) {
|
|
34
|
+
try {
|
|
35
|
+
const result = await client.callDaemon("management", { request });
|
|
36
|
+
if (result.kind !== "management") throw new Error("\u65E7\u5B88\u62A4\u8FDB\u7A0B\u6CA1\u6709\u7BA1\u7406\u5165\u53E3");
|
|
37
|
+
output(JSON.stringify(result.response) + "\n");
|
|
38
|
+
if (request.method === "events.subscribe" && !dispose) dispose = await client.ensureDaemonSubscription();
|
|
39
|
+
return;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (request.method !== "handshake" || !/unknown method|管理入口/.test(error instanceof Error ? error.message : String(error))) throw error;
|
|
42
|
+
legacy = true;
|
|
43
|
+
try {
|
|
44
|
+
process.env.SFMC_PLATFORM_VERSION = JSON.parse(fs.readFileSync(path.join(process.env.SFMC_ROOT, "node_modules", "@sfmc-bds", "sfmc", "package.json"), "utf8")).version;
|
|
45
|
+
} catch {
|
|
46
|
+
process.env.SFMC_PLATFORM_VERSION = "unknown";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const backend = await import("./server.js");
|
|
51
|
+
let value;
|
|
52
|
+
if (request.method === "handshake") {
|
|
53
|
+
value = { ...await backend.dispatchManagement(request), capabilities: ["services", "logs", "operations", "updates"], legacy: true };
|
|
54
|
+
} else if (request.method === "services.list") {
|
|
55
|
+
const result = await client.callDaemon("status");
|
|
56
|
+
if (result.kind !== "status") throw new Error("\u65E7\u90E8\u7F72\u65E0\u6CD5\u63D0\u4F9B\u670D\u52A1\u72B6\u6001");
|
|
57
|
+
value = { rows: result.rows };
|
|
58
|
+
} else if (request.method === "attachment.plan" || request.method === "attachment.apply") {
|
|
59
|
+
const status = await client.callDaemon("status");
|
|
60
|
+
if (status.kind !== "status") throw new Error("\u65E7\u90E8\u7F72\u65E0\u6CD5\u63D0\u4F9B\u670D\u52A1\u72B6\u6001");
|
|
61
|
+
const running = status.rows.filter((row) => row.running).map((row) => row.name);
|
|
62
|
+
if (request.method === "attachment.apply" && running.length) throw Object.assign(new Error("\u65E7\u5B88\u62A4\u8FDB\u7A0B\u7F3A\u5C11\u53EF\u786E\u8BA4\u7684\u4F18\u96C5\u7EF4\u62A4\u63A5\u53E3\uFF1B\u8BF7\u5148\u901A\u8FC7\u539F\u7BA1\u7406\u5668\u505C\u6B62\u670D\u52A1\uFF0C\u518D\u6267\u884C\u63A5\u5165\u5347\u7EA7"), { code: "conflict" });
|
|
63
|
+
const plan = await backend.dispatchManagement(request);
|
|
64
|
+
value = request.method === "attachment.plan" ? { ...plan, externalServices: running } : plan;
|
|
65
|
+
} else if (["events.subscribe", "logs.tail", "operations.list", "operations.get", "backups.list"].includes(request.method)) value = await backend.dispatchManagement(request);
|
|
66
|
+
else throw Object.assign(new Error("\u65E7\u90E8\u7F72\u9700\u5148\u901A\u8FC7\u63A5\u5165\u6D41\u7A0B\u5347\u7EA7\u5E73\u53F0\uFF0C\u518D\u4F7F\u7528\u5B8C\u6574\u7BA1\u7406\u529F\u80FD"), { code: "unsupported" });
|
|
67
|
+
output(JSON.stringify({ type: "res", id: request.id, ok: true, result: value }) + "\n");
|
|
68
|
+
} catch (error) {
|
|
69
|
+
output(JSON.stringify({ type: "res", id: request?.id ?? "invalid", ok: false, error: toManagementError(error) }) + "\n");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
lines.on("line", (line) => {
|
|
73
|
+
if (pending.size >= 64) {
|
|
74
|
+
process.stderr.write("\u7BA1\u7406\u8BF7\u6C42\u79EF\u538B\uFF0C\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\n");
|
|
75
|
+
lines.close();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const action = processLine(line).catch((error) => {
|
|
79
|
+
process.stderr.write(String(error) + "\n");
|
|
80
|
+
});
|
|
81
|
+
pending.add(action);
|
|
82
|
+
void action.finally(() => pending.delete(action));
|
|
83
|
+
});
|
|
84
|
+
await new Promise((resolve) => lines.once("close", resolve));
|
|
85
|
+
await Promise.allSettled(pending);
|
|
86
|
+
dispose?.();
|
|
87
|
+
forwarding?.();
|
|
88
|
+
if (ready) (await import("../daemon/client.js")).disconnectDaemonClient();
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
runManagementStdio
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/management/stdio.ts"],
|
|
4
|
+
"sourcesContent": ["import readline from \"node:readline\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { toManagementError, type ManagementRequest } from \"@sfmc-bds/management\";\n\n/** \u9996\u5E27\u786E\u5B9A\u5B9E\u4F8B\u76EE\u5F55\uFF0C\u4E4B\u540E\u624D\u52A0\u8F7D\u542B ROOT \u5355\u4F8B\u7684 CLI \u4EE3\u7801\u3002 */\nexport async function runManagementStdio() {\n const output = process.stdout.write.bind(process.stdout);\n for (const key of [\"log\", \"info\", \"warn\", \"error\", \"debug\"] as const) console[key] = (...args: unknown[]) => { process.stderr.write(args.map(value => typeof value === \"string\" ? value : JSON.stringify(value)).join(\" \") + \"\\n\"); };\n const lines = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });\n let ready = false;\n let dispose: (() => void) | undefined;\n let forwarding: (() => void) | undefined;\n let legacy = false;\n const pending = new Set<Promise<void>>();\n const processLine = async (line: string) => {\n let request: ManagementRequest = { id: \"invalid\", method: \"invalid\" };\n try {\n if (Buffer.byteLength(line) > 4 * 1024 * 1024) throw new Error(\"\u7BA1\u7406\u5E27\u8FC7\u5927\");\n request = JSON.parse(line) as ManagementRequest;\n if (!request || typeof request.id !== \"string\" || request.id.length > 128 || typeof request.method !== \"string\" || request.method.length > 128) throw new Error(\"\u975E\u6CD5\u7BA1\u7406\u5E27\");\n if (!ready) {\n if (request.method !== \"handshake\") throw new Error(\"\u9996\u4E2A\u8BF7\u6C42\u5FC5\u987B\u4E3A\u63E1\u624B\");\n const params = request.params as { root?: unknown } | undefined;\n if (typeof params?.root !== \"string\" || !path.isAbsolute(params.root) || !fs.statSync(params.root).isDirectory()) throw new Error(\"\u9700\u8981\u6709\u6548\u7684\u5B9E\u4F8B\u7EDD\u5BF9\u76EE\u5F55\");\n process.env.SFMC_ROOT = path.resolve(params.root); ready = true;\n } else if (request.method === \"handshake\" && (request.params as { root?: string } | undefined)?.root && path.resolve((request.params as { root: string }).root) !== process.env.SFMC_ROOT) {\n throw new Error(\"\u4E00\u4E2A\u7BA1\u7406\u4F1A\u8BDD\u53EA\u80FD\u8FDE\u63A5\u4E00\u4E2A\u5B9E\u4F8B\");\n }\n const client = await import(\"../daemon/client.js\");\n if (!forwarding) forwarding = client.onDaemonManagementEvent(event => output(JSON.stringify(event) + \"\\n\"));\n if (!legacy) {\n try {\n const result = await client.callDaemon(\"management\", { request });\n if (result.kind !== \"management\") throw new Error(\"\u65E7\u5B88\u62A4\u8FDB\u7A0B\u6CA1\u6709\u7BA1\u7406\u5165\u53E3\");\n output(JSON.stringify(result.response) + \"\\n\");\n if (request.method === \"events.subscribe\" && !dispose) dispose = await client.ensureDaemonSubscription();\n return;\n } catch (error) {\n if (request.method !== \"handshake\" || !/unknown method|\u7BA1\u7406\u5165\u53E3/.test(error instanceof Error ? error.message : String(error))) throw error;\n legacy = true;\n try { process.env.SFMC_PLATFORM_VERSION = (JSON.parse(fs.readFileSync(path.join(process.env.SFMC_ROOT!, \"node_modules\", \"@sfmc-bds\", \"sfmc\", \"package.json\"), \"utf8\")) as { version: string }).version; } catch { process.env.SFMC_PLATFORM_VERSION = \"unknown\"; }\n }\n }\n // \u65E7\u90E8\u7F72\u53EA\u5F00\u653E\u63A5\u5165\u89C4\u5212\u3001\u72B6\u6001\u548C\u6301\u4E45\u5316\u4EFB\u52A1\uFF1B\u5347\u7EA7\u4EA4\u7ED9\u72EC\u7ACB\u7EF4\u62A4\u8FDB\u7A0B\u3002\n const backend = await import(\"./server.js\");\n let value: unknown;\n if (request.method === \"handshake\") {\n value = { ...(await backend.dispatchManagement(request) as object), capabilities: [\"services\", \"logs\", \"operations\", \"updates\"], legacy: true };\n } else if (request.method === \"services.list\") {\n const result = await client.callDaemon(\"status\");\n if (result.kind !== \"status\") throw new Error(\"\u65E7\u90E8\u7F72\u65E0\u6CD5\u63D0\u4F9B\u670D\u52A1\u72B6\u6001\");\n value = { rows: result.rows };\n } else if (request.method === \"attachment.plan\" || request.method === \"attachment.apply\") {\n const status = await client.callDaemon(\"status\");\n if (status.kind !== \"status\") throw new Error(\"\u65E7\u90E8\u7F72\u65E0\u6CD5\u63D0\u4F9B\u670D\u52A1\u72B6\u6001\");\n const running = status.rows.filter(row => row.running).map(row => row.name);\n if (request.method === \"attachment.apply\" && running.length) throw Object.assign(new Error(\"\u65E7\u5B88\u62A4\u8FDB\u7A0B\u7F3A\u5C11\u53EF\u786E\u8BA4\u7684\u4F18\u96C5\u7EF4\u62A4\u63A5\u53E3\uFF1B\u8BF7\u5148\u901A\u8FC7\u539F\u7BA1\u7406\u5668\u505C\u6B62\u670D\u52A1\uFF0C\u518D\u6267\u884C\u63A5\u5165\u5347\u7EA7\"), { code: \"conflict\" });\n const plan = await backend.dispatchManagement(request);\n value = request.method === \"attachment.plan\" ? { ...plan as object, externalServices: running } : plan;\n } else if ([\"events.subscribe\", \"logs.tail\", \"operations.list\", \"operations.get\", \"backups.list\"].includes(request.method)) value = await backend.dispatchManagement(request);\n else throw Object.assign(new Error(\"\u65E7\u90E8\u7F72\u9700\u5148\u901A\u8FC7\u63A5\u5165\u6D41\u7A0B\u5347\u7EA7\u5E73\u53F0\uFF0C\u518D\u4F7F\u7528\u5B8C\u6574\u7BA1\u7406\u529F\u80FD\"), { code: \"unsupported\" });\n output(JSON.stringify({ type: \"res\", id: request.id, ok: true, result: value }) + \"\\n\");\n } catch (error) { output(JSON.stringify({ type: \"res\", id: request?.id ?? \"invalid\", ok: false, error: toManagementError(error) }) + \"\\n\"); }\n };\n lines.on(\"line\", line => {\n if (pending.size >= 64) { process.stderr.write(\"\u7BA1\u7406\u8BF7\u6C42\u79EF\u538B\uFF0C\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\\n\"); lines.close(); return; }\n const action = processLine(line).catch(error => { process.stderr.write(String(error) + \"\\n\"); });\n pending.add(action); void action.finally(() => pending.delete(action));\n });\n await new Promise<void>(resolve => lines.once(\"close\", resolve));\n await Promise.allSettled(pending); dispose?.(); forwarding?.();\n if (ready) (await import(\"../daemon/client.js\")).disconnectDaemonClient();\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { type OperationRecord } from "@sfmc-bds/management";
|
|
3
|
+
export declare function atomicJson(file: string, value: unknown): void;
|
|
4
|
+
export type TaskContext = {
|
|
5
|
+
phase: (name: string, message?: string) => void;
|
|
6
|
+
id: string;
|
|
7
|
+
};
|
|
8
|
+
/** 任务属于守护进程而非 SSH 会话;异常恢复只标记,不重放写入。 */
|
|
9
|
+
export declare class TaskStore extends EventEmitter {
|
|
10
|
+
private root;
|
|
11
|
+
private queue;
|
|
12
|
+
private directory;
|
|
13
|
+
constructor(root: string);
|
|
14
|
+
get(id: string): OperationRecord;
|
|
15
|
+
list(): OperationRecord[];
|
|
16
|
+
save(record: OperationRecord): void;
|
|
17
|
+
submit(kind: string, action: (context: TaskContext) => Promise<unknown>): {
|
|
18
|
+
operationId: string;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=tasks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/management/tasks.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE/E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAKtD;AACD,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F,uCAAuC;AACvC,qBAAa,SAAU,SAAQ,YAAY;IAG7B,OAAO,CAAC,IAAI;IAFxB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,SAAS,CAAS;IAC1B,YAAoB,IAAI,EAAE,MAAM,EAY/B;IACD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,CAG/B;IACD,IAAI,IAAI,eAAe,EAAE,CAIxB;IACD,IAAI,CAAC,MAAM,EAAE,eAAe,QAI3B;IACD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC;QAwB5D,WAAW;MACrB;CACF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
5
|
+
import { withMaintenanceLock } from "@sfmc-bds/management/node";
|
|
6
|
+
import { toManagementError } from "@sfmc-bds/management";
|
|
7
|
+
function atomicJson(file, value) {
|
|
8
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
9
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
10
|
+
fs.writeFileSync(temp, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
|
|
11
|
+
fs.renameSync(temp, file);
|
|
12
|
+
}
|
|
13
|
+
class TaskStore extends EventEmitter {
|
|
14
|
+
constructor(root) {
|
|
15
|
+
super();
|
|
16
|
+
this.root = root;
|
|
17
|
+
this.directory = path.join(root, ".sfmc", "operations");
|
|
18
|
+
fs.mkdirSync(this.directory, { recursive: true });
|
|
19
|
+
for (const record of this.list()) {
|
|
20
|
+
if (record.status === "queued" || record.status === "running") {
|
|
21
|
+
const pid = record.workerPid;
|
|
22
|
+
let active = false;
|
|
23
|
+
if (pid) {
|
|
24
|
+
try {
|
|
25
|
+
process.kill(pid, 0);
|
|
26
|
+
active = true;
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!active) {
|
|
31
|
+
record.status = "interrupted";
|
|
32
|
+
record.error = { code: "unavailable", message: "\u6267\u884C\u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA\uFF1B\u8BF7\u68C0\u67E5\u5907\u4EFD\u4E0E\u5B9E\u9645\u72B6\u6001\uFF0C\u4EFB\u52A1\u4E0D\u4F1A\u81EA\u52A8\u91CD\u53D1" };
|
|
33
|
+
this.save(record);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
root;
|
|
39
|
+
queue = Promise.resolve();
|
|
40
|
+
directory;
|
|
41
|
+
get(id) {
|
|
42
|
+
if (!/^[a-zA-Z0-9-]{1,80}$/.test(id)) throw new Error("\u975E\u6CD5\u4EFB\u52A1\u7F16\u53F7");
|
|
43
|
+
return JSON.parse(fs.readFileSync(path.join(this.directory, `${id}.json`), "utf8"));
|
|
44
|
+
}
|
|
45
|
+
list() {
|
|
46
|
+
return fs.readdirSync(this.directory).filter((name) => /^[a-f0-9-]{36}\.json$/.test(name)).flatMap((name) => {
|
|
47
|
+
try {
|
|
48
|
+
const record = JSON.parse(fs.readFileSync(path.join(this.directory, name), "utf8"));
|
|
49
|
+
return typeof record.createdAt === "string" && Array.isArray(record.phases) ? [record] : [];
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 200);
|
|
54
|
+
}
|
|
55
|
+
save(record) {
|
|
56
|
+
record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
atomicJson(path.join(this.directory, `${record.id}.json`), record);
|
|
58
|
+
this.emit("operation", record);
|
|
59
|
+
}
|
|
60
|
+
submit(kind, action) {
|
|
61
|
+
const record = { id: randomUUID(), kind, title: kind, status: "queued", phases: [], createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
62
|
+
this.save(record);
|
|
63
|
+
this.queue = this.queue.catch(() => {
|
|
64
|
+
}).then(async () => {
|
|
65
|
+
try {
|
|
66
|
+
await withMaintenanceLock(this.root, async () => {
|
|
67
|
+
record.status = "running";
|
|
68
|
+
this.save(record);
|
|
69
|
+
const phase = (name, message) => {
|
|
70
|
+
const previous = record.phases.at(-1);
|
|
71
|
+
if (previous?.status === "running") {
|
|
72
|
+
previous.status = "done";
|
|
73
|
+
previous.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
74
|
+
}
|
|
75
|
+
record.phases.push({ name, status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), ...message ? { message } : {} });
|
|
76
|
+
this.save(record);
|
|
77
|
+
};
|
|
78
|
+
record.result = await action({ phase, id: record.id });
|
|
79
|
+
if (record.result?.detached) return;
|
|
80
|
+
const last = record.phases.at(-1);
|
|
81
|
+
if (last) {
|
|
82
|
+
last.status = "done";
|
|
83
|
+
last.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
84
|
+
}
|
|
85
|
+
record.status = "succeeded";
|
|
86
|
+
this.save(record);
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
record.status = "failed";
|
|
90
|
+
record.error = toManagementError(error);
|
|
91
|
+
const last = record.phases.at(-1);
|
|
92
|
+
if (last) last.status = "failed";
|
|
93
|
+
this.save(record);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return { operationId: record.id };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
TaskStore,
|
|
101
|
+
atomicJson
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/management/tasks.ts"],
|
|
4
|
+
"sourcesContent": ["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { EventEmitter } from \"node:events\";\nimport { withMaintenanceLock } from \"@sfmc-bds/management/node\";\nimport { toManagementError, type OperationRecord } from \"@sfmc-bds/management\";\n\nexport function atomicJson(file: string, value: unknown) {\n fs.mkdirSync(path.dirname(file), { recursive: true });\n const temp = `${file}.${randomUUID()}.tmp`;\n fs.writeFileSync(temp, JSON.stringify(value, null, 2) + \"\\n\", { mode: 0o600 });\n fs.renameSync(temp, file);\n}\nexport type TaskContext = { phase: (name: string, message?: string) => void; id: string };\n\n/** \u4EFB\u52A1\u5C5E\u4E8E\u5B88\u62A4\u8FDB\u7A0B\u800C\u975E SSH \u4F1A\u8BDD\uFF1B\u5F02\u5E38\u6062\u590D\u53EA\u6807\u8BB0\uFF0C\u4E0D\u91CD\u653E\u5199\u5165\u3002 */\nexport class TaskStore extends EventEmitter {\n private queue: Promise<unknown> = Promise.resolve();\n private directory: string;\n constructor(private root: string) {\n super();\n this.directory = path.join(root, \".sfmc\", \"operations\");\n fs.mkdirSync(this.directory, { recursive: true });\n for (const record of this.list()) {\n if (record.status === \"queued\" || record.status === \"running\") {\n const pid = (record as OperationRecord & { workerPid?: number }).workerPid;\n let active = false;\n if (pid) { try { process.kill(pid, 0); active = true; } catch { /* \u5DF2\u9000\u51FA */ } }\n if (!active) { record.status = \"interrupted\"; record.error = { code: \"unavailable\", message: \"\u6267\u884C\u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA\uFF1B\u8BF7\u68C0\u67E5\u5907\u4EFD\u4E0E\u5B9E\u9645\u72B6\u6001\uFF0C\u4EFB\u52A1\u4E0D\u4F1A\u81EA\u52A8\u91CD\u53D1\" }; this.save(record); }\n }\n }\n }\n get(id: string): OperationRecord {\n if (!/^[a-zA-Z0-9-]{1,80}$/.test(id)) throw new Error(\"\u975E\u6CD5\u4EFB\u52A1\u7F16\u53F7\");\n return JSON.parse(fs.readFileSync(path.join(this.directory, `${id}.json`), \"utf8\")) as OperationRecord;\n }\n list(): OperationRecord[] {\n return fs.readdirSync(this.directory).filter(name => /^[a-f0-9-]{36}\\.json$/.test(name)).flatMap(name => {\n try { const record = JSON.parse(fs.readFileSync(path.join(this.directory, name), \"utf8\")) as OperationRecord; return typeof record.createdAt === \"string\" && Array.isArray(record.phases) ? [record] : []; } catch { return []; }\n }).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, 200);\n }\n save(record: OperationRecord) {\n record.updatedAt = new Date().toISOString();\n atomicJson(path.join(this.directory, `${record.id}.json`), record);\n this.emit(\"operation\", record);\n }\n submit(kind: string, action: (context: TaskContext) => Promise<unknown>) {\n const record: OperationRecord = { id: randomUUID(), kind, title: kind, status: \"queued\", phases: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() };\n this.save(record);\n this.queue = this.queue.catch(() => {}).then(async () => {\n try {\n await withMaintenanceLock(this.root, async () => {\n record.status = \"running\"; this.save(record);\n const phase = (name: string, message?: string) => {\n const previous = record.phases.at(-1);\n if (previous?.status === \"running\") { previous.status = \"done\"; previous.finishedAt = new Date().toISOString(); }\n record.phases.push({ name, status: \"running\", startedAt: new Date().toISOString(), ...(message ? { message } : {}) }); this.save(record);\n };\n record.result = await action({ phase, id: record.id });\n // \u72EC\u7ACB\u7EF4\u62A4\u8FDB\u7A0B\u63A5\u7BA1\u8BB0\u5F55\u4E4B\u540E\uFF0C\u5B88\u62A4\u8FDB\u7A0B\u4E0D\u518D\u8986\u76D6\u5176\u72B6\u6001\u3002\n if ((record.result as { detached?: boolean } | undefined)?.detached) return;\n const last = record.phases.at(-1); if (last) { last.status = \"done\"; last.finishedAt = new Date().toISOString(); }\n record.status = \"succeeded\"; this.save(record);\n });\n } catch (error) {\n record.status = \"failed\"; record.error = toManagementError(error);\n const last = record.phases.at(-1); if (last) last.status = \"failed\";\n this.save(record);\n }\n });\n return { operationId: record.id };\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
* 顶层命令别名:`module` / `mod`(由 main.ts / repl.ts 共同识别)。
|
|
6
6
|
* 子命令通道门禁见 command-surface.ts(dispatch 前由 main/repl 判定)。
|
|
7
7
|
*/
|
|
8
|
-
import { type CommandMode } from "./command-surface.js";
|
|
9
8
|
import { type CliResult } from "./cli-result.js";
|
|
9
|
+
import { type CommandMode } from "./command-surface.js";
|
|
10
10
|
/** 顶层命令名(主名 + 短别名),供帮助 / 补全 / 分发共用。 */
|
|
11
11
|
export declare const MODULE_CMD_NAMES: readonly ["module", "mod"];
|
|
12
12
|
export type ModuleCmdName = (typeof MODULE_CMD_NAMES)[number];
|
|
13
13
|
/** 判断是否为 module 顶层命令(含别名);避免 main/repl 再硬编码 case。 */
|
|
14
14
|
export declare function isModuleCommand(cmd: string | undefined): cmd is ModuleCmdName;
|
|
15
15
|
/** 开发者样式子命令(蓝标,非门禁)。 */
|
|
16
|
-
export declare const DEV_ACCENT_MODULE_SUBCOMMANDS: readonly ["build", "reload"];
|
|
16
|
+
export declare const DEV_ACCENT_MODULE_SUBCOMMANDS: readonly ["build", "reload", "submit"];
|
|
17
17
|
/** 当前通道下可见的 module 子命令(自动补全 / usage)。 */
|
|
18
18
|
export declare function getVisibleModuleSubcommands(mode: CommandMode): readonly string[];
|
|
19
19
|
/** sub 是否为开发者蓝标(help 着色)。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-commands.d.ts","sourceRoot":"","sources":["../src/module-commands.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,
|
|
1
|
+
{"version":3,"file":"module-commands.d.ts","sourceRoot":"","sources":["../src/module-commands.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAA+C,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAerG,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,YAAI,QAAQ,EAAE,KAAK,CAAU,CAAC;AAE3D,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,GAAG,IAAI,aAAa,CAE7E;AAED,wBAAwB;AACxB,eAAO,MAAM,6BAA6B,YAAI,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAEpF,yCAAyC;AACzC,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,MAAM,EAAE,CAEhF;AAED,6BAA6B;AAC7B,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEtE;AAED,qDAAqD;AACrD,wBAAgB,WAAW,CAAC,IAAI,GAAE,WAAoB,GAAG,MAAM,CAK9D;AA6FD,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCpE;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAO1D;AAKD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoErE;AAKD,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BnE;AAKD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAerE;AAuHD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAKxE;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAIzE;AAqCD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDtE;AAKD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BxE;AAuBD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAMpG;AAmDD,uDAAuD;AACvD,wBAAgB,0BAA0B,IAAI,MAAM,EAAE,CAiBrD"}
|
package/dist/module-commands.js
CHANGED
|
@@ -1,38 +1,31 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import { existsSync, lstatSync, readdirSync, statSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
1
|
import {
|
|
6
2
|
configPath,
|
|
7
3
|
modulePath,
|
|
8
4
|
readJson,
|
|
9
5
|
writeJson
|
|
10
6
|
} from "@sfmc-bds/sdk/node/config";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { existsSync, lstatSync, readdirSync, statSync } from "node:fs";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
15
11
|
import { failResult, okResult } from "./cli-result.js";
|
|
12
|
+
import { isDevAccentModuleSub, listVisibleModuleSubs } from "./command-surface.js";
|
|
16
13
|
import { t } from "./i18n/index.js";
|
|
17
|
-
import { c } from "./theme.js";
|
|
18
|
-
import { ROOT, resolveFetchModule } from "./runtime.js";
|
|
19
14
|
import { dirFingerprint, shouldSkipModuleEntry } from "./module-fingerprint.js";
|
|
20
15
|
import {
|
|
21
16
|
applyLockEnabled,
|
|
22
17
|
finalizeToggle,
|
|
23
18
|
resolveToggleTarget
|
|
24
19
|
} from "./module-toggle.js";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
resolveRegistryIndex
|
|
30
|
-
} from "./registry.js";
|
|
20
|
+
import { DEFAULT_REGISTRY_REPO, DEFAULT_REGISTRY_TAG, findUnknownModules, resolveRegistryIndex } from "./registry.js";
|
|
21
|
+
import { ROOT, resolveFetchModule } from "./runtime.js";
|
|
22
|
+
import { c } from "./theme.js";
|
|
23
|
+
import { isRetiredPlatformModule } from "@sfmc-bds/sdk/contracts";
|
|
31
24
|
const MODULE_CMD_NAMES = ["module", "mod"];
|
|
32
25
|
function isModuleCommand(cmd) {
|
|
33
26
|
return !!cmd && MODULE_CMD_NAMES.includes(cmd);
|
|
34
27
|
}
|
|
35
|
-
const DEV_ACCENT_MODULE_SUBCOMMANDS = ["build", "reload"];
|
|
28
|
+
const DEV_ACCENT_MODULE_SUBCOMMANDS = ["build", "reload", "submit"];
|
|
36
29
|
function getVisibleModuleSubcommands(mode) {
|
|
37
30
|
return listVisibleModuleSubs(mode);
|
|
38
31
|
}
|
|
@@ -210,9 +203,7 @@ ${query}`)];
|
|
|
210
203
|
lines.push(` source : github:${entry.repo}@${entry.tag}`);
|
|
211
204
|
lines.push(` github : https://github.com/${entry.repo}/tree/${entry.tag}`);
|
|
212
205
|
}
|
|
213
|
-
lines.push(
|
|
214
|
-
` local : ${isInstalled ? c.green(`installed @ ${installedPath}`) : c.dim("not installed")}`
|
|
215
|
-
);
|
|
206
|
+
lines.push(` local : ${isInstalled ? c.green(`installed @ ${installedPath}`) : c.dim("not installed")}`);
|
|
216
207
|
if (!isInstalled) {
|
|
217
208
|
lines.push(c.dim(`
|
|
218
209
|
install: sfmc mod install ${query}`));
|
|
@@ -238,7 +229,8 @@ ${id}`)];
|
|
|
238
229
|
lines.push(` schemaVer : ${m.manifest.schemaVersion ?? "(none)"}`);
|
|
239
230
|
lines.push(` routes : ${m.manifest.routes?.length ?? 0}`);
|
|
240
231
|
if (m.manifest.routes?.length) {
|
|
241
|
-
for (const r of m.manifest.routes)
|
|
232
|
+
for (const r of m.manifest.routes)
|
|
233
|
+
lines.push(c.dim(` ${r.method.padEnd(7)} ${r.path.padEnd(36)} ${r.handler}`));
|
|
242
234
|
}
|
|
243
235
|
lines.push(` migrations : ${m.manifest.migrations?.length ?? 0}`);
|
|
244
236
|
if (m.manifest.migrations?.length) {
|
|
@@ -342,6 +334,8 @@ async function notifyDbToggle(logicalId, action) {
|
|
|
342
334
|
async function cmdModuleToggle(query, action) {
|
|
343
335
|
const installed = await scanInstalled();
|
|
344
336
|
const target = resolveToggleTarget(query, buildToggleCandidates(installed));
|
|
337
|
+
if (action === "enable" && target && isRetiredPlatformModule(target.logicalId)) return failResult("monitor \u5DF2\u6536\u7F16\u81F3\u5E73\u53F0\uFF0C\u65E0\u9700\u542F\u7528\uFF1B\u8BF7\u66F4\u65B0\u5E73\u53F0\u884C\u4E3A\u5305\u3002");
|
|
338
|
+
if (action === "enable" && target && isRetiredPlatformModule(target.logicalId)) return failResult("monitor \u5DF2\u6536\u7F16\u81F3\u5E73\u53F0\uFF0C\u65E0\u9700\u542F\u7528\uFF1B\u8BF7\u66F4\u65B0\u5E73\u53F0\u884C\u4E3A\u5305\u3002");
|
|
345
339
|
if (!target) {
|
|
346
340
|
return failResult(c.red(t("mod.notInstalled", { id: query })));
|
|
347
341
|
}
|
|
@@ -352,9 +346,7 @@ async function cmdModuleToggle(query, action) {
|
|
|
352
346
|
try {
|
|
353
347
|
writeLocalModuleEnabled(target.logicalId, enabled);
|
|
354
348
|
} catch (err) {
|
|
355
|
-
return failResult(
|
|
356
|
-
c.red(t("mod.toggleLockFailed", { id: target.logicalId, message: err.message }))
|
|
357
|
-
);
|
|
349
|
+
return failResult(c.red(t("mod.toggleLockFailed", { id: target.logicalId, message: err.message })));
|
|
358
350
|
}
|
|
359
351
|
const notify = await notifyDbToggle(target.logicalId, action);
|
|
360
352
|
const fin = finalizeToggle(true, notify);
|
|
@@ -370,6 +362,7 @@ ${warn}`);
|
|
|
370
362
|
}
|
|
371
363
|
async function cmdModuleEnable(args) {
|
|
372
364
|
const id = args[0];
|
|
365
|
+
if (id && isRetiredPlatformModule(id)) return failResult("monitor \u5DF2\u6536\u7F16\u81F3\u5E73\u53F0\uFF0C\u65E0\u9700\u5B89\u88C5\u6216\u542F\u7528\uFF1B\u8BF7\u66F4\u65B0\u5E73\u53F0\u884C\u4E3A\u5305\u3002");
|
|
373
366
|
if (!id) return failResult(c.yellow(t("mod.enable.usage")));
|
|
374
367
|
return cmdModuleToggle(id, "enable");
|
|
375
368
|
}
|
|
@@ -476,11 +469,22 @@ function parseFlags(args) {
|
|
|
476
469
|
return flags;
|
|
477
470
|
}
|
|
478
471
|
async function dispatchModuleCommand(sub, args) {
|
|
472
|
+
if (sub && ["install", "uninstall", "remove", "enable", "disable", "update", "pin", "build", "reload"].includes(sub) && !args.includes("--check")) {
|
|
473
|
+
const { withMaintenanceLock } = await import("@sfmc-bds/management/node");
|
|
474
|
+
return withMaintenanceLock(ROOT, () => dispatchModuleCommandUnlocked(sub, args));
|
|
475
|
+
}
|
|
476
|
+
return dispatchModuleCommandUnlocked(sub, args);
|
|
477
|
+
}
|
|
478
|
+
async function dispatchModuleCommandUnlocked(sub, args) {
|
|
479
479
|
switch (sub) {
|
|
480
480
|
case "list":
|
|
481
481
|
return cmdModuleList(args);
|
|
482
482
|
case "search":
|
|
483
483
|
return cmdModuleSearch(args);
|
|
484
|
+
case "submit": {
|
|
485
|
+
const { runRegistrySubmitCommand } = await import("@sfmc-bds/devkit");
|
|
486
|
+
return runRegistrySubmitCommand(args);
|
|
487
|
+
}
|
|
484
488
|
case "install":
|
|
485
489
|
return cmdModuleInstall(args);
|
|
486
490
|
case "uninstall":
|