@mafly/bdev-cli 1.0.4 → 1.0.6
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 +64 -16
- package/dist/commands/init.js +20 -2
- package/dist/utils/manifest.d.ts +8 -0
- package/dist/utils/manifest.js +234 -0
- 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,11 +93,18 @@ 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;
|
|
99
|
-
let
|
|
103
|
+
let queuedFilePath = null;
|
|
100
104
|
let rebuildTimeout = null;
|
|
105
|
+
let lastReloadTime = 0;
|
|
106
|
+
const RELOAD_COOLDOWN_MS = 1200;
|
|
107
|
+
const DEBOUNCE_MS = 350;
|
|
101
108
|
const ignoredPatterns = [
|
|
102
109
|
/(^|[/\\])\../, // dotfiles
|
|
103
110
|
/node_modules/,
|
|
@@ -115,11 +122,41 @@ export async function devCommand(dirArg, options = {}) {
|
|
|
115
122
|
persistent: true,
|
|
116
123
|
ignoreInitial: true,
|
|
117
124
|
});
|
|
125
|
+
const scheduleRebuild = (filePath) => {
|
|
126
|
+
queuedFilePath = filePath;
|
|
127
|
+
if (rebuildTimeout) {
|
|
128
|
+
clearTimeout(rebuildTimeout);
|
|
129
|
+
}
|
|
130
|
+
rebuildTimeout = setTimeout(() => {
|
|
131
|
+
rebuildTimeout = null;
|
|
132
|
+
if (!isRebuilding && queuedFilePath) {
|
|
133
|
+
const target = queuedFilePath;
|
|
134
|
+
queuedFilePath = null;
|
|
135
|
+
runRebuildCycle(target);
|
|
136
|
+
}
|
|
137
|
+
}, DEBOUNCE_MS);
|
|
138
|
+
};
|
|
118
139
|
const runRebuildCycle = async (triggerPath) => {
|
|
119
140
|
isRebuilding = true;
|
|
120
|
-
rebuildPending = false;
|
|
121
141
|
const rel = path.relative(rootDir, triggerPath);
|
|
142
|
+
const isManifestRelated = triggerPath.endsWith("manifest.json") || triggerPath.endsWith("package.json");
|
|
122
143
|
try {
|
|
144
|
+
// If resource_pack was added while dev server is running
|
|
145
|
+
if (!hasRp && fs.existsSync(rpDir)) {
|
|
146
|
+
hasRp = true;
|
|
147
|
+
watcher.add(rpDir);
|
|
148
|
+
logger.info("Detected new Resource Pack. Watching resource_pack...");
|
|
149
|
+
if (shouldLink && mojang) {
|
|
150
|
+
const destRp = path.join(mojang.devResourcePacks, `${folderName}_RP`);
|
|
151
|
+
try {
|
|
152
|
+
linkPack(rpDir, destRp);
|
|
153
|
+
logger.success(`Linked Resource Pack -> ${destRp}`);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
logger.error(`Failed to link Resource Pack: ${err.message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
123
160
|
if (triggerPath.endsWith("manifest.json")) {
|
|
124
161
|
const res = validateManifest(triggerPath);
|
|
125
162
|
if (res.valid) {
|
|
@@ -135,9 +172,15 @@ export async function devCommand(dirArg, options = {}) {
|
|
|
135
172
|
const buildOk = await buildCommand(rootDir);
|
|
136
173
|
if (buildOk && hotReloadServer) {
|
|
137
174
|
if (hotReloadServer.clientCount > 0) {
|
|
138
|
-
// Buffer
|
|
139
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
175
|
+
// Buffer 200ms before reload command so Proton/Wine file cache flushes
|
|
176
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
177
|
+
// Enforce reload cooldown so rapid saves cannot crash Minecraft scripting engine
|
|
178
|
+
const elapsed = Date.now() - lastReloadTime;
|
|
179
|
+
if (elapsed < RELOAD_COOLDOWN_MS) {
|
|
180
|
+
await new Promise((resolve) => setTimeout(resolve, RELOAD_COOLDOWN_MS - elapsed));
|
|
181
|
+
}
|
|
140
182
|
hotReloadServer.reload();
|
|
183
|
+
lastReloadTime = Date.now();
|
|
141
184
|
logger.success("Sent /reload to Minecraft via WebSocket");
|
|
142
185
|
}
|
|
143
186
|
else {
|
|
@@ -145,30 +188,35 @@ export async function devCommand(dirArg, options = {}) {
|
|
|
145
188
|
}
|
|
146
189
|
}
|
|
147
190
|
}
|
|
191
|
+
if (isManifestRelated) {
|
|
192
|
+
logger.warn("Note: Minecraft /reload only reloads scripts. Re-enter world to apply manifest/package.json changes.");
|
|
193
|
+
}
|
|
148
194
|
}
|
|
149
195
|
catch (err) {
|
|
150
196
|
logger.error(`Error during rebuild: ${err.message}`);
|
|
151
197
|
}
|
|
152
198
|
finally {
|
|
153
199
|
isRebuilding = false;
|
|
154
|
-
if (
|
|
155
|
-
|
|
200
|
+
if (queuedFilePath) {
|
|
201
|
+
const nextTarget = queuedFilePath;
|
|
202
|
+
queuedFilePath = null;
|
|
203
|
+
scheduleRebuild(nextTarget);
|
|
156
204
|
}
|
|
157
205
|
}
|
|
158
206
|
};
|
|
159
207
|
const triggerRebuild = (filePath) => {
|
|
208
|
+
// Ignore internal writes to manifest.json caused by syncManifestDependencies
|
|
209
|
+
if (filePath.endsWith("manifest.json") &&
|
|
210
|
+
Date.now() - getLastManifestSyncTime() < 600) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
160
213
|
const rel = path.relative(rootDir, filePath);
|
|
161
214
|
logger.info(`Change detected in ${rel}`);
|
|
162
215
|
if (isRebuilding) {
|
|
163
|
-
|
|
216
|
+
queuedFilePath = filePath;
|
|
164
217
|
return;
|
|
165
218
|
}
|
|
166
|
-
|
|
167
|
-
clearTimeout(rebuildTimeout);
|
|
168
|
-
}
|
|
169
|
-
rebuildTimeout = setTimeout(() => {
|
|
170
|
-
runRebuildCycle(filePath);
|
|
171
|
-
}, 300);
|
|
219
|
+
scheduleRebuild(filePath);
|
|
172
220
|
};
|
|
173
221
|
watcher.on("change", triggerRebuild);
|
|
174
222
|
watcher.on("add", triggerRebuild);
|
package/dist/commands/init.js
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
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
5
|
import { resolveMinecraftDependencies } from "../utils/npm.js";
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
|
-
const DEFAULT_ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
|
|
8
7
|
export async function initCommand(targetDirArg, options) {
|
|
9
8
|
const targetDir = path.resolve(targetDirArg || ".");
|
|
10
9
|
const projectName = options.name || path.basename(targetDir) || "my-bedrock-addon";
|
|
11
10
|
const projectDesc = options.desc || `${projectName} - Bedrock Addon created with bdev`;
|
|
12
11
|
const isTs = options.lang !== "js";
|
|
13
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
|
+
}
|
|
14
32
|
logger.info(`Scaffolding Bedrock Addon project: ${projectName}`);
|
|
15
33
|
logger.info(`Directory: ${targetDir}`);
|
|
16
34
|
logger.info(`Language: ${isTs ? "TypeScript" : "JavaScript"} | Resource Pack: ${hasRp ? "Yes" : "No"}`);
|
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)) {
|
|
@@ -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
|
+
}
|