@sfmc-bds/cli 0.1.0 → 0.2.0-beta.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/README.md +7 -1
- package/dist/cli-result.d.ts +12 -0
- package/dist/cli-result.d.ts.map +1 -0
- package/dist/cli-result.js +15 -0
- package/dist/cli-result.js.map +7 -0
- package/dist/commands-behavior-pack.d.ts +11 -42
- package/dist/commands-behavior-pack.d.ts.map +1 -1
- package/dist/commands-behavior-pack.js +40 -159
- package/dist/commands-behavior-pack.js.map +7 -1
- package/dist/commands-reload.d.ts +9 -0
- package/dist/commands-reload.d.ts.map +1 -0
- package/dist/commands-reload.js +30 -0
- package/dist/commands-reload.js.map +7 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +182 -172
- package/dist/commands.js.map +7 -1
- package/dist/i18n/index.d.ts +26 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +107 -0
- package/dist/i18n/index.js.map +7 -0
- package/dist/i18n/locales/en.d.ts +381 -0
- package/dist/i18n/locales/en.d.ts.map +1 -0
- package/dist/i18n/locales/en.js +393 -0
- package/dist/i18n/locales/en.js.map +7 -0
- package/dist/i18n/locales/zh-CN.d.ts +4 -0
- package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
- package/dist/i18n/locales/zh-CN.js +379 -0
- package/dist/i18n/locales/zh-CN.js.map +7 -0
- package/dist/i18n/types.d.ts +7 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +17 -0
- package/dist/i18n/types.js.map +7 -0
- package/dist/index.d.ts +5 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -43
- package/dist/index.js.map +7 -1
- package/dist/interactive-prompts.d.ts +8 -0
- package/dist/interactive-prompts.d.ts.map +1 -0
- package/dist/interactive-prompts.js +57 -0
- package/dist/interactive-prompts.js.map +7 -0
- package/dist/locale-command.d.ts +3 -0
- package/dist/locale-command.d.ts.map +1 -0
- package/dist/locale-command.js +19 -0
- package/dist/locale-command.js.map +7 -0
- package/dist/logs.d.ts +22 -4
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +208 -193
- package/dist/logs.js.map +7 -1
- package/dist/main.js +135 -173
- package/dist/main.js.map +7 -1
- package/dist/module-commands.d.ts +43 -4
- package/dist/module-commands.d.ts.map +1 -1
- package/dist/module-commands.js +440 -327
- package/dist/module-commands.js.map +7 -1
- package/dist/module-fingerprint.d.ts +7 -0
- package/dist/module-fingerprint.d.ts.map +1 -0
- package/dist/module-fingerprint.js +31 -0
- package/dist/module-fingerprint.js.map +7 -0
- package/dist/module-wizard.d.ts +14 -0
- package/dist/module-wizard.d.ts.map +1 -0
- package/dist/module-wizard.js +313 -0
- package/dist/module-wizard.js.map +7 -0
- package/dist/pack-lifecycle.d.ts +95 -0
- package/dist/pack-lifecycle.d.ts.map +1 -0
- package/dist/pack-lifecycle.js +634 -0
- package/dist/pack-lifecycle.js.map +7 -0
- package/dist/pack-update/bindings.d.ts +12 -0
- package/dist/pack-update/bindings.d.ts.map +1 -0
- package/dist/pack-update/bindings.js +64 -0
- package/dist/pack-update/bindings.js.map +7 -0
- package/dist/pack-update/config.d.ts +22 -0
- package/dist/pack-update/config.d.ts.map +1 -0
- package/dist/pack-update/config.js +141 -0
- package/dist/pack-update/config.js.map +7 -0
- package/dist/pack-update/providers/curseforge.d.ts +23 -0
- package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
- package/dist/pack-update/providers/curseforge.js +209 -0
- package/dist/pack-update/providers/curseforge.js.map +7 -0
- package/dist/pack-update/providers/index.d.ts +16 -0
- package/dist/pack-update/providers/index.d.ts.map +1 -0
- package/dist/pack-update/providers/index.js +37 -0
- package/dist/pack-update/providers/index.js.map +7 -0
- package/dist/pack-update/service.d.ts +21 -0
- package/dist/pack-update/service.d.ts.map +1 -0
- package/dist/pack-update/service.js +567 -0
- package/dist/pack-update/service.js.map +7 -0
- package/dist/pack-update/types.d.ts +126 -0
- package/dist/pack-update/types.d.ts.map +1 -0
- package/dist/pack-update/types.js +8 -0
- package/dist/pack-update/types.js.map +7 -0
- package/dist/pack-update/version-policy.d.ts +80 -0
- package/dist/pack-update/version-policy.d.ts.map +1 -0
- package/dist/pack-update/version-policy.js +216 -0
- package/dist/pack-update/version-policy.js.map +7 -0
- package/dist/registry.d.ts +12 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +73 -106
- package/dist/registry.js.map +7 -1
- package/dist/remote-agent.d.ts.map +1 -1
- package/dist/remote-agent.js +160 -176
- package/dist/remote-agent.js.map +7 -1
- package/dist/repl.d.ts +4 -0
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +683 -605
- package/dist/repl.js.map +7 -1
- package/dist/runtime.d.ts +40 -14
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +185 -62
- package/dist/runtime.js.map +7 -1
- package/dist/services.d.ts +2 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +251 -238
- package/dist/services.js.map +7 -1
- package/dist/sfmc-modules-root.d.ts +24 -0
- package/dist/sfmc-modules-root.d.ts.map +1 -0
- package/dist/sfmc-modules-root.js +70 -0
- package/dist/sfmc-modules-root.js.map +7 -0
- package/dist/theme.d.ts +4 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +134 -48
- package/dist/theme.js.map +7 -1
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +363 -438
- package/dist/wizard.js.map +7 -1
- package/dist/world-packs.d.ts +22 -0
- package/dist/world-packs.d.ts.map +1 -0
- package/dist/world-packs.js +872 -0
- package/dist/world-packs.js.map +7 -0
- package/package.json +10 -10
- package/dist/dispatcher.d.ts +0 -13
- package/dist/dispatcher.d.ts.map +0 -1
- package/dist/dispatcher.js +0 -23
- package/dist/dispatcher.js.map +0 -1
- package/dist/terminal.d.ts +0 -3
- package/dist/terminal.d.ts.map +0 -1
- package/dist/terminal.js +0 -178
- package/dist/terminal.js.map +0 -1
package/dist/wizard.js
CHANGED
|
@@ -1,475 +1,400 @@
|
|
|
1
1
|
import { confirm, intro, isCancel, multiselect, note, outro, select, tasks, text } from "@clack/prompts";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
configPath,
|
|
4
|
+
ensureCoreConfigs,
|
|
5
|
+
modulePath,
|
|
6
|
+
patchJson as patchConfig,
|
|
7
|
+
readJson,
|
|
8
|
+
withConfigSchema,
|
|
9
|
+
writeJson
|
|
10
|
+
} from "@sfmc-bds/sdk/node/config";
|
|
4
11
|
import { execFileSync } from "node:child_process";
|
|
5
12
|
import fs from "node:fs";
|
|
6
13
|
import path from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
14
|
+
import { ensureDirectory, pickDirectory } from "./interactive-prompts.js";
|
|
15
|
+
import { persistLocale, t } from "./i18n/index.js";
|
|
16
|
+
import { ROOT, isMonorepoLayout, isRuntimeInitialized, resolveFetchModule, spawnService } from "./runtime.js";
|
|
17
|
+
import { ensurePackUpdateConfigFile } from "./pack-update/config.js";
|
|
9
18
|
import { c } from "./theme.js";
|
|
10
|
-
/** 仓顶服务 config 浅合并写盘。委托 SDK 统一实现,禁止自己 mkdir+writeFileSync。 */
|
|
11
19
|
function patchJson(rootDir, name, updates) {
|
|
12
|
-
|
|
20
|
+
patchConfig(configPath(rootDir, name), updates);
|
|
13
21
|
}
|
|
14
|
-
function
|
|
22
|
+
async function waitForHealth(port, ms = 15e3) {
|
|
23
|
+
const t2 = Date.now();
|
|
24
|
+
while (Date.now() - t2 < ms) {
|
|
15
25
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
async function pickDirectory(message, defaultDirectory) {
|
|
24
|
-
const method = await select({
|
|
25
|
-
message,
|
|
26
|
-
options: [
|
|
27
|
-
{ value: "text", label: "Enter path", hint: defaultDirectory },
|
|
28
|
-
{ value: "browse", label: "Browse...", hint: "open system folder picker" },
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
if (isCancel(method))
|
|
32
|
-
return defaultDirectory;
|
|
33
|
-
if (method === "browse")
|
|
34
|
-
return pickDirectoryDialog(message, defaultDirectory) ?? defaultDirectory;
|
|
35
|
-
const selected = await text({ message, initialValue: defaultDirectory });
|
|
36
|
-
return isCancel(selected) || !selected ? defaultDirectory : selected;
|
|
37
|
-
}
|
|
38
|
-
function pickDirectoryDialog(title, defaultDirectory) {
|
|
39
|
-
const escape = (value) => value.replace(/'/g, "''");
|
|
40
|
-
const script = [
|
|
41
|
-
"Add-Type -AssemblyName System.Windows.Forms",
|
|
42
|
-
"$dialog = New-Object System.Windows.Forms.FolderBrowserDialog",
|
|
43
|
-
`$dialog.Description = '${escape(title)}'`,
|
|
44
|
-
`$dialog.SelectedPath = '${escape(defaultDirectory)}'`,
|
|
45
|
-
"if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dialog.SelectedPath }",
|
|
46
|
-
].join("; ");
|
|
47
|
-
try {
|
|
48
|
-
const output = execFileSync("powershell.exe", ["-NoProfile", "-STA", "-Command", script], {
|
|
49
|
-
encoding: "utf-8",
|
|
50
|
-
timeout: 30_000,
|
|
51
|
-
windowsHide: true,
|
|
52
|
-
});
|
|
53
|
-
return output.trim() || null;
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return null;
|
|
26
|
+
if ((await fetch(`http://127.0.0.1:${port}/api/health`)).ok) return true;
|
|
27
|
+
} catch {
|
|
57
28
|
}
|
|
29
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
58
32
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
if ((await fetch(`http://127.0.0.1:${port}/api/health`)).ok)
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
/* retry */
|
|
68
|
-
}
|
|
69
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
async function extractZip(data, destDir, options = { overwrite: true }) {
|
|
74
|
-
const zip = await JSZip.loadAsync(data);
|
|
75
|
-
const files = [];
|
|
76
|
-
const safeJoin = (base, relative) => {
|
|
77
|
-
const fullPath = path.resolve(base, relative);
|
|
78
|
-
const relativePath = path.relative(base, fullPath);
|
|
79
|
-
if (relativePath === "" || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
80
|
-
throw new Error(`Path traversal detected: ${relative}`);
|
|
81
|
-
}
|
|
82
|
-
return fullPath;
|
|
83
|
-
};
|
|
84
|
-
const concurrency = 10;
|
|
85
|
-
const entries = Object.keys(zip.files);
|
|
86
|
-
const chunks = [];
|
|
87
|
-
for (let i = 0; i < entries.length; i += concurrency) {
|
|
88
|
-
chunks.push(entries.slice(i, i + concurrency));
|
|
89
|
-
}
|
|
90
|
-
for (const chunk of chunks) {
|
|
91
|
-
await Promise.all(chunk.map(async (relPath) => {
|
|
92
|
-
if (relPath.endsWith("/"))
|
|
93
|
-
return;
|
|
94
|
-
const file = zip.file(relPath);
|
|
95
|
-
if (!file)
|
|
96
|
-
return;
|
|
97
|
-
const targetPath = safeJoin(destDir, relPath);
|
|
98
|
-
if (!options.overwrite && fs.existsSync(targetPath)) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
102
|
-
const content = await file.async("nodebuffer");
|
|
103
|
-
await fs.promises.writeFile(targetPath, content);
|
|
104
|
-
files.push(relPath);
|
|
105
|
-
}));
|
|
106
|
-
}
|
|
107
|
-
return files;
|
|
108
|
-
}
|
|
109
|
-
/** SEA asset: { zipBaseName, targetDirRelative, SEA_asset_name } */
|
|
110
|
-
const ASSETS = [
|
|
111
|
-
{ target: "configs", npmTarget: "configs", asset: "configs_default" },
|
|
112
|
-
{ target: "modules", npmTarget: "modules", asset: "modules" },
|
|
33
|
+
const RUNTIME_DIRS = [
|
|
34
|
+
{ target: "configs", npmTarget: "configs" },
|
|
35
|
+
{ target: "modules", npmTarget: "modules" }
|
|
113
36
|
];
|
|
114
37
|
function missingRuntimeAssets(rootDir) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
38
|
+
return RUNTIME_DIRS.flatMap(({ target, npmTarget }) => {
|
|
39
|
+
const directory = path.join(rootDir, npmTarget);
|
|
40
|
+
try {
|
|
41
|
+
return fs.statSync(directory).isDirectory() && fs.readdirSync(directory).length > 0 ? [] : [target];
|
|
42
|
+
} catch {
|
|
43
|
+
return [target];
|
|
44
|
+
}
|
|
45
|
+
});
|
|
124
46
|
}
|
|
125
47
|
async function runBdsUpdate(rootDir, channel) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
48
|
+
const child = spawnService("update", [`--channel=${channel}`, "--force", "--no-start"], {
|
|
49
|
+
cwd: rootDir,
|
|
50
|
+
stdio: "pipe"
|
|
51
|
+
});
|
|
52
|
+
const output = [];
|
|
53
|
+
child.stdout?.on("data", (data) => output.push(data.toString()));
|
|
54
|
+
child.stderr?.on("data", (data) => output.push(data.toString()));
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
child.once("error", (error) => resolve({ code: null, output: `${output.join("")}${error.message}` }));
|
|
57
|
+
child.once("close", (code) => resolve({ code, output: output.join("") }));
|
|
58
|
+
});
|
|
137
59
|
}
|
|
138
60
|
async function prepareRuntimeAssets(rootDir) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
return;
|
|
61
|
+
if (isMonorepoLayout(rootDir)) {
|
|
62
|
+
const missing = missingRuntimeAssets(rootDir);
|
|
63
|
+
if (missing.length > 0) {
|
|
64
|
+
throw new Error(t("wizard.npmMissing", { list: missing.join(", ") }));
|
|
145
65
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
task: async (message) => {
|
|
150
|
-
for (const { target, asset } of ASSETS) {
|
|
151
|
-
message(`Extracting ${target}`);
|
|
152
|
-
const assetBuffer = getAsset(asset);
|
|
153
|
-
if (!assetBuffer)
|
|
154
|
-
throw new Error(`Bundled asset not found: ${asset}`);
|
|
155
|
-
const destDir = path.join(rootDir, target);
|
|
156
|
-
if (!ensureDirectory(destDir))
|
|
157
|
-
throw new Error(`Cannot create asset directory: ${destDir}`);
|
|
158
|
-
await extractZip(Buffer.from(assetBuffer), destDir, { overwrite: true });
|
|
159
|
-
}
|
|
160
|
-
return "Assets extracted";
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
]);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
seedNpmRuntimeLayout(rootDir);
|
|
164
69
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
70
|
+
function seedNpmRuntimeLayout(rootDir) {
|
|
71
|
+
ensureCoreConfigs(rootDir, ["db_config", "qq_config", "bds_updater", "permissions"]);
|
|
72
|
+
if (path.resolve(rootDir) === path.resolve(ROOT)) {
|
|
73
|
+
ensurePackUpdateConfigFile();
|
|
74
|
+
}
|
|
75
|
+
const modulesRoot = path.join(rootDir, "modules");
|
|
76
|
+
const packagesDir = path.join(modulesRoot, "packages");
|
|
77
|
+
fs.mkdirSync(packagesDir, { recursive: true });
|
|
78
|
+
const catalogPath = path.join(modulesRoot, "catalog.json");
|
|
79
|
+
if (!fs.existsSync(catalogPath)) {
|
|
80
|
+
writeJson(
|
|
81
|
+
catalogPath,
|
|
82
|
+
withConfigSchema({ version: 1, modules: [] }, "module_catalog", "modules")
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const lockPath = path.join(modulesRoot, "module-lock.json");
|
|
86
|
+
if (!fs.existsSync(lockPath)) {
|
|
87
|
+
writeJson(lockPath, { version: 1, modules: {} });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function runWizard() {
|
|
91
|
+
intro(c.bold(t("wizard.intro")));
|
|
92
|
+
const langPick = await select({
|
|
93
|
+
message: t("locale.wizard"),
|
|
94
|
+
options: [
|
|
95
|
+
{ value: "zh-CN", label: t("locale.opt.zh") },
|
|
96
|
+
{ value: "en", label: t("locale.opt.en") }
|
|
97
|
+
],
|
|
98
|
+
initialValue: "zh-CN"
|
|
99
|
+
});
|
|
100
|
+
if (!isCancel(langPick)) {
|
|
101
|
+
persistLocale(ROOT, langPick);
|
|
102
|
+
}
|
|
103
|
+
if (isRuntimeInitialized()) {
|
|
104
|
+
const r = await confirm({ message: t("wizard.rerun"), initialValue: false });
|
|
105
|
+
if (isCancel(r) || !r) {
|
|
106
|
+
outro(c.dim(t("wizard.skipped")));
|
|
107
|
+
return;
|
|
174
108
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
109
|
+
}
|
|
110
|
+
const rootDir = ROOT;
|
|
111
|
+
note(c.text(t("wizard.runtimeRoot", { root: rootDir })), t("wizard.step1"));
|
|
112
|
+
try {
|
|
113
|
+
await prepareRuntimeAssets(rootDir);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
outro(c.red(t("wizard.prepFailed", { message: error.message })));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
note(c.text(t("wizard.step2Note")), t("wizard.step2"));
|
|
119
|
+
const bdsResolved = await pickDirectory(t("wizard.bdsDir"), path.join(rootDir, "BDS"));
|
|
120
|
+
if (!ensureDirectory(bdsResolved)) {
|
|
121
|
+
outro(c.red(t("wizard.cannotCreateBds", { dir: bdsResolved })));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
let llbotPath;
|
|
125
|
+
const llbotEnabled = await confirm({ message: t("wizard.enableLlbot"), initialValue: false });
|
|
126
|
+
if (!isCancel(llbotEnabled) && llbotEnabled) {
|
|
127
|
+
const picked = await pickDirectory(t("wizard.llbotDir"), path.join(rootDir, "LLBOT"));
|
|
128
|
+
if (ensureDirectory(picked)) {
|
|
129
|
+
llbotPath = picked;
|
|
130
|
+
} else {
|
|
131
|
+
llbotPath = path.join(rootDir, "LLBOT");
|
|
132
|
+
note(c.text(t("wizard.usingDefault", { path: llbotPath })), t("common.tips"));
|
|
181
133
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
134
|
+
}
|
|
135
|
+
const dbDirInput = await pickDirectory(t("wizard.dbDir"), path.join(rootDir, "data"));
|
|
136
|
+
const dbDir = ensureDirectory(dbDirInput) ? dbDirInput : path.join(rootDir, "data");
|
|
137
|
+
if (dbDir !== dbDirInput) {
|
|
138
|
+
note(c.text(t("wizard.usingDefault", { path: dbDir })), t("common.tips"));
|
|
139
|
+
}
|
|
140
|
+
const dbPortRaw = await text({
|
|
141
|
+
message: t("wizard.dbPort"),
|
|
142
|
+
initialValue: "3001",
|
|
143
|
+
validate: (v) => {
|
|
144
|
+
const n = parseInt(v, 10);
|
|
145
|
+
if (isNaN(n) || n < 1024 || n > 65535) return t("wizard.portRange");
|
|
146
|
+
if (v.length === 0) return t("wizard.valueRequired");
|
|
185
147
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
148
|
+
});
|
|
149
|
+
const dbPort = isCancel(dbPortRaw) ? 3001 : parseInt(dbPortRaw, 10);
|
|
150
|
+
const bdsExe = path.join(bdsResolved, "bedrock_server.exe");
|
|
151
|
+
const bdsExists = fs.existsSync(bdsExe);
|
|
152
|
+
note(
|
|
153
|
+
bdsExists ? c.green(t("wizard.bdsFound", { path: bdsResolved })) : c.yellow(t("wizard.bdsNotFound", { path: bdsResolved })),
|
|
154
|
+
t("wizard.step3")
|
|
155
|
+
);
|
|
156
|
+
const EULA = await confirm({
|
|
157
|
+
message: t("wizard.eula"),
|
|
158
|
+
initialValue: false
|
|
159
|
+
});
|
|
160
|
+
if (isCancel(EULA) || !EULA) {
|
|
161
|
+
outro(c.yellow(t("wizard.disagreed")));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
let downloadBds = false;
|
|
165
|
+
let bdsChannel = "release";
|
|
166
|
+
let backupDir = path.join(path.dirname(bdsResolved), "backups");
|
|
167
|
+
let preserve = [];
|
|
168
|
+
if (!bdsExists) {
|
|
169
|
+
const d = await confirm({ message: t("wizard.downloadBds"), initialValue: true });
|
|
170
|
+
if (!isCancel(d) && d) {
|
|
171
|
+
const ch = await select({
|
|
172
|
+
message: t("wizard.selectChannel"),
|
|
173
|
+
options: [
|
|
174
|
+
{ value: "release", label: t("wizard.channel.release"), hint: t("wizard.channel.releaseHint") },
|
|
175
|
+
{ value: "preview", label: t("wizard.channel.preview"), hint: t("wizard.channel.previewHint") }
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
if (!isCancel(ch)) {
|
|
179
|
+
bdsChannel = ch;
|
|
180
|
+
downloadBds = true;
|
|
181
|
+
}
|
|
182
|
+
if (!ensureDirectory(backupDir)) {
|
|
183
|
+
outro(c.red(t("wizard.cannotCreateBackup", { dir: backupDir })));
|
|
191
184
|
return;
|
|
185
|
+
}
|
|
192
186
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
187
|
+
}
|
|
188
|
+
backupDir = await pickDirectory(t("wizard.backupDir"), backupDir);
|
|
189
|
+
const pr = await multiselect({
|
|
190
|
+
message: t("wizard.preserve"),
|
|
191
|
+
options: [
|
|
192
|
+
{ value: "server.properties", label: "server.properties", hint: t("wizard.preserve.serverProps"), disabled: true },
|
|
193
|
+
{ value: "whitelist.json", label: "whitelist.json" },
|
|
194
|
+
{ value: "permissions.json", label: "permissions.json" },
|
|
195
|
+
{ value: "allowlist.json", label: "allowlist.json" },
|
|
196
|
+
{ value: "worlds", label: "worlds/", hint: t("wizard.preserve.worlds") },
|
|
197
|
+
{ value: "config", label: "config/", hint: t("wizard.preserve.config"), disabled: true }
|
|
198
|
+
],
|
|
199
|
+
required: false
|
|
200
|
+
});
|
|
201
|
+
if (!isCancel(pr)) preserve = pr;
|
|
202
|
+
note(c.yellow(t("wizard.updaterTip")), t("common.tips"));
|
|
203
|
+
const catalog = readJson(modulePath(rootDir, "catalog.json")) ?? {};
|
|
204
|
+
const catalogModules = [];
|
|
205
|
+
if (Array.isArray(catalog.modules)) {
|
|
206
|
+
for (const m of catalog.modules) {
|
|
207
|
+
const entry = m;
|
|
208
|
+
catalogModules.push({
|
|
209
|
+
id: String(entry.id ?? ""),
|
|
210
|
+
name: String(entry.name ?? entry.id ?? ""),
|
|
211
|
+
type: String(entry.type ?? "feature"),
|
|
212
|
+
description: String(entry.description ?? "")
|
|
213
|
+
});
|
|
204
214
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
}
|
|
216
|
+
note(
|
|
217
|
+
c.text(
|
|
218
|
+
catalogModules.length > 0 ? t("wizard.modulesFound", { count: catalogModules.length }) : t("wizard.modulesNone")
|
|
219
|
+
),
|
|
220
|
+
t("wizard.step4")
|
|
221
|
+
);
|
|
222
|
+
let selectedModules = [];
|
|
223
|
+
const coreMd = [];
|
|
224
|
+
const featMd = [];
|
|
225
|
+
catalogModules.forEach((k) => {
|
|
226
|
+
if (k.type === "core") {
|
|
227
|
+
coreMd.push({
|
|
228
|
+
value: k.id,
|
|
229
|
+
label: `${k.name} (${k.type})`,
|
|
230
|
+
hint: String(k.description ?? t("wizard.emptyHint")),
|
|
231
|
+
disabled: true
|
|
232
|
+
});
|
|
233
|
+
} else if (k.type === "feature") {
|
|
234
|
+
featMd.push({
|
|
235
|
+
value: k.id,
|
|
236
|
+
label: `${k.name} (${k.type})`,
|
|
237
|
+
hint: String(k.description ?? t("wizard.emptyHint"))
|
|
238
|
+
});
|
|
209
239
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return "Enter 1024–65535";
|
|
217
|
-
if (v.length === 0)
|
|
218
|
-
return `Value is required!`;
|
|
219
|
-
},
|
|
220
|
-
});
|
|
221
|
-
const dbPort = isCancel(dbPortRaw) ? 3001 : parseInt(dbPortRaw, 10);
|
|
222
|
-
// Step 4: BDS environment
|
|
223
|
-
const bdsExe = path.join(bdsResolved, "bedrock_server.exe");
|
|
224
|
-
const bdsExists = fs.existsSync(bdsExe);
|
|
225
|
-
note(bdsExists ? c.green(`Found at ${bdsResolved}`) : c.yellow(`Not found at ${bdsResolved}`), "Step 3 - BDS Environment");
|
|
226
|
-
const EULA = await confirm({
|
|
227
|
-
message: "Do you agree to the EULA(https://www.minecraft.net/en-us/eula) published by Mojang? You must agree to it before you can use this program.",
|
|
228
|
-
initialValue: false,
|
|
240
|
+
});
|
|
241
|
+
if (catalogModules.length > 0) {
|
|
242
|
+
const r = await multiselect({
|
|
243
|
+
message: t("wizard.enableModules"),
|
|
244
|
+
options: [...coreMd, ...featMd],
|
|
245
|
+
required: false
|
|
229
246
|
});
|
|
230
|
-
if (isCancel(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
247
|
+
if (!isCancel(r)) selectedModules = r;
|
|
248
|
+
}
|
|
249
|
+
const wizardTasks = [
|
|
250
|
+
{
|
|
251
|
+
title: t("wizard.updatingConfigs"),
|
|
252
|
+
task: () => {
|
|
253
|
+
const slashPath = (value) => value.replace(/\\/g, "/");
|
|
254
|
+
patchJson(rootDir, "db_config.json", {
|
|
255
|
+
db_port: dbPort,
|
|
256
|
+
dbDir: slashPath(path.relative(rootDir, path.join(dbDir, "sfmc_data.db"))),
|
|
257
|
+
modulesDir: "modules"
|
|
258
|
+
});
|
|
259
|
+
const llbotOn = !!llbotEnabled && !!llbotPath;
|
|
260
|
+
patchJson(rootDir, "qq_config.json", {
|
|
261
|
+
llbot_enabled: llbotOn,
|
|
262
|
+
llbot_path: llbotPath ? slashPath(llbotPath) : "",
|
|
263
|
+
llbot_cwd: llbotPath ? slashPath(llbotPath) : ""
|
|
264
|
+
});
|
|
265
|
+
patchJson(rootDir, "bds_updater.json", {
|
|
266
|
+
bds_path: slashPath(bdsResolved),
|
|
267
|
+
channel: bdsChannel,
|
|
268
|
+
backup_dir: slashPath(backupDir),
|
|
269
|
+
preserve,
|
|
270
|
+
qq_notify: llbotOn
|
|
271
|
+
});
|
|
272
|
+
patchJson(rootDir, "runtime.json", {
|
|
273
|
+
runtime_root: rootDir,
|
|
274
|
+
initialized_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
275
|
+
locale: isCancel(langPick) ? void 0 : langPick
|
|
276
|
+
});
|
|
277
|
+
return t("wizard.configsUpdated");
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
title: t("wizard.updatingModules"),
|
|
282
|
+
task: async () => {
|
|
283
|
+
const lock = readJson(modulePath(rootDir, "module-lock.json")) ?? {
|
|
284
|
+
version: 1,
|
|
285
|
+
modules: {}
|
|
286
|
+
};
|
|
287
|
+
if (!lock.modules) lock.modules = {};
|
|
288
|
+
const now = Date.now();
|
|
289
|
+
for (const id of selectedModules) lock.modules[id] = { enabled: true, updatedAt: now };
|
|
290
|
+
writeJson(modulePath(rootDir, "module-lock.json"), lock);
|
|
291
|
+
return t("wizard.modulesUpdated");
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
title: t("wizard.initDb"),
|
|
296
|
+
task: async () => {
|
|
297
|
+
const child = spawnService("db", [], {
|
|
298
|
+
cwd: rootDir,
|
|
299
|
+
stdio: "ignore",
|
|
300
|
+
env: { ...process.env, DB_PORT: String(dbPort) }
|
|
301
|
+
});
|
|
302
|
+
if (!await waitForHealth(dbPort)) {
|
|
303
|
+
child.kill("SIGTERM");
|
|
304
|
+
return t("wizard.dbTimeout");
|
|
286
305
|
}
|
|
306
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
307
|
+
child.kill("SIGTERM");
|
|
308
|
+
setTimeout(() => child.kill("SIGKILL"), 3e3).unref();
|
|
309
|
+
return t("wizard.dbOk");
|
|
310
|
+
}
|
|
287
311
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
label: `${k.name} (${k.type})`,
|
|
297
|
-
hint: String(k.description ?? "(empty)"),
|
|
298
|
-
disabled: true,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
else if (k.type === "feature") {
|
|
302
|
-
featMd.push({
|
|
303
|
-
value: k.id,
|
|
304
|
-
label: `${k.name} (${k.type})`,
|
|
305
|
-
hint: String(k.description ?? "(empty)"),
|
|
306
|
-
});
|
|
312
|
+
];
|
|
313
|
+
if (downloadBds) {
|
|
314
|
+
wizardTasks.push({
|
|
315
|
+
title: t("wizard.downloadingBds"),
|
|
316
|
+
task: async () => {
|
|
317
|
+
const result = await runBdsUpdate(rootDir, bdsChannel);
|
|
318
|
+
if (result.code !== 0) {
|
|
319
|
+
return t("wizard.downloadFail");
|
|
307
320
|
}
|
|
321
|
+
return t("wizard.bdsDownloaded");
|
|
322
|
+
}
|
|
308
323
|
});
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
// Write paths into configs
|
|
320
|
-
const t = [
|
|
321
|
-
{
|
|
322
|
-
title: "Updating configs",
|
|
323
|
-
task: () => {
|
|
324
|
-
const slashPath = (value) => value.replace(/\\/g, "/");
|
|
325
|
-
patchJson(rootDir, "db_config.json", {
|
|
326
|
-
db_port: dbPort,
|
|
327
|
-
dbDir: slashPath(path.relative(rootDir, path.join(dbDir, "sfmc_data.db"))),
|
|
328
|
-
modulesDir: "modules",
|
|
329
|
-
});
|
|
330
|
-
const llbotOn = !!llbotEnabled && !!llbotPath;
|
|
331
|
-
patchJson(rootDir, "qq_config.json", {
|
|
332
|
-
llbot_enabled: llbotOn,
|
|
333
|
-
llbot_path: llbotPath ? slashPath(llbotPath) : "",
|
|
334
|
-
llbot_cwd: llbotPath ? slashPath(llbotPath) : "",
|
|
335
|
-
});
|
|
336
|
-
patchJson(rootDir, "bds_updater.json", {
|
|
337
|
-
bds_path: slashPath(bdsResolved),
|
|
338
|
-
channel: bdsChannel,
|
|
339
|
-
backup_dir: slashPath(backupDir),
|
|
340
|
-
preserve,
|
|
341
|
-
qq_notify: llbotOn,
|
|
342
|
-
});
|
|
343
|
-
return "Configs updated";
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
title: "Updating module state",
|
|
348
|
-
task: async () => {
|
|
349
|
-
const lock = readJson(modulePath(rootDir, "module-lock.json")) ?? {
|
|
350
|
-
version: 1,
|
|
351
|
-
modules: {},
|
|
352
|
-
};
|
|
353
|
-
if (!lock.modules)
|
|
354
|
-
lock.modules = {};
|
|
355
|
-
const now = Date.now();
|
|
356
|
-
for (const id of selectedModules)
|
|
357
|
-
lock.modules[id] = { enabled: true, updatedAt: now };
|
|
358
|
-
writeJson(modulePath(rootDir, "module-lock.json"), lock);
|
|
359
|
-
return "Module state updated";
|
|
360
|
-
},
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
title: "Initializing database",
|
|
364
|
-
task: async () => {
|
|
365
|
-
const child = spawnService("db", [], {
|
|
366
|
-
cwd: rootDir,
|
|
367
|
-
stdio: "ignore",
|
|
368
|
-
env: { ...process.env, DB_PORT: String(dbPort) },
|
|
369
|
-
});
|
|
370
|
-
if (!(await waitForHealth(dbPort))) {
|
|
371
|
-
child.kill("SIGTERM");
|
|
372
|
-
return "Database startup timed out";
|
|
373
|
-
}
|
|
374
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
375
|
-
child.kill("SIGTERM");
|
|
376
|
-
setTimeout(() => child.kill("SIGKILL"), 3000).unref();
|
|
377
|
-
return "Database initialized";
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
];
|
|
381
|
-
if (downloadBds) {
|
|
382
|
-
t.push({
|
|
383
|
-
title: "Downloading BDS",
|
|
384
|
-
task: async () => {
|
|
385
|
-
const result = await runBdsUpdate(rootDir, bdsChannel);
|
|
386
|
-
if (result.code !== 0) {
|
|
387
|
-
return "Downloaded Failure.";
|
|
388
|
-
}
|
|
389
|
-
return "BDS downloaded";
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
try {
|
|
394
|
-
await tasks(t);
|
|
395
|
-
}
|
|
396
|
-
catch (e) {
|
|
397
|
-
outro(c.red(`Error: ${e.message}`));
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
/* install → build → deploy chain. If the user skipped module selection
|
|
401
|
-
* earlier, this still runs `behavior-pack build` + `deploy` so the BP/
|
|
402
|
-
* RP folders exist on disk — BDS just sees an empty script. */
|
|
403
|
-
await runInstallBuildDeploy(rootDir, selectedModules, bdsResolved);
|
|
404
|
-
outro(c.green("Done! Run help to learn managing."));
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
await tasks(wizardTasks);
|
|
327
|
+
} catch (e) {
|
|
328
|
+
outro(c.red(t("common.error", { message: e.message })));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
await runInstallBuildDeploy(rootDir, selectedModules, bdsResolved);
|
|
332
|
+
outro(c.green(t("wizard.done")));
|
|
405
333
|
}
|
|
406
334
|
async function runInstallBuildDeploy(rootDir, selectedModules, bdsResolved) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
{
|
|
412
|
-
title: `Installing ${selectedModules.length} module(s) from first-party registry`,
|
|
413
|
-
task: async () => {
|
|
414
|
-
execFileSync(process.execPath, [fetchScript, "install", ...selectedModules], {
|
|
415
|
-
cwd: rootDir,
|
|
416
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
417
|
-
});
|
|
418
|
-
return `${selectedModules.length} installed`;
|
|
419
|
-
},
|
|
420
|
-
},
|
|
421
|
-
];
|
|
422
|
-
try {
|
|
423
|
-
await tasks(installT);
|
|
424
|
-
}
|
|
425
|
-
catch (e) {
|
|
426
|
-
note(c.yellow(`install failed: ${e.message}\nYou can retry with: sfmc module install <id>`));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
note(c.yellow(`tools/fetch-module.mjs missing — run \`sfmc module install <id>\` later for each of: ${selectedModules.join(", ")}`));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
note(c.dim("No modules selected — behavior pack will be empty until you install one with `sfmc module install <id>`."));
|
|
435
|
-
}
|
|
436
|
-
const { cmdBehaviorPackBuild, cmdBehaviorPackDeploy } = await import("./commands-behavior-pack.js");
|
|
437
|
-
const buildT = [
|
|
335
|
+
if (selectedModules.length > 0) {
|
|
336
|
+
const fetchScript = resolveFetchModule();
|
|
337
|
+
if (fetchScript) {
|
|
338
|
+
const installT = [
|
|
438
339
|
{
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
340
|
+
title: t("wizard.installing", { count: selectedModules.length }),
|
|
341
|
+
task: async () => {
|
|
342
|
+
execFileSync(process.execPath, [fetchScript, "install", ...selectedModules], {
|
|
343
|
+
cwd: rootDir,
|
|
344
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
345
|
+
env: { ...process.env, SFMC_ROOT: rootDir }
|
|
346
|
+
});
|
|
347
|
+
return t("wizard.installed", { count: selectedModules.length });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
];
|
|
351
|
+
try {
|
|
352
|
+
await tasks(installT);
|
|
353
|
+
} catch (e) {
|
|
354
|
+
note(c.yellow(t("wizard.installFailed", { message: e.message })));
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
note(c.yellow(t("wizard.fetchMissing", { list: selectedModules.join(", ") })));
|
|
454
358
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
await tasks(deployT);
|
|
468
|
-
note(c.green("Restart BDS to load the new behavior pack."));
|
|
359
|
+
} else {
|
|
360
|
+
note(c.dim(t("wizard.noModulesSelected")));
|
|
361
|
+
}
|
|
362
|
+
const { cmdBehaviorPackBuild, cmdBehaviorPackDeploy } = await import("./commands-behavior-pack.js");
|
|
363
|
+
const buildT = [
|
|
364
|
+
{
|
|
365
|
+
title: t("wizard.buildingBp"),
|
|
366
|
+
task: async () => {
|
|
367
|
+
const r = await cmdBehaviorPackBuild([]);
|
|
368
|
+
if (!r.ok) throw new Error(r.message);
|
|
369
|
+
return r.message;
|
|
370
|
+
}
|
|
469
371
|
}
|
|
470
|
-
|
|
471
|
-
|
|
372
|
+
];
|
|
373
|
+
try {
|
|
374
|
+
await tasks(buildT);
|
|
375
|
+
} catch (e) {
|
|
376
|
+
note(c.yellow(t("wizard.buildFailed", { message: e.message })));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const deployT = [
|
|
380
|
+
{
|
|
381
|
+
title: t("wizard.deploying", { path: bdsResolved }),
|
|
382
|
+
task: async () => {
|
|
383
|
+
const r = await cmdBehaviorPackDeploy([]);
|
|
384
|
+
if (!r.ok) throw new Error(r.message);
|
|
385
|
+
return r.message;
|
|
386
|
+
}
|
|
472
387
|
}
|
|
473
|
-
|
|
388
|
+
];
|
|
389
|
+
try {
|
|
390
|
+
await tasks(deployT);
|
|
391
|
+
note(c.green(t("wizard.restartBds")));
|
|
392
|
+
} catch (e) {
|
|
393
|
+
note(c.yellow(t("wizard.deployFailed", { message: e.message })));
|
|
394
|
+
}
|
|
395
|
+
void rootDir;
|
|
474
396
|
}
|
|
475
|
-
|
|
397
|
+
export {
|
|
398
|
+
runWizard
|
|
399
|
+
};
|
|
400
|
+
//# sourceMappingURL=wizard.js.map
|