@mafly/bdev-cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +225 -2
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.js +94 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.js +46 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.js +165 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +84 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +164 -0
- package/dist/commands/package.d.ts +5 -0
- package/dist/commands/package.js +103 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +148 -0
- package/dist/utils/bundler.d.ts +2 -0
- package/dist/utils/bundler.js +29 -0
- package/dist/utils/config.d.ts +14 -0
- package/dist/utils/config.js +58 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +19 -0
- package/dist/utils/manifest.d.ts +42 -0
- package/dist/utils/manifest.js +146 -0
- package/dist/utils/mojang.d.ts +8 -0
- package/dist/utils/mojang.js +122 -0
- package/dist/utils/wsserver.d.ts +25 -0
- package/dist/utils/wsserver.js +109 -0
- package/package.json +52 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mafly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,226 @@
|
|
|
1
|
-
#
|
|
1
|
+
# bdev
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A simple CLI tool for Minecraft Bedrock Edition addon development.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **TypeScript Compilation**: Bundles scripts with `esbuild`, keeping `@minecraft/*` modules external.
|
|
10
|
+
- **In-Game Hot Reload**: Built-in WebSocket server triggers `/reload` in your active Minecraft world on file save.
|
|
11
|
+
- **Live Sync**: Symlinks behavior and resource packs to Bedrock `development_*_packs` folders.
|
|
12
|
+
- **Manifest Management**: Generates and validates `manifest.json` files with v4 UUIDs.
|
|
13
|
+
- **Distribution Packaging**: Compresses packs into `.mcaddon` and `.mcpack` files.
|
|
14
|
+
- **Diagnostics**: Checks Node.js, npm, Bun, and `com.mojang` path detection.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Option 1: Via NPM (Recommended)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @mafly/bdev-cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or run directly without installing:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @mafly/bdev-cli --help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Option 2: Standalone Binary
|
|
33
|
+
|
|
34
|
+
Download the compiled binary from [GitHub Releases](https://github.com/Mafly/bdev/releases).
|
|
35
|
+
|
|
36
|
+
Binary naming format: `bdev-<version>-<os>-<arch>`
|
|
37
|
+
|
|
38
|
+
| Platform | Binary |
|
|
39
|
+
| --------------------- | -------------------------------- |
|
|
40
|
+
| Windows (x64) | `bdev-<version>-windows-x64.exe` |
|
|
41
|
+
| Linux (x64) | `bdev-<version>-linux-x64` |
|
|
42
|
+
| Linux (arm64) | `bdev-<version>-linux-arm64` |
|
|
43
|
+
| macOS (Apple Silicon) | `bdev-<version>-macos-arm64` |
|
|
44
|
+
| macOS (Intel) | `bdev-<version>-macos-x64` |
|
|
45
|
+
|
|
46
|
+
On Linux/macOS:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
chmod +x bdev-<version>-linux-x64
|
|
50
|
+
sudo mv bdev-<version>-linux-x64 /usr/local/bin/bdev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Option 3: From Source (Bun)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/Mafly/bdev.git
|
|
57
|
+
cd bdev
|
|
58
|
+
bun install
|
|
59
|
+
bun run build
|
|
60
|
+
./bdev --help
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 1. Create project
|
|
69
|
+
bdev init my-addon
|
|
70
|
+
cd my-addon
|
|
71
|
+
|
|
72
|
+
# 2. (Optional) Set com.mojang path if not detected automatically
|
|
73
|
+
bdev set-path "C:\Users\<user>\AppData\Local\Packages\Microsoft.MinecraftUWP_8wekyb3d8bbwe\LocalState\games\com.mojang"
|
|
74
|
+
|
|
75
|
+
# 3. Start dev server
|
|
76
|
+
bdev dev
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
In your Minecraft world (Cheats Enabled):
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
/connect localhost:8000
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Saving any script file will rebuild and trigger `/reload` in game.
|
|
86
|
+
|
|
87
|
+
<details>
|
|
88
|
+
<summary><b>Project Structure</b></summary>
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
my-addon/
|
|
92
|
+
├── behavior_pack/
|
|
93
|
+
│ ├── manifest.json
|
|
94
|
+
│ └── pack_icon.png
|
|
95
|
+
├── resource_pack/
|
|
96
|
+
│ ├── manifest.json
|
|
97
|
+
│ ├── pack_icon.png
|
|
98
|
+
│ └── texts/en_US.lang
|
|
99
|
+
├── src/
|
|
100
|
+
│ └── main.ts
|
|
101
|
+
├── bdev.json
|
|
102
|
+
├── package.json
|
|
103
|
+
├── tsconfig.json
|
|
104
|
+
└── .gitignore
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
</details>
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Command Reference
|
|
112
|
+
|
|
113
|
+
<details>
|
|
114
|
+
<summary><b>bdev init [directory]</b> — Scaffold project</summary>
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bdev init my-addon [options]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
| Flag | Description | Default |
|
|
121
|
+
| ------------------- | --------------------------------------- | ----------------- |
|
|
122
|
+
| `-n, --name <name>` | Project and pack name | Directory name |
|
|
123
|
+
| `-d, --desc <desc>` | Description in manifest.json | Addon description |
|
|
124
|
+
| `-p, --path <path>` | Save com.mojang path to bdev.json | Auto-detected |
|
|
125
|
+
| `--no-rp` | Behavior pack only (skip resource pack) | Both packs |
|
|
126
|
+
| `--lang <ts\|js>` | Script language (`ts` or `js`) | `ts` |
|
|
127
|
+
|
|
128
|
+
</details>
|
|
129
|
+
|
|
130
|
+
<details>
|
|
131
|
+
<summary><b>bdev dev [directory]</b> — Live watch and Hot Reload</summary>
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
bdev dev [options]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Symlinks packs to `com.mojang`, starts WebSocket server, and watches for changes.
|
|
138
|
+
|
|
139
|
+
| Flag | Description | Default |
|
|
140
|
+
| ------------------- | -------------------------------- | ---------------------- |
|
|
141
|
+
| `-p, --path <path>` | Custom com.mojang directory path | Config / auto-detected |
|
|
142
|
+
| `-s, --save` | Save `-p` path into `bdev.json` | `false` |
|
|
143
|
+
| `-P, --port <port>` | WebSocket port for `/connect` | `8000` |
|
|
144
|
+
| `--no-ws` | Disable WebSocket server | Enabled |
|
|
145
|
+
| `--no-link` | Skip creating directory symlinks | Symlinked |
|
|
146
|
+
|
|
147
|
+
</details>
|
|
148
|
+
|
|
149
|
+
<details>
|
|
150
|
+
<summary><b>bdev build [directory]</b> — Compile TypeScript</summary>
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
bdev build [options]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Bundles `src/**/*.ts` into `behavior_pack/scripts/main.js` and validates `manifest.json`.
|
|
157
|
+
|
|
158
|
+
| Flag | Description | Default |
|
|
159
|
+
| ---------------- | ------------------------- | ------------------ |
|
|
160
|
+
| `-m, --minify` | Minify bundled JavaScript | `false` |
|
|
161
|
+
| `--no-sourcemap` | Disable sourcemap output | Sourcemaps enabled |
|
|
162
|
+
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
<details>
|
|
166
|
+
<summary><b>bdev package [directory]</b> — Build and export archives</summary>
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
bdev package [options]
|
|
170
|
+
# alias: bdev deploy
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Creates distribution packages inside `dist/`:
|
|
174
|
+
|
|
175
|
+
- `dist/<name>.mcaddon` (contains both BP and RP)
|
|
176
|
+
- `dist/<name>_BP.mcpack`
|
|
177
|
+
- `dist/<name>_RP.mcpack`
|
|
178
|
+
|
|
179
|
+
| Flag | Description | Default |
|
|
180
|
+
| -------------------- | ---------------- | ------------ |
|
|
181
|
+
| `-o, --output <dir>` | Output directory | `dist/` |
|
|
182
|
+
| `-n, --name <name>` | File name prefix | Project name |
|
|
183
|
+
|
|
184
|
+
</details>
|
|
185
|
+
|
|
186
|
+
<details>
|
|
187
|
+
<summary><b>bdev set-path & config</b> — Manage configuration</summary>
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Set Bedrock com.mojang path locally (bdev.json)
|
|
191
|
+
bdev set-path "C:\path\to\com.mojang"
|
|
192
|
+
|
|
193
|
+
# Set Bedrock path globally (~/.bdev/config.json)
|
|
194
|
+
bdev set-path "C:\path\to\com.mojang" -g
|
|
195
|
+
|
|
196
|
+
# View configuration
|
|
197
|
+
bdev config get
|
|
198
|
+
bdev config get mojangPath
|
|
199
|
+
|
|
200
|
+
# Set generic config key
|
|
201
|
+
bdev config set <key> <value> [-g]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
</details>
|
|
205
|
+
|
|
206
|
+
<details>
|
|
207
|
+
<summary><b>bdev doctor</b> — Environment diagnostics</summary>
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
bdev doctor [-p <path>]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Verifies:
|
|
214
|
+
|
|
215
|
+
- Operating system platform
|
|
216
|
+
- Node.js runtime version
|
|
217
|
+
- npm package manager availability
|
|
218
|
+
- Bun runtime availability
|
|
219
|
+
- Minecraft Bedrock `com.mojang` path detection
|
|
220
|
+
</details>
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { runBuild } from "../utils/bundler.js";
|
|
4
|
+
import { validateManifest } from "../utils/manifest.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
export async function buildCommand(dirArg, options = {}) {
|
|
7
|
+
const rootDir = path.resolve(dirArg || ".");
|
|
8
|
+
const bpDir = path.join(rootDir, "behavior_pack");
|
|
9
|
+
const bpManifestPath = path.join(bpDir, "manifest.json");
|
|
10
|
+
// 1. Validate Behavior Pack Manifest
|
|
11
|
+
if (!fs.existsSync(bpManifestPath)) {
|
|
12
|
+
logger.error(`Behavior pack manifest not found: ${bpManifestPath}`);
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
logger.info("Validating manifest.json files...");
|
|
16
|
+
const bpValidation = validateManifest(bpManifestPath);
|
|
17
|
+
if (!bpValidation.valid) {
|
|
18
|
+
logger.error("Behavior pack manifest validation failed:");
|
|
19
|
+
for (const err of bpValidation.errors) {
|
|
20
|
+
console.error(` - ${err}`);
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
logger.success("Behavior pack manifest valid");
|
|
25
|
+
// Validate Resource Pack Manifest if exists
|
|
26
|
+
const rpDir = path.join(rootDir, "resource_pack");
|
|
27
|
+
const rpManifestPath = path.join(rpDir, "manifest.json");
|
|
28
|
+
if (fs.existsSync(rpManifestPath)) {
|
|
29
|
+
const rpValidation = validateManifest(rpManifestPath);
|
|
30
|
+
if (!rpValidation.valid) {
|
|
31
|
+
logger.error("Resource pack manifest validation failed:");
|
|
32
|
+
for (const err of rpValidation.errors) {
|
|
33
|
+
console.error(` - ${err}`);
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
logger.success("Resource pack manifest valid");
|
|
38
|
+
}
|
|
39
|
+
// 2. Determine if TypeScript compilation is needed
|
|
40
|
+
const srcDir = path.join(rootDir, "src");
|
|
41
|
+
if (!fs.existsSync(srcDir)) {
|
|
42
|
+
logger.info("No 'src' directory found. Skipping script compilation.");
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// Find entry points
|
|
46
|
+
let entryPoint = "";
|
|
47
|
+
if (fs.existsSync(path.join(srcDir, "main.ts"))) {
|
|
48
|
+
entryPoint = path.join(srcDir, "main.ts");
|
|
49
|
+
}
|
|
50
|
+
else if (fs.existsSync(path.join(srcDir, "index.ts"))) {
|
|
51
|
+
entryPoint = path.join(srcDir, "index.ts");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Look for any .ts file
|
|
55
|
+
const files = fs.readdirSync(srcDir).filter((f) => f.endsWith(".ts"));
|
|
56
|
+
if (files.length > 0) {
|
|
57
|
+
entryPoint = path.join(srcDir, files[0]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!entryPoint) {
|
|
61
|
+
logger.warn("No TypeScript entry point (.ts) found in 'src/'. Skipping bundling.");
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
const outDir = path.join(bpDir, "scripts");
|
|
65
|
+
if (!fs.existsSync(outDir)) {
|
|
66
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
const outFile = path.join(outDir, "main.js");
|
|
69
|
+
const spinner = logger.spinner(`Bundling ${path.relative(rootDir, entryPoint)} -> ${path.relative(rootDir, outFile)}...`);
|
|
70
|
+
try {
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
await runBuild({
|
|
73
|
+
entryPoints: [entryPoint],
|
|
74
|
+
outfile: outFile,
|
|
75
|
+
bundle: true,
|
|
76
|
+
format: "esm",
|
|
77
|
+
target: "es2020",
|
|
78
|
+
platform: "neutral",
|
|
79
|
+
minify: options.minify ?? false,
|
|
80
|
+
sourcemap: options.sourcemap ?? true,
|
|
81
|
+
external: [
|
|
82
|
+
"@minecraft/*",
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
const duration = Date.now() - startTime;
|
|
86
|
+
spinner.succeed(`Build succeeded in ${duration}ms (${path.relative(rootDir, outFile)})`);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
spinner.fail("Build failed");
|
|
91
|
+
logger.error(err.message);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ConfigOptions {
|
|
2
|
+
global?: boolean;
|
|
3
|
+
dir?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function setConfigCommand(key: string, value: string, options?: ConfigOptions): Promise<void>;
|
|
6
|
+
export declare function getConfigCommand(key?: string, options?: ConfigOptions): Promise<void>;
|
|
7
|
+
export declare function setPathCommand(inputPath: string, options?: ConfigOptions): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { loadGlobalConfig, loadMergedConfig, saveConfig, getGlobalConfigPath, getLocalConfigPath, } from "../utils/config.js";
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
export async function setConfigCommand(key, value, options = {}) {
|
|
6
|
+
const normalizedKey = key === "path" || key === "mojang-path" ? "mojangPath" : key;
|
|
7
|
+
if (normalizedKey === "mojangPath") {
|
|
8
|
+
const resolved = path.resolve(value);
|
|
9
|
+
if (!fs.existsSync(resolved)) {
|
|
10
|
+
logger.warn(`Target path does not exist on disk: ${resolved}`);
|
|
11
|
+
}
|
|
12
|
+
value = resolved;
|
|
13
|
+
}
|
|
14
|
+
const updates = {
|
|
15
|
+
[normalizedKey]: value,
|
|
16
|
+
};
|
|
17
|
+
const savedFile = saveConfig(updates, {
|
|
18
|
+
global: options.global,
|
|
19
|
+
dir: options.dir,
|
|
20
|
+
});
|
|
21
|
+
logger.success(`Set ${normalizedKey} = "${value}" in ${savedFile}`);
|
|
22
|
+
}
|
|
23
|
+
export async function getConfigCommand(key, options = {}) {
|
|
24
|
+
const conf = options.global
|
|
25
|
+
? loadGlobalConfig()
|
|
26
|
+
: loadMergedConfig(options.dir);
|
|
27
|
+
if (key) {
|
|
28
|
+
const normalizedKey = key === "path" || key === "mojang-path" ? "mojangPath" : key;
|
|
29
|
+
const val = conf[normalizedKey];
|
|
30
|
+
if (val !== undefined) {
|
|
31
|
+
console.log(val);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
logger.info(`Key '${key}' is not set.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
logger.info("Current configuration:");
|
|
39
|
+
console.log(JSON.stringify(conf, null, 2));
|
|
40
|
+
logger.info(`Local config file: ${getLocalConfigPath(options.dir)}`);
|
|
41
|
+
logger.info(`Global config file: ${getGlobalConfigPath()}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function setPathCommand(inputPath, options = {}) {
|
|
45
|
+
await setConfigCommand("mojangPath", inputPath, options);
|
|
46
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chokidar from "chokidar";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { buildCommand } from "./build.js";
|
|
6
|
+
import { findMojangPath, linkPack } from "../utils/mojang.js";
|
|
7
|
+
import { saveConfig } from "../utils/config.js";
|
|
8
|
+
import { BedrockHotReloadServer } from "../utils/wsserver.js";
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
10
|
+
import { validateManifest } from "../utils/manifest.js";
|
|
11
|
+
export async function devCommand(dirArg, options = {}) {
|
|
12
|
+
const rootDir = path.resolve(dirArg || ".");
|
|
13
|
+
const bpDir = path.join(rootDir, "behavior_pack");
|
|
14
|
+
const rpDir = path.join(rootDir, "resource_pack");
|
|
15
|
+
const hasBp = fs.existsSync(bpDir);
|
|
16
|
+
const hasRp = fs.existsSync(rpDir);
|
|
17
|
+
if (!hasBp && !hasRp) {
|
|
18
|
+
logger.error(`No behavior_pack or resource_pack found in ${rootDir}. Run 'bdev init' first.`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const folderName = path.basename(rootDir);
|
|
22
|
+
// If --save and path provided, save to bdev.json
|
|
23
|
+
if (options.save && options.path) {
|
|
24
|
+
const resolvedPath = path.resolve(options.path);
|
|
25
|
+
saveConfig({ mojangPath: resolvedPath }, { dir: rootDir });
|
|
26
|
+
logger.success(`Saved mojang path to bdev.json: ${resolvedPath}`);
|
|
27
|
+
}
|
|
28
|
+
// 1. Initial build
|
|
29
|
+
logger.info("Executing initial build...");
|
|
30
|
+
const initialBuildOk = await buildCommand(rootDir);
|
|
31
|
+
if (!initialBuildOk) {
|
|
32
|
+
logger.warn("Initial build had warnings or errors. Continuing dev server...");
|
|
33
|
+
}
|
|
34
|
+
// 2. Setup symlinks to com.mojang
|
|
35
|
+
const shouldLink = options.link !== false;
|
|
36
|
+
if (shouldLink) {
|
|
37
|
+
const mojang = findMojangPath(options.path, rootDir);
|
|
38
|
+
if (!mojang) {
|
|
39
|
+
logger.warn("Minecraft com.mojang directory not found.");
|
|
40
|
+
logger.warn("Tip: Set with 'bdev set-path <path>' or pass '-p <path>' or 'BEDROCK_MOJANG_PATH'.");
|
|
41
|
+
logger.warn("Continuing in watch-only mode (without symlinking).");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
logger.info(`Found com.mojang location (${mojang.type}): ${mojang.root}`);
|
|
45
|
+
if (hasBp) {
|
|
46
|
+
const destBp = path.join(mojang.devBehaviorPacks, `${folderName}_BP`);
|
|
47
|
+
try {
|
|
48
|
+
linkPack(bpDir, destBp);
|
|
49
|
+
logger.success(`Linked Behavior Pack -> ${destBp}`);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.error(`Failed to link Behavior Pack: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (hasRp) {
|
|
56
|
+
const destRp = path.join(mojang.devResourcePacks, `${folderName}_RP`);
|
|
57
|
+
try {
|
|
58
|
+
linkPack(rpDir, destRp);
|
|
59
|
+
logger.success(`Linked Resource Pack -> ${destRp}`);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logger.error(`Failed to link Resource Pack: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 3. Start WebSocket Hot Reload server
|
|
68
|
+
const enableWs = options.ws !== false;
|
|
69
|
+
let hotReloadServer = null;
|
|
70
|
+
let wsPort = options.port ? Number(options.port) : 8000;
|
|
71
|
+
if (enableWs) {
|
|
72
|
+
try {
|
|
73
|
+
hotReloadServer = new BedrockHotReloadServer(wsPort);
|
|
74
|
+
wsPort = await hotReloadServer.start();
|
|
75
|
+
console.log();
|
|
76
|
+
logger.info(pc.bold(`Hot Reload WebSocket server active on ${pc.cyan(`ws://localhost:${wsPort}`)}`));
|
|
77
|
+
logger.info(pc.yellow(`To connect in Minecraft (cheats on): ${pc.bold(`/connect localhost:${wsPort}`)}`));
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
logger.error(`Failed to start WebSocket hot reload server: ${err.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 4. Start chokidar watcher
|
|
85
|
+
const watchPaths = [];
|
|
86
|
+
const srcDir = path.join(rootDir, "src");
|
|
87
|
+
if (fs.existsSync(srcDir)) {
|
|
88
|
+
watchPaths.push(srcDir);
|
|
89
|
+
}
|
|
90
|
+
if (hasBp) {
|
|
91
|
+
watchPaths.push(bpDir);
|
|
92
|
+
}
|
|
93
|
+
if (hasRp) {
|
|
94
|
+
watchPaths.push(rpDir);
|
|
95
|
+
}
|
|
96
|
+
logger.info(`Starting file watcher on: ${watchPaths.map((p) => path.relative(rootDir, p)).join(", ")}`);
|
|
97
|
+
logger.info("Press Ctrl+C to stop.");
|
|
98
|
+
let isRebuilding = false;
|
|
99
|
+
let rebuildTimeout = null;
|
|
100
|
+
const watcher = chokidar.watch(watchPaths, {
|
|
101
|
+
ignored: [
|
|
102
|
+
/(^|[/\\])\../, // dotfiles
|
|
103
|
+
/node_modules/,
|
|
104
|
+
/dist/,
|
|
105
|
+
/behavior_pack[/\\]scripts[/\\]main\.js(\.map)?$/, // ignore build output
|
|
106
|
+
],
|
|
107
|
+
persistent: true,
|
|
108
|
+
ignoreInitial: true,
|
|
109
|
+
});
|
|
110
|
+
const triggerRebuild = (filePath) => {
|
|
111
|
+
const rel = path.relative(rootDir, filePath);
|
|
112
|
+
logger.info(`Change detected in ${rel}`);
|
|
113
|
+
if (rebuildTimeout) {
|
|
114
|
+
clearTimeout(rebuildTimeout);
|
|
115
|
+
}
|
|
116
|
+
rebuildTimeout = setTimeout(async () => {
|
|
117
|
+
if (isRebuilding)
|
|
118
|
+
return;
|
|
119
|
+
isRebuilding = true;
|
|
120
|
+
try {
|
|
121
|
+
if (filePath.endsWith("manifest.json")) {
|
|
122
|
+
const res = validateManifest(filePath);
|
|
123
|
+
if (res.valid) {
|
|
124
|
+
logger.success(`Validated ${rel}`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
logger.error(`Invalid manifest in ${rel}:`);
|
|
128
|
+
res.errors.forEach((e) => console.error(` - ${e}`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Run build if src changed or if script output needed
|
|
132
|
+
if (fs.existsSync(srcDir)) {
|
|
133
|
+
const buildOk = await buildCommand(rootDir);
|
|
134
|
+
if (buildOk && hotReloadServer) {
|
|
135
|
+
if (hotReloadServer.clientCount > 0) {
|
|
136
|
+
hotReloadServer.reload();
|
|
137
|
+
logger.success("Sent /reload to Minecraft via WebSocket");
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
logger.info(`Hot reload skipped (no Minecraft client connected; run /connect localhost:${wsPort} in game)`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
logger.error(`Error during rebuild: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
isRebuilding = false;
|
|
150
|
+
}
|
|
151
|
+
}, 100);
|
|
152
|
+
};
|
|
153
|
+
watcher.on("change", triggerRebuild);
|
|
154
|
+
watcher.on("add", triggerRebuild);
|
|
155
|
+
watcher.on("unlink", triggerRebuild);
|
|
156
|
+
// Handle graceful exit
|
|
157
|
+
process.on("SIGINT", async () => {
|
|
158
|
+
logger.info("Stopping dev server and watcher...");
|
|
159
|
+
await watcher.close();
|
|
160
|
+
if (hotReloadServer) {
|
|
161
|
+
await hotReloadServer.stop();
|
|
162
|
+
}
|
|
163
|
+
process.exit(0);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doctorCommand(customPath?: string): Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
import { findMojangPath } from "../utils/mojang.js";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
function checkCommand(cmd) {
|
|
7
|
+
try {
|
|
8
|
+
const out = execSync(cmd, { stdio: ["pipe", "pipe", "pipe"], encoding: "utf-8" });
|
|
9
|
+
return out.trim();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function doctorCommand(customPath) {
|
|
16
|
+
console.log(pc.bold("\n Bedrock Dev (bdev) Environment Doctor\n"));
|
|
17
|
+
const checks = [];
|
|
18
|
+
// OS Info
|
|
19
|
+
const platform = os.platform();
|
|
20
|
+
const release = os.release();
|
|
21
|
+
checks.push({
|
|
22
|
+
name: "Operating System",
|
|
23
|
+
ok: true,
|
|
24
|
+
details: `${platform} (${release})`,
|
|
25
|
+
});
|
|
26
|
+
// Node.js
|
|
27
|
+
const nodeVersion = process.version;
|
|
28
|
+
checks.push({
|
|
29
|
+
name: "Node.js Runtime",
|
|
30
|
+
ok: Boolean(nodeVersion),
|
|
31
|
+
details: nodeVersion || "Not found",
|
|
32
|
+
});
|
|
33
|
+
// npm
|
|
34
|
+
const npmVersion = checkCommand("npm --version");
|
|
35
|
+
checks.push({
|
|
36
|
+
name: "npm Package Manager",
|
|
37
|
+
ok: Boolean(npmVersion),
|
|
38
|
+
details: npmVersion ? `v${npmVersion}` : "Not found in PATH",
|
|
39
|
+
});
|
|
40
|
+
// Bun
|
|
41
|
+
const bunVersion = checkCommand("bun --version");
|
|
42
|
+
checks.push({
|
|
43
|
+
name: "Bun Runtime",
|
|
44
|
+
ok: Boolean(bunVersion),
|
|
45
|
+
details: bunVersion ? `v${bunVersion}` : "Not found in PATH (optional)",
|
|
46
|
+
});
|
|
47
|
+
// Minecraft Bedrock com.mojang folder
|
|
48
|
+
const mojang = findMojangPath(customPath);
|
|
49
|
+
if (mojang) {
|
|
50
|
+
checks.push({
|
|
51
|
+
name: "Minecraft Bedrock (com.mojang)",
|
|
52
|
+
ok: true,
|
|
53
|
+
details: `Found (${mojang.type}): ${mojang.root}`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
checks.push({
|
|
58
|
+
name: "Minecraft Bedrock (com.mojang)",
|
|
59
|
+
ok: false,
|
|
60
|
+
details: "Directory not found. Expected in Windows UWP LocalAppData or specify via BEDROCK_MOJANG_PATH",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Display results
|
|
64
|
+
let allPassed = true;
|
|
65
|
+
for (const item of checks) {
|
|
66
|
+
const symbol = item.ok ? pc.green("✔") : pc.red("✖");
|
|
67
|
+
const nameStr = pc.bold(item.name.padEnd(32));
|
|
68
|
+
console.log(` ${symbol} ${nameStr} ${item.details}`);
|
|
69
|
+
if (!item.ok && item.name !== "Minecraft Bedrock (com.mojang)") {
|
|
70
|
+
allPassed = false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
console.log();
|
|
74
|
+
if (!mojang) {
|
|
75
|
+
logger.warn("Notice: com.mojang was not detected automatically. If you develop on Windows or use a custom path, export BEDROCK_MOJANG_PATH='/path/to/com.mojang'.");
|
|
76
|
+
}
|
|
77
|
+
if (allPassed) {
|
|
78
|
+
logger.success("Core development environment is ready!");
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
logger.warn("Some environment checks did not pass. Review the items marked with ✖ above.");
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
}
|