@mafly/bdev-cli 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/build.js +7 -1
- package/dist/commands/dev.js +89 -44
- package/dist/commands/init.js +44 -17
- package/dist/utils/manifest.d.ts +8 -0
- package/dist/utils/manifest.js +235 -1
- package/dist/utils/npm.d.ts +2 -0
- package/dist/utils/npm.js +35 -0
- package/dist/utils/wsserver.js +9 -3
- package/package.json +1 -1
package/dist/commands/build.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { runBuild } from "../utils/bundler.js";
|
|
4
|
-
import { validateManifest } from "../utils/manifest.js";
|
|
4
|
+
import { validateManifest, syncManifestDependencies, addResourcePack, } from "../utils/manifest.js";
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
export async function buildCommand(dirArg, options = {}) {
|
|
7
7
|
const rootDir = path.resolve(dirArg || ".");
|
|
8
8
|
const bpDir = path.join(rootDir, "behavior_pack");
|
|
9
9
|
const bpManifestPath = path.join(bpDir, "manifest.json");
|
|
10
|
+
// Sync @minecraft/* dependencies from package.json into manifest.json
|
|
11
|
+
syncManifestDependencies(rootDir);
|
|
10
12
|
// 1. Validate Behavior Pack Manifest
|
|
11
13
|
if (!fs.existsSync(bpManifestPath)) {
|
|
12
14
|
logger.error(`Behavior pack manifest not found: ${bpManifestPath}`);
|
|
@@ -25,6 +27,10 @@ export async function buildCommand(dirArg, options = {}) {
|
|
|
25
27
|
// Validate Resource Pack Manifest if exists
|
|
26
28
|
const rpDir = path.join(rootDir, "resource_pack");
|
|
27
29
|
const rpManifestPath = path.join(rpDir, "manifest.json");
|
|
30
|
+
if (fs.existsSync(rpDir) && !fs.existsSync(rpManifestPath)) {
|
|
31
|
+
logger.info("Found resource_pack folder without manifest. Initializing Resource Pack...");
|
|
32
|
+
addResourcePack(rootDir);
|
|
33
|
+
}
|
|
28
34
|
if (fs.existsSync(rpManifestPath)) {
|
|
29
35
|
const rpValidation = validateManifest(rpManifestPath);
|
|
30
36
|
if (!rpValidation.valid) {
|
package/dist/commands/dev.js
CHANGED
|
@@ -7,13 +7,13 @@ import { findMojangPath, linkPack } from "../utils/mojang.js";
|
|
|
7
7
|
import { saveConfig } from "../utils/config.js";
|
|
8
8
|
import { BedrockHotReloadServer } from "../utils/wsserver.js";
|
|
9
9
|
import { logger } from "../utils/logger.js";
|
|
10
|
-
import { validateManifest } from "../utils/manifest.js";
|
|
10
|
+
import { validateManifest, getLastManifestSyncTime } from "../utils/manifest.js";
|
|
11
11
|
export async function devCommand(dirArg, options = {}) {
|
|
12
12
|
const rootDir = path.resolve(dirArg || ".");
|
|
13
13
|
const bpDir = path.join(rootDir, "behavior_pack");
|
|
14
14
|
const rpDir = path.join(rootDir, "resource_pack");
|
|
15
15
|
const hasBp = fs.existsSync(bpDir);
|
|
16
|
-
|
|
16
|
+
let hasRp = fs.existsSync(rpDir);
|
|
17
17
|
if (!hasBp && !hasRp) {
|
|
18
18
|
logger.error(`No behavior_pack or resource_pack found in ${rootDir}. Run 'bdev init' first.`);
|
|
19
19
|
process.exit(1);
|
|
@@ -33,8 +33,8 @@ export async function devCommand(dirArg, options = {}) {
|
|
|
33
33
|
}
|
|
34
34
|
// 2. Setup symlinks to com.mojang
|
|
35
35
|
const shouldLink = options.link !== false;
|
|
36
|
+
const mojang = shouldLink ? findMojangPath(options.path, rootDir) : null;
|
|
36
37
|
if (shouldLink) {
|
|
37
|
-
const mojang = findMojangPath(options.path, rootDir);
|
|
38
38
|
if (!mojang) {
|
|
39
39
|
logger.warn("Minecraft com.mojang directory not found.");
|
|
40
40
|
logger.warn("Tip: Set with 'bdev set-path <path>' or pass '-p <path>' or 'BEDROCK_MOJANG_PATH'.");
|
|
@@ -93,62 +93,107 @@ export async function devCommand(dirArg, options = {}) {
|
|
|
93
93
|
if (hasRp) {
|
|
94
94
|
watchPaths.push(rpDir);
|
|
95
95
|
}
|
|
96
|
+
const pkgJsonPath = path.join(rootDir, "package.json");
|
|
97
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
98
|
+
watchPaths.push(pkgJsonPath);
|
|
99
|
+
}
|
|
96
100
|
logger.info(`Starting file watcher on: ${watchPaths.map((p) => path.relative(rootDir, p)).join(", ")}`);
|
|
97
101
|
logger.info("Press Ctrl+C to stop.");
|
|
98
102
|
let isRebuilding = false;
|
|
103
|
+
let rebuildPending = false;
|
|
99
104
|
let rebuildTimeout = null;
|
|
105
|
+
const ignoredPatterns = [
|
|
106
|
+
/(^|[/\\])\../, // dotfiles
|
|
107
|
+
/node_modules/,
|
|
108
|
+
/dist/,
|
|
109
|
+
];
|
|
110
|
+
if (fs.existsSync(srcDir)) {
|
|
111
|
+
// Ignore entire scripts directory in behavior pack when compiling from src
|
|
112
|
+
ignoredPatterns.push(/(^|[/\\])behavior_pack[/\\]scripts([/\\].*)?$/);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
ignoredPatterns.push(/\.map$/);
|
|
116
|
+
}
|
|
100
117
|
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
|
-
],
|
|
118
|
+
ignored: ignoredPatterns,
|
|
107
119
|
persistent: true,
|
|
108
120
|
ignoreInitial: true,
|
|
109
121
|
});
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
logger.success(`
|
|
122
|
+
const runRebuildCycle = async (triggerPath) => {
|
|
123
|
+
isRebuilding = true;
|
|
124
|
+
rebuildPending = false;
|
|
125
|
+
const rel = path.relative(rootDir, triggerPath);
|
|
126
|
+
try {
|
|
127
|
+
// If resource_pack was added while dev server is running
|
|
128
|
+
if (!hasRp && fs.existsSync(rpDir)) {
|
|
129
|
+
hasRp = true;
|
|
130
|
+
watcher.add(rpDir);
|
|
131
|
+
logger.info("Detected new Resource Pack. Watching resource_pack...");
|
|
132
|
+
if (shouldLink && mojang) {
|
|
133
|
+
const destRp = path.join(mojang.devResourcePacks, `${folderName}_RP`);
|
|
134
|
+
try {
|
|
135
|
+
linkPack(rpDir, destRp);
|
|
136
|
+
logger.success(`Linked Resource Pack -> ${destRp}`);
|
|
125
137
|
}
|
|
126
|
-
|
|
127
|
-
logger.error(`
|
|
128
|
-
res.errors.forEach((e) => console.error(` - ${e}`));
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger.error(`Failed to link Resource Pack: ${err.message}`);
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
logger.info(`Hot reload skipped (no Minecraft client connected; run /connect localhost:${wsPort} in game)`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
142
|
+
}
|
|
143
|
+
if (triggerPath.endsWith("manifest.json")) {
|
|
144
|
+
const res = validateManifest(triggerPath);
|
|
145
|
+
if (res.valid) {
|
|
146
|
+
logger.success(`Validated ${rel}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
logger.error(`Invalid manifest in ${rel}:`);
|
|
150
|
+
res.errors.forEach((e) => console.error(` - ${e}`));
|
|
143
151
|
}
|
|
144
152
|
}
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
// Run build if src changed or if script output needed
|
|
154
|
+
if (fs.existsSync(srcDir)) {
|
|
155
|
+
const buildOk = await buildCommand(rootDir);
|
|
156
|
+
if (buildOk && hotReloadServer) {
|
|
157
|
+
if (hotReloadServer.clientCount > 0) {
|
|
158
|
+
// Buffer 150ms before reload command so Proton/Wine file cache flushes
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
160
|
+
hotReloadServer.reload();
|
|
161
|
+
logger.success("Sent /reload to Minecraft via WebSocket");
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
logger.info(`Hot reload skipped (no Minecraft client connected; run /connect localhost:${wsPort} in game)`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
147
167
|
}
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
logger.error(`Error during rebuild: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
finally {
|
|
173
|
+
isRebuilding = false;
|
|
174
|
+
if (rebuildPending) {
|
|
175
|
+
runRebuildCycle(triggerPath);
|
|
150
176
|
}
|
|
151
|
-
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const triggerRebuild = (filePath) => {
|
|
180
|
+
// Ignore internal writes to manifest.json caused by syncManifestDependencies
|
|
181
|
+
if (filePath.endsWith("manifest.json") &&
|
|
182
|
+
Date.now() - getLastManifestSyncTime() < 600) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const rel = path.relative(rootDir, filePath);
|
|
186
|
+
logger.info(`Change detected in ${rel}`);
|
|
187
|
+
if (isRebuilding) {
|
|
188
|
+
rebuildPending = true;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (rebuildTimeout) {
|
|
192
|
+
clearTimeout(rebuildTimeout);
|
|
193
|
+
}
|
|
194
|
+
rebuildTimeout = setTimeout(() => {
|
|
195
|
+
runRebuildCycle(filePath);
|
|
196
|
+
}, 300);
|
|
152
197
|
};
|
|
153
198
|
watcher.on("change", triggerRebuild);
|
|
154
199
|
watcher.on("add", triggerRebuild);
|
package/dist/commands/init.js
CHANGED
|
@@ -1,28 +1,55 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { generateManifests } from "../utils/manifest.js";
|
|
3
|
+
import { generateManifests, addResourcePack, DEFAULT_ICON_BASE64, } from "../utils/manifest.js";
|
|
4
4
|
import { saveConfig } from "../utils/config.js";
|
|
5
|
+
import { resolveMinecraftDependencies } from "../utils/npm.js";
|
|
5
6
|
import { logger } from "../utils/logger.js";
|
|
6
|
-
const DEFAULT_ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
|
7
7
|
export async function initCommand(targetDirArg, options) {
|
|
8
8
|
const targetDir = path.resolve(targetDirArg || ".");
|
|
9
9
|
const projectName = options.name || path.basename(targetDir) || "my-bedrock-addon";
|
|
10
10
|
const projectDesc = options.desc || `${projectName} - Bedrock Addon created with bdev`;
|
|
11
11
|
const isTs = options.lang !== "js";
|
|
12
12
|
const hasRp = options.rp !== false;
|
|
13
|
+
const existingBpManifest = path.join(targetDir, "behavior_pack", "manifest.json");
|
|
14
|
+
if (fs.existsSync(existingBpManifest)) {
|
|
15
|
+
if (hasRp) {
|
|
16
|
+
const existingRpManifest = path.join(targetDir, "resource_pack", "manifest.json");
|
|
17
|
+
if (!fs.existsSync(existingRpManifest)) {
|
|
18
|
+
logger.info("Existing Behavior Pack project detected without Resource Pack.");
|
|
19
|
+
addResourcePack(targetDir, { name: projectName, description: projectDesc });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
logger.warn("Project already initialized with Behavior Pack and Resource Pack.");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
logger.warn("Project is already initialized.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
13
32
|
logger.info(`Scaffolding Bedrock Addon project: ${projectName}`);
|
|
14
33
|
logger.info(`Directory: ${targetDir}`);
|
|
15
34
|
logger.info(`Language: ${isTs ? "TypeScript" : "JavaScript"} | Resource Pack: ${hasRp ? "Yes" : "No"}`);
|
|
16
35
|
if (!fs.existsSync(targetDir)) {
|
|
17
36
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
18
37
|
}
|
|
38
|
+
const spinner = logger.spinner("Fetching latest @minecraft/* versions from npm...");
|
|
39
|
+
const mcVersions = await resolveMinecraftDependencies([
|
|
40
|
+
"@minecraft/server",
|
|
41
|
+
"@minecraft/server-ui",
|
|
42
|
+
]);
|
|
43
|
+
const serverVersion = mcVersions["@minecraft/server"] || "2.11.0";
|
|
44
|
+
const serverUiVersion = mcVersions["@minecraft/server-ui"] || "2.3.0";
|
|
45
|
+
spinner.succeed(`Resolved dependencies: @minecraft/server@${serverVersion}, @minecraft/server-ui@${serverUiVersion}`);
|
|
19
46
|
const { bpManifest, rpManifest } = generateManifests({
|
|
20
47
|
name: projectName,
|
|
21
48
|
description: projectDesc,
|
|
22
49
|
hasScript: true,
|
|
23
50
|
hasRp,
|
|
24
51
|
minEngineVersion: [1, 21, 0],
|
|
25
|
-
serverVersion
|
|
52
|
+
serverVersion,
|
|
26
53
|
});
|
|
27
54
|
// 1. Behavior Pack
|
|
28
55
|
const bpDir = path.join(targetDir, "behavior_pack");
|
|
@@ -40,24 +67,24 @@ export async function initCommand(targetDirArg, options) {
|
|
|
40
67
|
fs.writeFileSync(path.join(textsDir, "en_US.lang"), `pack.name=${projectName}\npack.description=${projectDesc}\n`);
|
|
41
68
|
}
|
|
42
69
|
// 3. Scripts / Source code
|
|
43
|
-
const
|
|
70
|
+
const starterCodeTs = `import { world, type Player } from "@minecraft/server";
|
|
44
71
|
|
|
45
|
-
world.afterEvents.
|
|
46
|
-
|
|
72
|
+
world.afterEvents.playerSpawn.subscribe((e) => {
|
|
73
|
+
const player: Player = e.player;
|
|
74
|
+
player.sendMessage("Welcome!");
|
|
47
75
|
});
|
|
76
|
+
`;
|
|
77
|
+
const starterCodeJs = `import { world } from "@minecraft/server";
|
|
48
78
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Periodic tick logic
|
|
54
|
-
}
|
|
55
|
-
}, 20);
|
|
79
|
+
world.afterEvents.playerSpawn.subscribe((e) => {
|
|
80
|
+
const player = e.player;
|
|
81
|
+
player.sendMessage("Welcome!");
|
|
82
|
+
});
|
|
56
83
|
`;
|
|
57
84
|
if (isTs) {
|
|
58
85
|
const srcDir = path.join(targetDir, "src");
|
|
59
86
|
fs.mkdirSync(srcDir, { recursive: true });
|
|
60
|
-
fs.writeFileSync(path.join(srcDir, "main.ts"),
|
|
87
|
+
fs.writeFileSync(path.join(srcDir, "main.ts"), starterCodeTs);
|
|
61
88
|
// tsconfig.json
|
|
62
89
|
const tsconfig = {
|
|
63
90
|
compilerOptions: {
|
|
@@ -75,7 +102,7 @@ system.runInterval(() => {
|
|
|
75
102
|
else {
|
|
76
103
|
const scriptsDir = path.join(bpDir, "scripts");
|
|
77
104
|
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
78
|
-
fs.writeFileSync(path.join(scriptsDir, "main.js"),
|
|
105
|
+
fs.writeFileSync(path.join(scriptsDir, "main.js"), starterCodeJs);
|
|
79
106
|
}
|
|
80
107
|
// 4. package.json
|
|
81
108
|
const pkgJson = {
|
|
@@ -89,8 +116,8 @@ system.runInterval(() => {
|
|
|
89
116
|
package: "bdev package",
|
|
90
117
|
},
|
|
91
118
|
dependencies: {
|
|
92
|
-
"@minecraft/server":
|
|
93
|
-
"@minecraft/server-ui":
|
|
119
|
+
"@minecraft/server": serverVersion,
|
|
120
|
+
"@minecraft/server-ui": serverUiVersion,
|
|
94
121
|
},
|
|
95
122
|
};
|
|
96
123
|
if (isTs) {
|
package/dist/utils/manifest.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const DEFAULT_ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
|
1
2
|
export interface ManifestHeader {
|
|
2
3
|
name: string;
|
|
3
4
|
description: string;
|
|
@@ -40,3 +41,10 @@ export declare function generateManifests(opts: {
|
|
|
40
41
|
bpManifest: BedrockManifest;
|
|
41
42
|
rpManifest?: BedrockManifest;
|
|
42
43
|
};
|
|
44
|
+
export interface AddResourcePackOptions {
|
|
45
|
+
name?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function addResourcePack(rootDir: string, options?: AddResourcePackOptions): boolean;
|
|
49
|
+
export declare function getLastManifestSyncTime(): number;
|
|
50
|
+
export declare function syncManifestDependencies(rootDir: string): boolean;
|
package/dist/utils/manifest.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { v4 as uuidv4, validate as validateUuid } from "uuid";
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
export const DEFAULT_ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
|
3
6
|
export function validateManifest(manifestPath) {
|
|
4
7
|
const errors = [];
|
|
5
8
|
if (!fs.existsSync(manifestPath)) {
|
|
@@ -100,7 +103,7 @@ export function generateManifests(opts) {
|
|
|
100
103
|
if (opts.hasScript) {
|
|
101
104
|
bpDependencies.push({
|
|
102
105
|
module_name: "@minecraft/server",
|
|
103
|
-
version: opts.serverVersion || "
|
|
106
|
+
version: opts.serverVersion || "2.11.0",
|
|
104
107
|
});
|
|
105
108
|
}
|
|
106
109
|
if (opts.hasRp) {
|
|
@@ -144,3 +147,234 @@ export function generateManifests(opts) {
|
|
|
144
147
|
}
|
|
145
148
|
return { bpManifest, rpManifest };
|
|
146
149
|
}
|
|
150
|
+
export function addResourcePack(rootDir, options = {}) {
|
|
151
|
+
const bpDir = path.join(rootDir, "behavior_pack");
|
|
152
|
+
const bpManifestPath = path.join(bpDir, "manifest.json");
|
|
153
|
+
if (!fs.existsSync(bpManifestPath)) {
|
|
154
|
+
logger.error(`Behavior pack manifest not found in ${rootDir}. Run 'bdev init' first.`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const rpDir = path.join(rootDir, "resource_pack");
|
|
158
|
+
const rpManifestPath = path.join(rpDir, "manifest.json");
|
|
159
|
+
let bpManifest;
|
|
160
|
+
try {
|
|
161
|
+
bpManifest = JSON.parse(fs.readFileSync(bpManifestPath, "utf-8"));
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
logger.error(`Failed to parse ${bpManifestPath}: ${err.message}`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (fs.existsSync(rpManifestPath)) {
|
|
168
|
+
try {
|
|
169
|
+
const rpManifest = JSON.parse(fs.readFileSync(rpManifestPath, "utf-8"));
|
|
170
|
+
const rpUuid = rpManifest?.header?.uuid;
|
|
171
|
+
if (rpUuid) {
|
|
172
|
+
if (!Array.isArray(bpManifest.dependencies)) {
|
|
173
|
+
bpManifest.dependencies = [];
|
|
174
|
+
}
|
|
175
|
+
const hasDep = bpManifest.dependencies.some((d) => d.uuid?.toLowerCase() === rpUuid.toLowerCase());
|
|
176
|
+
if (!hasDep) {
|
|
177
|
+
bpManifest.dependencies.push({
|
|
178
|
+
uuid: rpUuid,
|
|
179
|
+
version: rpManifest.header?.version || [1, 0, 0],
|
|
180
|
+
});
|
|
181
|
+
fs.writeFileSync(bpManifestPath, JSON.stringify(bpManifest, null, 2) + "\n");
|
|
182
|
+
logger.success("Linked existing Resource Pack to Behavior Pack manifest.");
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// ignore
|
|
189
|
+
}
|
|
190
|
+
logger.warn("Resource pack already exists in this project.");
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
let projectName = options.name;
|
|
194
|
+
let projectDesc = options.description;
|
|
195
|
+
const pkgJsonPath = path.join(rootDir, "package.json");
|
|
196
|
+
if ((!projectName || !projectDesc) && fs.existsSync(pkgJsonPath)) {
|
|
197
|
+
try {
|
|
198
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
199
|
+
if (!projectName && pkg.name)
|
|
200
|
+
projectName = pkg.name;
|
|
201
|
+
if (!projectDesc && pkg.description)
|
|
202
|
+
projectDesc = pkg.description;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// ignore
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (!projectName) {
|
|
209
|
+
projectName = (bpManifest.header?.name || path.basename(rootDir)).replace(/\s*\(BP\)$/i, "");
|
|
210
|
+
}
|
|
211
|
+
if (!projectDesc) {
|
|
212
|
+
projectDesc = bpManifest.header?.description || `${projectName} Resource Pack`;
|
|
213
|
+
}
|
|
214
|
+
const minEngineVersion = bpManifest.header?.min_engine_version || [1, 21, 0];
|
|
215
|
+
const rpHeaderUuid = uuidv4();
|
|
216
|
+
const rpModuleUuid = uuidv4();
|
|
217
|
+
const rpManifest = {
|
|
218
|
+
format_version: 2,
|
|
219
|
+
header: {
|
|
220
|
+
name: `${projectName} (RP)`,
|
|
221
|
+
description: projectDesc,
|
|
222
|
+
uuid: rpHeaderUuid,
|
|
223
|
+
version: [1, 0, 0],
|
|
224
|
+
min_engine_version: minEngineVersion,
|
|
225
|
+
},
|
|
226
|
+
modules: [
|
|
227
|
+
{
|
|
228
|
+
description: "Resource Pack Modules",
|
|
229
|
+
type: "resources",
|
|
230
|
+
uuid: rpModuleUuid,
|
|
231
|
+
version: [1, 0, 0],
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
fs.mkdirSync(rpDir, { recursive: true });
|
|
236
|
+
fs.writeFileSync(rpManifestPath, JSON.stringify(rpManifest, null, 2) + "\n");
|
|
237
|
+
const bpIconPath = path.join(bpDir, "pack_icon.png");
|
|
238
|
+
const rpIconPath = path.join(rpDir, "pack_icon.png");
|
|
239
|
+
if (fs.existsSync(bpIconPath)) {
|
|
240
|
+
fs.copyFileSync(bpIconPath, rpIconPath);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
fs.writeFileSync(rpIconPath, Buffer.from(DEFAULT_ICON_BASE64, "base64"));
|
|
244
|
+
}
|
|
245
|
+
const textsDir = path.join(rpDir, "texts");
|
|
246
|
+
fs.mkdirSync(textsDir, { recursive: true });
|
|
247
|
+
fs.writeFileSync(path.join(textsDir, "en_US.lang"), `pack.name=${projectName}\npack.description=${projectDesc}\n`);
|
|
248
|
+
if (!Array.isArray(bpManifest.dependencies)) {
|
|
249
|
+
bpManifest.dependencies = [];
|
|
250
|
+
}
|
|
251
|
+
const existingRpIndex = bpManifest.dependencies.findIndex((d) => d.uuid && !d.module_name);
|
|
252
|
+
if (existingRpIndex !== -1) {
|
|
253
|
+
bpManifest.dependencies[existingRpIndex] = {
|
|
254
|
+
uuid: rpHeaderUuid,
|
|
255
|
+
version: [1, 0, 0],
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
bpManifest.dependencies.push({
|
|
260
|
+
uuid: rpHeaderUuid,
|
|
261
|
+
version: [1, 0, 0],
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
fs.writeFileSync(bpManifestPath, JSON.stringify(bpManifest, null, 2) + "\n");
|
|
265
|
+
logger.success(`Resource Pack created and linked to Behavior Pack at ${rpDir}`);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
let lastManifestSyncTime = 0;
|
|
269
|
+
export function getLastManifestSyncTime() {
|
|
270
|
+
return lastManifestSyncTime;
|
|
271
|
+
}
|
|
272
|
+
export function syncManifestDependencies(rootDir) {
|
|
273
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
274
|
+
const bpManifestPath = path.join(rootDir, "behavior_pack", "manifest.json");
|
|
275
|
+
if (!fs.existsSync(pkgPath) || !fs.existsSync(bpManifestPath)) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
let pkg;
|
|
279
|
+
let bpManifest;
|
|
280
|
+
try {
|
|
281
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
282
|
+
bpManifest = JSON.parse(fs.readFileSync(bpManifestPath, "utf-8"));
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const targetDeps = new Map();
|
|
288
|
+
if (pkg.devDependencies && typeof pkg.devDependencies === "object") {
|
|
289
|
+
for (const [name, ver] of Object.entries(pkg.devDependencies)) {
|
|
290
|
+
if (name.startsWith("@minecraft/") && typeof ver === "string") {
|
|
291
|
+
targetDeps.set(name, ver.replace(/^[~^>=v\s]+/, ""));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (pkg.dependencies && typeof pkg.dependencies === "object") {
|
|
296
|
+
for (const [name, ver] of Object.entries(pkg.dependencies)) {
|
|
297
|
+
if (name.startsWith("@minecraft/") && typeof ver === "string") {
|
|
298
|
+
targetDeps.set(name, ver.replace(/^[~^>=v\s]+/, ""));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (!Array.isArray(bpManifest.dependencies)) {
|
|
303
|
+
bpManifest.dependencies = [];
|
|
304
|
+
}
|
|
305
|
+
let modified = false;
|
|
306
|
+
const updatedModules = [];
|
|
307
|
+
// 1. Update existing @minecraft/* entries or prune removed ones
|
|
308
|
+
const toRemoveIndices = [];
|
|
309
|
+
bpManifest.dependencies.forEach((dep, idx) => {
|
|
310
|
+
if (dep.module_name && dep.module_name.startsWith("@minecraft/")) {
|
|
311
|
+
const targetVer = targetDeps.get(dep.module_name);
|
|
312
|
+
if (targetVer) {
|
|
313
|
+
if (dep.version !== targetVer) {
|
|
314
|
+
dep.version = targetVer;
|
|
315
|
+
modified = true;
|
|
316
|
+
updatedModules.push(`${dep.module_name}@${targetVer}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (dep.module_name !== "@minecraft/server") {
|
|
320
|
+
// Removed from package.json
|
|
321
|
+
toRemoveIndices.push(idx);
|
|
322
|
+
modified = true;
|
|
323
|
+
updatedModules.push(`-${dep.module_name}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
if (toRemoveIndices.length > 0) {
|
|
328
|
+
bpManifest.dependencies = bpManifest.dependencies.filter((_, idx) => !toRemoveIndices.includes(idx));
|
|
329
|
+
}
|
|
330
|
+
// 2. Add new @minecraft/* dependencies from package.json that are missing in manifest
|
|
331
|
+
for (const [name, ver] of targetDeps.entries()) {
|
|
332
|
+
const exists = bpManifest.dependencies.some((d) => d.module_name === name);
|
|
333
|
+
if (!exists) {
|
|
334
|
+
bpManifest.dependencies.push({
|
|
335
|
+
module_name: name,
|
|
336
|
+
version: ver,
|
|
337
|
+
});
|
|
338
|
+
modified = true;
|
|
339
|
+
updatedModules.push(`+${name}@${ver}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// 3. Link resource pack if exists and not linked
|
|
343
|
+
const rpManifestPath = path.join(rootDir, "resource_pack", "manifest.json");
|
|
344
|
+
if (fs.existsSync(rpManifestPath)) {
|
|
345
|
+
try {
|
|
346
|
+
const rpManifest = JSON.parse(fs.readFileSync(rpManifestPath, "utf-8"));
|
|
347
|
+
const rpUuid = rpManifest?.header?.uuid;
|
|
348
|
+
if (rpUuid) {
|
|
349
|
+
const hasRpDep = bpManifest.dependencies.some((d) => d.uuid && d.uuid.toLowerCase() === rpUuid.toLowerCase());
|
|
350
|
+
if (!hasRpDep) {
|
|
351
|
+
const staleRpIndex = bpManifest.dependencies.findIndex((d) => d.uuid && !d.module_name);
|
|
352
|
+
if (staleRpIndex !== -1) {
|
|
353
|
+
bpManifest.dependencies[staleRpIndex] = {
|
|
354
|
+
uuid: rpUuid,
|
|
355
|
+
version: rpManifest.header?.version || [1, 0, 0],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
bpManifest.dependencies.push({
|
|
360
|
+
uuid: rpUuid,
|
|
361
|
+
version: rpManifest.header?.version || [1, 0, 0],
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
modified = true;
|
|
365
|
+
updatedModules.push(`linked RP (${rpUuid.slice(0, 8)}...)`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// ignore
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (modified) {
|
|
374
|
+
lastManifestSyncTime = Date.now();
|
|
375
|
+
fs.writeFileSync(bpManifestPath, JSON.stringify(bpManifest, null, 2) + "\n");
|
|
376
|
+
logger.success(`Synchronized manifest.json dependencies: ${updatedModules.join(", ")}`);
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const DEFAULT_FALLBACKS = {
|
|
2
|
+
"@minecraft/server": "2.11.0",
|
|
3
|
+
"@minecraft/server-ui": "2.3.0",
|
|
4
|
+
"@minecraft/server-admin": "1.0.0-beta",
|
|
5
|
+
"@minecraft/server-gametest": "1.0.0-beta",
|
|
6
|
+
"@minecraft/server-net": "1.0.0-beta",
|
|
7
|
+
};
|
|
8
|
+
export async function fetchLatestNpmVersion(pkgName, fallback) {
|
|
9
|
+
const defaultFallback = fallback || DEFAULT_FALLBACKS[pkgName] || "latest";
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
12
|
+
headers: {
|
|
13
|
+
Accept: "application/json",
|
|
14
|
+
},
|
|
15
|
+
signal: AbortSignal.timeout(4000),
|
|
16
|
+
});
|
|
17
|
+
if (res.ok) {
|
|
18
|
+
const data = (await res.json());
|
|
19
|
+
if (data?.version) {
|
|
20
|
+
return data.version;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Offline or network timeout, return fallback
|
|
26
|
+
}
|
|
27
|
+
return defaultFallback;
|
|
28
|
+
}
|
|
29
|
+
export async function resolveMinecraftDependencies(packages) {
|
|
30
|
+
const entries = await Promise.all(packages.map(async (pkg) => {
|
|
31
|
+
const ver = await fetchLatestNpmVersion(pkg);
|
|
32
|
+
return [pkg, ver];
|
|
33
|
+
}));
|
|
34
|
+
return Object.fromEntries(entries);
|
|
35
|
+
}
|
package/dist/utils/wsserver.js
CHANGED
|
@@ -51,8 +51,13 @@ export class BedrockHotReloadServer {
|
|
|
51
51
|
socket.on("message", (data) => {
|
|
52
52
|
try {
|
|
53
53
|
const res = JSON.parse(data.toString());
|
|
54
|
-
if (res?.
|
|
55
|
-
|
|
54
|
+
if (res?.header?.messagePurpose === "commandResponse") {
|
|
55
|
+
if (res.body?.statusCode && res.body.statusCode !== 0) {
|
|
56
|
+
logger.warn(`Minecraft command error (${res.body.statusCode}): ${res.body.statusMessage || "Error"}`);
|
|
57
|
+
}
|
|
58
|
+
else if (res.body?.statusMessage) {
|
|
59
|
+
logger.info(`Minecraft: ${res.body.statusMessage}`);
|
|
60
|
+
}
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
catch {
|
|
@@ -63,7 +68,8 @@ export class BedrockHotReloadServer {
|
|
|
63
68
|
this.clients.delete(socket);
|
|
64
69
|
logger.info("Minecraft disconnected from WebSocket.");
|
|
65
70
|
});
|
|
66
|
-
socket.on("error", () => {
|
|
71
|
+
socket.on("error", (err) => {
|
|
72
|
+
logger.warn(`WebSocket client error: ${err.message}`);
|
|
67
73
|
this.clients.delete(socket);
|
|
68
74
|
});
|
|
69
75
|
});
|