@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.
Files changed (138) hide show
  1. package/README.md +7 -1
  2. package/dist/cli-result.d.ts +12 -0
  3. package/dist/cli-result.d.ts.map +1 -0
  4. package/dist/cli-result.js +15 -0
  5. package/dist/cli-result.js.map +7 -0
  6. package/dist/commands-behavior-pack.d.ts +11 -42
  7. package/dist/commands-behavior-pack.d.ts.map +1 -1
  8. package/dist/commands-behavior-pack.js +40 -159
  9. package/dist/commands-behavior-pack.js.map +7 -1
  10. package/dist/commands-reload.d.ts +9 -0
  11. package/dist/commands-reload.d.ts.map +1 -0
  12. package/dist/commands-reload.js +30 -0
  13. package/dist/commands-reload.js.map +7 -0
  14. package/dist/commands.d.ts +7 -0
  15. package/dist/commands.d.ts.map +1 -1
  16. package/dist/commands.js +182 -172
  17. package/dist/commands.js.map +7 -1
  18. package/dist/i18n/index.d.ts +26 -0
  19. package/dist/i18n/index.d.ts.map +1 -0
  20. package/dist/i18n/index.js +107 -0
  21. package/dist/i18n/index.js.map +7 -0
  22. package/dist/i18n/locales/en.d.ts +381 -0
  23. package/dist/i18n/locales/en.d.ts.map +1 -0
  24. package/dist/i18n/locales/en.js +393 -0
  25. package/dist/i18n/locales/en.js.map +7 -0
  26. package/dist/i18n/locales/zh-CN.d.ts +4 -0
  27. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  28. package/dist/i18n/locales/zh-CN.js +379 -0
  29. package/dist/i18n/locales/zh-CN.js.map +7 -0
  30. package/dist/i18n/types.d.ts +7 -0
  31. package/dist/i18n/types.d.ts.map +1 -0
  32. package/dist/i18n/types.js +17 -0
  33. package/dist/i18n/types.js.map +7 -0
  34. package/dist/index.d.ts +5 -11
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +51 -43
  37. package/dist/index.js.map +7 -1
  38. package/dist/interactive-prompts.d.ts +8 -0
  39. package/dist/interactive-prompts.d.ts.map +1 -0
  40. package/dist/interactive-prompts.js +57 -0
  41. package/dist/interactive-prompts.js.map +7 -0
  42. package/dist/locale-command.d.ts +3 -0
  43. package/dist/locale-command.d.ts.map +1 -0
  44. package/dist/locale-command.js +19 -0
  45. package/dist/locale-command.js.map +7 -0
  46. package/dist/logs.d.ts +22 -4
  47. package/dist/logs.d.ts.map +1 -1
  48. package/dist/logs.js +208 -193
  49. package/dist/logs.js.map +7 -1
  50. package/dist/main.js +135 -173
  51. package/dist/main.js.map +7 -1
  52. package/dist/module-commands.d.ts +43 -4
  53. package/dist/module-commands.d.ts.map +1 -1
  54. package/dist/module-commands.js +440 -327
  55. package/dist/module-commands.js.map +7 -1
  56. package/dist/module-fingerprint.d.ts +7 -0
  57. package/dist/module-fingerprint.d.ts.map +1 -0
  58. package/dist/module-fingerprint.js +31 -0
  59. package/dist/module-fingerprint.js.map +7 -0
  60. package/dist/module-wizard.d.ts +14 -0
  61. package/dist/module-wizard.d.ts.map +1 -0
  62. package/dist/module-wizard.js +313 -0
  63. package/dist/module-wizard.js.map +7 -0
  64. package/dist/pack-lifecycle.d.ts +95 -0
  65. package/dist/pack-lifecycle.d.ts.map +1 -0
  66. package/dist/pack-lifecycle.js +634 -0
  67. package/dist/pack-lifecycle.js.map +7 -0
  68. package/dist/pack-update/bindings.d.ts +12 -0
  69. package/dist/pack-update/bindings.d.ts.map +1 -0
  70. package/dist/pack-update/bindings.js +64 -0
  71. package/dist/pack-update/bindings.js.map +7 -0
  72. package/dist/pack-update/config.d.ts +22 -0
  73. package/dist/pack-update/config.d.ts.map +1 -0
  74. package/dist/pack-update/config.js +141 -0
  75. package/dist/pack-update/config.js.map +7 -0
  76. package/dist/pack-update/providers/curseforge.d.ts +23 -0
  77. package/dist/pack-update/providers/curseforge.d.ts.map +1 -0
  78. package/dist/pack-update/providers/curseforge.js +209 -0
  79. package/dist/pack-update/providers/curseforge.js.map +7 -0
  80. package/dist/pack-update/providers/index.d.ts +16 -0
  81. package/dist/pack-update/providers/index.d.ts.map +1 -0
  82. package/dist/pack-update/providers/index.js +37 -0
  83. package/dist/pack-update/providers/index.js.map +7 -0
  84. package/dist/pack-update/service.d.ts +21 -0
  85. package/dist/pack-update/service.d.ts.map +1 -0
  86. package/dist/pack-update/service.js +567 -0
  87. package/dist/pack-update/service.js.map +7 -0
  88. package/dist/pack-update/types.d.ts +126 -0
  89. package/dist/pack-update/types.d.ts.map +1 -0
  90. package/dist/pack-update/types.js +8 -0
  91. package/dist/pack-update/types.js.map +7 -0
  92. package/dist/pack-update/version-policy.d.ts +80 -0
  93. package/dist/pack-update/version-policy.d.ts.map +1 -0
  94. package/dist/pack-update/version-policy.js +216 -0
  95. package/dist/pack-update/version-policy.js.map +7 -0
  96. package/dist/registry.d.ts +12 -4
  97. package/dist/registry.d.ts.map +1 -1
  98. package/dist/registry.js +73 -106
  99. package/dist/registry.js.map +7 -1
  100. package/dist/remote-agent.d.ts.map +1 -1
  101. package/dist/remote-agent.js +160 -176
  102. package/dist/remote-agent.js.map +7 -1
  103. package/dist/repl.d.ts +4 -0
  104. package/dist/repl.d.ts.map +1 -1
  105. package/dist/repl.js +683 -605
  106. package/dist/repl.js.map +7 -1
  107. package/dist/runtime.d.ts +40 -14
  108. package/dist/runtime.d.ts.map +1 -1
  109. package/dist/runtime.js +185 -62
  110. package/dist/runtime.js.map +7 -1
  111. package/dist/services.d.ts +2 -0
  112. package/dist/services.d.ts.map +1 -1
  113. package/dist/services.js +251 -238
  114. package/dist/services.js.map +7 -1
  115. package/dist/sfmc-modules-root.d.ts +24 -0
  116. package/dist/sfmc-modules-root.d.ts.map +1 -0
  117. package/dist/sfmc-modules-root.js +70 -0
  118. package/dist/sfmc-modules-root.js.map +7 -0
  119. package/dist/theme.d.ts +4 -0
  120. package/dist/theme.d.ts.map +1 -1
  121. package/dist/theme.js +134 -48
  122. package/dist/theme.js.map +7 -1
  123. package/dist/wizard.d.ts.map +1 -1
  124. package/dist/wizard.js +363 -438
  125. package/dist/wizard.js.map +7 -1
  126. package/dist/world-packs.d.ts +22 -0
  127. package/dist/world-packs.d.ts.map +1 -0
  128. package/dist/world-packs.js +872 -0
  129. package/dist/world-packs.js.map +7 -0
  130. package/package.json +10 -10
  131. package/dist/dispatcher.d.ts +0 -13
  132. package/dist/dispatcher.d.ts.map +0 -1
  133. package/dist/dispatcher.js +0 -23
  134. package/dist/dispatcher.js.map +0 -1
  135. package/dist/terminal.d.ts +0 -3
  136. package/dist/terminal.d.ts.map +0 -1
  137. package/dist/terminal.js +0 -178
  138. 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 { configPath, modulePath, patchJson as patchConfig, readJson, writeJson, } from "@sfmc-bds/sdk/node/config";
3
- import JSZip from "jszip";
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 { getAsset } from "node:sea";
8
- import { IS_SEA, ROOT, spawnService } from "./runtime.js";
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
- patchConfig(configPath(rootDir, name), updates);
20
+ patchConfig(configPath(rootDir, name), updates);
13
21
  }
14
- function ensureDirectory(directory) {
22
+ async function waitForHealth(port, ms = 15e3) {
23
+ const t2 = Date.now();
24
+ while (Date.now() - t2 < ms) {
15
25
  try {
16
- fs.mkdirSync(directory, { recursive: true });
17
- return fs.statSync(directory).isDirectory();
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
- async function waitForHealth(port, ms = 15000) {
60
- const t = Date.now();
61
- while (Date.now() - t < ms) {
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
- return ASSETS.flatMap(({ target, npmTarget }) => {
116
- const directory = path.join(rootDir, npmTarget);
117
- try {
118
- return fs.statSync(directory).isDirectory() && fs.readdirSync(directory).length > 0 ? [] : [target];
119
- }
120
- catch {
121
- return [target];
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
- const child = spawnService("update", [`--channel=${channel}`, "--force", "--no-start"], {
127
- cwd: rootDir,
128
- stdio: "pipe",
129
- });
130
- const output = [];
131
- child.stdout?.on("data", (data) => output.push(data.toString()));
132
- child.stderr?.on("data", (data) => output.push(data.toString()));
133
- return new Promise((resolve) => {
134
- child.once("error", (error) => resolve({ code: null, output: `${output.join("")}${error.message}` }));
135
- child.once("close", (code) => resolve({ code, output: output.join("") }));
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
- if (!IS_SEA) {
140
- const missing = missingRuntimeAssets(rootDir);
141
- if (missing.length > 0) {
142
- throw new Error(`npm runtime is missing required directories: ${missing.join(", ")}`);
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
- await tasks([
147
- {
148
- title: "Extracting bundled assets",
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
- export async function runWizard() {
166
- intro(c.bold("Setup Wizard =D"));
167
- const hasConfigs = fs.existsSync(configPath(ROOT, "db_config.json"));
168
- if (hasConfigs) {
169
- const r = await confirm({ message: "Configs already exist. Re-run setup?", initialValue: false });
170
- if (isCancel(r) || !r) {
171
- outro(c.dim("Setup skipped"));
172
- return;
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
- // Step 1: Runtime Environment
176
- const rootDir = ROOT;
177
- note(c.text(`Runtime root: ${rootDir}`), "Step 1 - Runtime Environment");
178
- try {
179
- await prepareRuntimeAssets(rootDir);
180
- patchJson(rootDir, "runtime.json", { runtime_root: rootDir, initialized_at: new Date().toISOString() });
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
- catch (error) {
183
- outro(c.red(`Runtime preparation failed: ${error.message}`));
184
- return;
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
- // Step 2: External runtime paths
187
- note(c.text("Select paths for BDS, Database, and LLBOT"), "Step 2 - External Runtimes");
188
- const bdsResolved = await pickDirectory("BDS installation directory", path.join(rootDir, "BDS"));
189
- if (!ensureDirectory(bdsResolved)) {
190
- outro(c.red(`Cannot create BDS installation directory: ${bdsResolved}`));
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
- let llbotPath;
194
- const llbotEnabled = await confirm({ message: "Enable LLBot (QQ bridge)?", initialValue: false });
195
- if (!isCancel(llbotEnabled) && llbotEnabled) {
196
- const picked = await pickDirectory("LLBot runtime directory", path.join(rootDir, "LLBOT"));
197
- if (ensureDirectory(picked)) {
198
- llbotPath = picked;
199
- }
200
- else {
201
- llbotPath = path.join(rootDir, "LLBOT");
202
- note(c.text(`Using default: ${llbotPath}`), "TIPS");
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
- const dbDirInput = await pickDirectory("Database storage directory", path.join(rootDir, "data"));
206
- const dbDir = ensureDirectory(dbDirInput) ? dbDirInput : path.join(rootDir, "data");
207
- if (dbDir !== dbDirInput) {
208
- note(c.text(`Using default: ${dbDir}`), "TIPS");
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
- const dbPortRaw = await text({
211
- message: "Database server port:",
212
- initialValue: "3001",
213
- validate: (v) => {
214
- const n = parseInt(v, 10);
215
- if (isNaN(n) || n < 1024 || n > 65535)
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(EULA) || !EULA) {
231
- outro(c.yellow("Disagreed."));
232
- return;
233
- }
234
- let downloadBds = false;
235
- let bdsChannel = "release";
236
- let backupDir = path.join(path.dirname(bdsResolved), "backups");
237
- let preserve = [];
238
- if (!bdsExists) {
239
- const d = await confirm({ message: "Download BDS now?", initialValue: true });
240
- if (!isCancel(d) && d) {
241
- const ch = await select({
242
- message: "Select channel:",
243
- options: [
244
- { value: "release", label: "Release", hint: "stable" },
245
- { value: "preview", label: "Preview", hint: "may be unstable" },
246
- ],
247
- });
248
- if (!isCancel(ch)) {
249
- bdsChannel = ch;
250
- downloadBds = true;
251
- }
252
- if (!ensureDirectory(backupDir)) {
253
- outro(c.red(`Cannot create BDS backup directory: ${backupDir}`));
254
- return;
255
- }
256
- }
257
- }
258
- backupDir = await pickDirectory("BDS backup directory", backupDir);
259
- const pr = await multiselect({
260
- message: "Files to preserve on update(When updates are enabled next time):",
261
- options: [
262
- { value: "server.properties", label: "server.properties", hint: "server config", disabled: true },
263
- { value: "whitelist.json", label: "whitelist.json" },
264
- { value: "permissions.json", label: "permissions.json" },
265
- { value: "allowlist.json", label: "allowlist.json" },
266
- { value: "worlds", label: "worlds/", hint: "world data" },
267
- { value: "config", label: "config/", hint: "config directory", disabled: true },
268
- ],
269
- required: false,
270
- });
271
- if (!isCancel(pr))
272
- preserve = pr;
273
- note(c.yellow("If could not find BDS runtime, updater will be started later.\n You can configure updater later in configs/bds_updater.json"), "TIPS");
274
- // Step 5: Module initialization
275
- const catalog = readJson(modulePath(rootDir, "catalog.json")) ?? {};
276
- const catalogModules = [];
277
- if (Array.isArray(catalog.modules)) {
278
- for (const m of catalog.modules) {
279
- const entry = m;
280
- catalogModules.push({
281
- id: String(entry.id ?? ""),
282
- name: String(entry.name ?? entry.id ?? ""),
283
- type: String(entry.type ?? "feature"),
284
- description: String(entry.description ?? ""),
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
- note(c.text(catalogModules.length > 0 ? `Found ${catalogModules.length} optional modules` : "No modules catalog found"), "Step 4 - Modules");
289
- let selectedModules = [];
290
- const coreMd = [];
291
- const featMd = [];
292
- catalogModules.forEach((k) => {
293
- if (k.type === "core") {
294
- coreMd.push({
295
- value: k.id,
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
- if (catalogModules.length > 0) {
310
- const r = await multiselect({
311
- message: "Enable modules:",
312
- // merge core and feature module option arrays into a single options array
313
- options: [...coreMd, ...featMd],
314
- required: false,
315
- });
316
- if (!isCancel(r))
317
- selectedModules = r;
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
- if (selectedModules.length > 0) {
408
- const fetchScript = path.join(rootDir, "tools", "fetch-module.mjs");
409
- if (fs.existsSync(fetchScript)) {
410
- const installT = [
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
- title: "Building behavior pack",
440
- task: async () => {
441
- const out = await cmdBehaviorPackBuild([]);
442
- if (out.startsWith(""))
443
- throw new Error(out);
444
- return out;
445
- },
446
- },
447
- ];
448
- try {
449
- await tasks(buildT);
450
- }
451
- catch (e) {
452
- note(c.yellow(`build failed: ${e.message}\nRun \`sfmc behavior-pack build\` later to retry.`));
453
- return;
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
- const deployT = [
456
- {
457
- title: `Deploying to ${bdsResolved}`,
458
- task: async () => {
459
- const out = await cmdBehaviorPackDeploy([]);
460
- if (out.startsWith(""))
461
- throw new Error(out);
462
- return out;
463
- },
464
- },
465
- ];
466
- try {
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
- catch (e) {
471
- note(c.yellow(`deploy failed: ${e.message}\nRun \`sfmc behavior-pack deploy\` after fixing bds_root in configs/.`));
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
- void rootDir;
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
- //# sourceMappingURL=wizard.js.map
397
+ export {
398
+ runWizard
399
+ };
400
+ //# sourceMappingURL=wizard.js.map