diff --git a/.gitignore b/.gitignore index 09c4f200..54d2e74a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ tools/.sfmc-registry-cache.json *.tmp *.tmp.* *.log +*.tgz # Proactive-agent credentials (security-audit.sh) .credentials/ diff --git a/package-lock.json b/package-lock.json index 8c0a5113..ed704950 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3171,7 +3171,7 @@ "sfmc-meta": { "name": "@sfmc-bds/sfmc", "version": "0.1.0", - "license": "ISC", + "license": "GPL-3.0-only", "dependencies": { "@sfmc-bds/bds-tools": "^0.1.0", "@sfmc-bds/cli": "^0.1.0", diff --git a/sfmc-bds-sfmc-0.1.0.tgz b/sfmc-bds-sfmc-0.1.0.tgz deleted file mode 100644 index 6534ac03..00000000 Binary files a/sfmc-bds-sfmc-0.1.0.tgz and /dev/null differ diff --git a/sfmc/src/main.ts b/sfmc/src/main.ts index 492a844c..1fd7fab9 100644 --- a/sfmc/src/main.ts +++ b/sfmc/src/main.ts @@ -3,16 +3,17 @@ import process from "node:process"; import pkg from "../package.json" with { type: "json" }; import { cmdLogs, cmdRestart, cmdStart, cmdStartAll, cmdStatus, cmdStop, cmdStopAll, cmdUpdate } from "./commands.js"; import { HELP, startRepl } from "./repl.js"; -import { dispatchModuleCommand, scanAndWarnUnknown } from "./module-commands.js"; +import { dispatchModuleCommand, isModuleCommand, scanAndWarnUnknown } from "./module-commands.js"; import { cmdBehaviorPackBuild, cmdBehaviorPackDeploy } from "./commands-behavior-pack.js"; import { disableRemoteAgent, enrollRemoteAgent, remoteStatus, startRemoteAgent, stopRemoteAgent } from "./remote-agent.js"; import { c } from "./theme.js"; function printVersion(): void { - `${c.text(`⠪⡁⡯⠁`)} + /* 此前模板字符串未写入 stdout,导致 `sfmc --version` 无输出。 */ + console.log(`${c.text(`⠪⡁⡯⠁`)} ${c.text(`⠒⠁⠃`)}${c.purple(`⠄`)} ${c.text(`⡷⡇⡎⠁`)} ${c.text(`S`)}${c.dim(`cripts`)} ${c.text(`F`)}${c.dim(`or`)} ${c.text(`M`)}${c.dim(`ine`)}${c.text(`c`)}${c.dim(`raft Server`)} v${pkg.version} - ${c.text(`⠃⠃⠑⠂`)} ${c.dim(`https://github.com/DogeLakeDev/ScriptsForMinecraftServer`)}\n`; + ${c.text(`⠃⠃⠑⠂`)} ${c.dim(`https://github.com/DogeLakeDev/ScriptsForMinecraftServer`)}`); } function printUsage(): void { @@ -138,13 +139,13 @@ async function main(): Promise { } break; } - case "module": - case "mod": { - const [sub, ...subRest] = rest; - console.log(await dispatchModuleCommand(sub, subRest)); - break; - } default: + /* module/mod 别名只维护 MODULE_CMD_NAMES,避免 main/repl case 链漂移(OCP)。 */ + if (isModuleCommand(cmd)) { + const [sub, ...subRest] = rest; + console.log(await dispatchModuleCommand(sub, subRest)); + break; + } console.log(c.red(`Unknown command: ${cmd}`)); printUsage(); process.exit(1); diff --git a/sfmc/src/module-commands.ts b/sfmc/src/module-commands.ts index 75c1118d..3f3c0954 100644 --- a/sfmc/src/module-commands.ts +++ b/sfmc/src/module-commands.ts @@ -28,6 +28,14 @@ /** 顶层命令名(主名 + 短别名),供 HELP / 补全 / 分发共用。 */ export const MODULE_CMD_NAMES = ["module", "mod"] as const; +/** HELP 行首标签,如 `module/mod`(与 MODULE_CMD_NAMES 同源,避免两处漂移)。 */ +export const MODULE_CMD_LABEL = MODULE_CMD_NAMES.join("/"); + +/** 是否为 module 顶层命令(含短别名)。新增别名只改 MODULE_CMD_NAMES。 */ +export function isModuleCommand(name: string | undefined): boolean { + return !!name && (MODULE_CMD_NAMES as readonly string[]).includes(name); +} + /** 对外展示与 Tab 补全用的子命令列表(不含 remove 等同义别名)。 */ export const MODULE_SUBCOMMANDS = [ "list", @@ -41,7 +49,7 @@ export const MODULE_SUBCOMMANDS = [ ] as const; export const MODULE_USAGE = - "Usage: sfmc module|mod [args]"; + `Usage: sfmc ${MODULE_CMD_NAMES.join("|")} <${MODULE_SUBCOMMANDS.join("|")}> [args]`; import fs from "node:fs/promises"; import { existsSync, readdirSync } from "node:fs"; diff --git a/sfmc/src/repl.ts b/sfmc/src/repl.ts index 41dab0b2..34685c12 100644 --- a/sfmc/src/repl.ts +++ b/sfmc/src/repl.ts @@ -2,7 +2,13 @@ import process, { stdin, stdout } from "node:process"; import pkg from "../package.json" with { type: "json" }; import { cmdLogs, cmdRestart, cmdSend, cmdStart, cmdStartAll, cmdStatus, cmdStop, cmdStopAll, cmdUpdate } from "./commands.js"; import { formatLog, getAllLogs, onLog, wrapLogLine, type LogLevel, type LogSource, type UnifiedLog } from "./logs.js"; -import { dispatchModuleCommand, listInstalledModuleIdsSync, MODULE_CMD_NAMES, MODULE_SUBCOMMANDS } from "./module-commands.js"; +import { + dispatchModuleCommand, + isModuleCommand, + listInstalledModuleIdsSync, + MODULE_CMD_NAMES, + MODULE_SUBCOMMANDS, +} from "./module-commands.js"; import { listRegistryModuleIdsSync } from "./registry.js"; import { disableRemoteAgent, enrollRemoteAgent, remoteStatus, startRemoteAgent } from "./remote-agent.js"; import { forceStopAll, SERVICE_NAMES, stopAll } from "./services.js"; @@ -14,6 +20,9 @@ function setRaw(v: boolean): void { } catch {} } +/** HELP 行首:把 MODULE_CMD_NAMES 着色后用 / 拼接(如 module/mod)。 */ +const MODULE_HELP_LABEL = MODULE_CMD_NAMES.map((n) => c.green(n)).join("/"); + const welcome = `\n ${c.text(`⠪⡁⡯⠁`)} ${c.text(`⠒⠁⠃`)}${c.purple(`⠄`)} @@ -43,19 +52,19 @@ ${c.bold("Commands")} ${c.green("remote enroll")} [name] Enroll this supervisor with a controller ${c.green("remote disable")} Disable + disconnect remote agent - ${c.green("module")}/${c.green("mod")} list + ${MODULE_HELP_LABEL} list List installed modules - ${c.green("module")}/${c.green("mod")} search [id] + ${MODULE_HELP_LABEL} search [id] Fetch registry list / show one module's registry info - ${c.green("module")}/${c.green("mod")} install [--from ] + ${MODULE_HELP_LABEL} install [--from ] Fetch + install a module - ${c.green("module")}/${c.green("mod")} uninstall + ${MODULE_HELP_LABEL} uninstall Remove an installed module - ${c.green("module")}/${c.green("mod")} verify [id] + ${MODULE_HELP_LABEL} verify [id] Verify installed modules (SHA-256) - ${c.green("module")}/${c.green("mod")} info + ${MODULE_HELP_LABEL} info Show one installed module's details - ${c.green("module")}/${c.green("mod")} enable|disable + ${MODULE_HELP_LABEL} enable|disable Toggle module (needs db-server) ${c.green("version")} Show version ${c.green("help")} Show this @@ -148,8 +157,9 @@ function getCompletions(parsed: ParsedLine): string[] { case "remote": if (argIndex === 0) return ["status", "enroll", "disable"].filter(sw); return []; - case "module": - case "mod": { + default: { + /* module/mod 等别名统一走 MODULE_CMD_NAMES,避免 case 链与权威源漂移。 */ + if (!isModuleCommand(cmd)) return []; if (argIndex === 0) return [...MODULE_SUBCOMMANDS].filter(sw); const verb = words[0] ?? ""; /* search:补全 registry 缓存中的 id;其余本地已装 id */ @@ -167,8 +177,6 @@ function getCompletions(parsed: ParsedLine): string[] { } return []; } - default: - return []; } } @@ -659,17 +667,16 @@ async function execCmd(parts: string[]): Promise { } break; } - case "module": - case "mod": { - const [sub, ...subRest] = args; - stdout.write((await dispatchModuleCommand(sub, subRest)) + "\n"); - break; - } case "quit": case "exit": case "q": throw "QUIT"; default: + if (isModuleCommand(cmd)) { + const [sub, ...subRest] = args; + stdout.write((await dispatchModuleCommand(sub, subRest)) + "\n"); + break; + } stdout.write(c.yellow(`Unknown: ${cmd} (try: help)\n`)); } } diff --git a/sfmc/src/runtime.ts b/sfmc/src/runtime.ts index aa774bfe..09c175ae 100644 --- a/sfmc/src/runtime.ts +++ b/sfmc/src/runtime.ts @@ -164,6 +164,16 @@ function nodeBinary(): string { return IS_SEA ? "node" : process.execPath; } +function serviceChildEnv(service: ServiceId, optsEnv?: NodeJS.ProcessEnv | null) { + return { + ...process.env, + ...optsEnv, + SFMC_SERVICE: service, + SFMC_ROOT: ROOT, + SFMC_PACKAGES_DIR: PACKAGES_DIR, + }; +} + /** * 启动一个子服务。 * @@ -171,26 +181,14 @@ function nodeBinary(): string { * - SFMC_SERVICE / SFMC_ROOT / SFMC_PACKAGES_DIR */ export function spawnService(service: ServiceId, args: string[] = [], opts: SpawnOptions = {}) { - const env = { - ...process.env, - ...opts.env, - SFMC_SERVICE: service, - SFMC_ROOT: ROOT, - SFMC_PACKAGES_DIR: PACKAGES_DIR, - }; + const env = serviceChildEnv(service, opts.env as NodeJS.ProcessEnv | undefined); const script = resolveServiceScript(service); return spawn(nodeBinary(), [script, ...args], { ...opts, env }); } /** spawnService 的同步版本 */ export function spawnServiceSync(service: ServiceId, args: string[] = [], opts: SpawnSyncOptions = {}) { - const env = { - ...process.env, - ...opts.env, - SFMC_SERVICE: service, - SFMC_ROOT: ROOT, - SFMC_PACKAGES_DIR: PACKAGES_DIR, - }; + const env = serviceChildEnv(service, opts.env as NodeJS.ProcessEnv | undefined); const script = resolveServiceScript(service); return spawnSync(nodeBinary(), [script, ...args], { ...opts, env }); } diff --git a/tools/check-ootb.mjs b/tools/check-ootb.mjs index a04ad526..6dce578c 100755 --- a/tools/check-ootb.mjs +++ b/tools/check-ootb.mjs @@ -157,7 +157,10 @@ async function main() { console.log("[ootb] WARN: sfmc/dist/main.js 缺失 — 跳过 CLI 检查"); } else { const r = runSync(process.execPath, [SFMC_DIST, "--help"], { cwd: ROOT }); - const hasModule = /module\s+(list|install)/.test(r.stdout + r.stderr); + /* HELP 现为 "module/mod list"(别名同行);兼容旧 "module list" / 裸 "mod list"。 */ + const helpText = String(r.stdout || "") + String(r.stderr || ""); + const hasModule = + /\bmodule(\/\S+)?\s+(list|install)\b/.test(helpText) || /\bmod\s+(list|install)\b/.test(helpText); if (r.status === 0 && hasModule) pass("sfmc CLI module 子命令已注册"); else fail("sfmc CLI module 子命令已注册", (r.stderr || r.stdout || `exit ${r.status}`).trim()); }