mbler 0.2.0 → 0.2.2
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/build.d.ts +145 -0
- package/dist/build.esm.js +1312 -0
- package/dist/build.esm.js.map +1 -0
- package/dist/build.js +1339 -0
- package/dist/build.js.map +1 -0
- package/dist/index.d.ts +11 -144
- package/dist/index.esm.js +150 -964
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +148 -965
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/build.js
ADDED
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var mcxDef = require('@mbler/mcx-core');
|
|
4
|
+
var _chalk = require('chalk');
|
|
5
|
+
var jsonPlugin = require('@rollup/plugin-json');
|
|
6
|
+
var resolvePlugin = require('@rollup/plugin-node-resolve');
|
|
7
|
+
var minifyPlugin = require('@rollup/plugin-terser');
|
|
8
|
+
var chokidar = require('chokidar');
|
|
9
|
+
var fs = require('node:fs/promises');
|
|
10
|
+
var path = require('node:path');
|
|
11
|
+
var rollup = require('rollup');
|
|
12
|
+
var readline = require('readline');
|
|
13
|
+
var os = require('node:os');
|
|
14
|
+
var npmRegistryFetch = require('npm-registry-fetch');
|
|
15
|
+
require('node:child_process');
|
|
16
|
+
var crypto = require('node:crypto');
|
|
17
|
+
var fs$1 = require('node:fs');
|
|
18
|
+
var node_process = require('node:process');
|
|
19
|
+
var AdmZip = require('adm-zip');
|
|
20
|
+
var commonjs = require('@rollup/plugin-commonjs');
|
|
21
|
+
var mcxServer = require('@mbler/mcx-server');
|
|
22
|
+
var typescript = require('@rollup/plugin-typescript');
|
|
23
|
+
var runTsc = require('@volar/typescript/lib/quickstart/runTsc');
|
|
24
|
+
|
|
25
|
+
function _interopNamespaceDefault(e) {
|
|
26
|
+
var n = Object.create(null);
|
|
27
|
+
if (e) {
|
|
28
|
+
Object.keys(e).forEach(function (k) {
|
|
29
|
+
if (k !== 'default') {
|
|
30
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
31
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function () { return e[k]; }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
n.default = e;
|
|
39
|
+
return Object.freeze(n);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var mcxDef__namespace = /*#__PURE__*/_interopNamespaceDefault(mcxDef);
|
|
43
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
44
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
45
|
+
var rollup__namespace = /*#__PURE__*/_interopNamespaceDefault(rollup);
|
|
46
|
+
var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
47
|
+
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$1);
|
|
48
|
+
|
|
49
|
+
// 启用 raw mode 和键盘事件(仅在 TTY 环境中)
|
|
50
|
+
if (process.stdin.isTTY) {
|
|
51
|
+
process.stdin.setRawMode(true);
|
|
52
|
+
readline__namespace.emitKeypressEvents(process.stdin);
|
|
53
|
+
}
|
|
54
|
+
const promises = [];
|
|
55
|
+
const tasks = [];
|
|
56
|
+
process.on("exit", (code) => {
|
|
57
|
+
process.stdout.write("\x1b[?25h");
|
|
58
|
+
});
|
|
59
|
+
const endTasks = [];
|
|
60
|
+
function onEnd(task) {
|
|
61
|
+
endTasks.push(task);
|
|
62
|
+
}
|
|
63
|
+
click("c", {
|
|
64
|
+
ctrl: true,
|
|
65
|
+
}).then(() => {
|
|
66
|
+
endTasks.forEach((task) => task());
|
|
67
|
+
process.exit(0);
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* 监听键盘输入并触发对应的 Promise 或任务
|
|
71
|
+
*/
|
|
72
|
+
function handler(name, { ctrl, alt, }, raw) {
|
|
73
|
+
// 查找是否有匹配的 Promise 等待触发
|
|
74
|
+
const find = promises.find((e) => e.name === name && e.ctrl === ctrl && e.alt === alt);
|
|
75
|
+
if (find) {
|
|
76
|
+
find.resolve();
|
|
77
|
+
promises.splice(promises.indexOf(find), 1);
|
|
78
|
+
}
|
|
79
|
+
// 通知所有注册的监听任务
|
|
80
|
+
tasks.forEach((item) => item(name, ctrl, alt, raw));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 模拟等待某个按键被按下,返回一个 Promise
|
|
84
|
+
*/
|
|
85
|
+
function click(name, { ctrl = false, alt = false, }) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
promises.push({
|
|
88
|
+
name,
|
|
89
|
+
ctrl: ctrl || false,
|
|
90
|
+
alt: alt || false,
|
|
91
|
+
resolve,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 工具类:提供控制台交互功能,比如高亮菜单渲染、交互式选择等
|
|
97
|
+
*/
|
|
98
|
+
class Input {
|
|
99
|
+
/**
|
|
100
|
+
* 渲染一个字符串数组,高亮当前选中的项
|
|
101
|
+
* @param arr 菜单项数组
|
|
102
|
+
* @param index 当前选中索引
|
|
103
|
+
* @returns 格式化后的字符串
|
|
104
|
+
*/
|
|
105
|
+
static render(arr, index) {
|
|
106
|
+
return arr
|
|
107
|
+
.map((item, pindex) => {
|
|
108
|
+
if (pindex === index)
|
|
109
|
+
return "\x1b[1m\x1b[32m" + item + "\x1b[0m"; // 亮绿,高亮
|
|
110
|
+
return "\x1b[1m\x1b[33m" + item + "\x1b[0m"; // 亮黄
|
|
111
|
+
})
|
|
112
|
+
.join(" ");
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 提供一个交互式菜单选择器
|
|
116
|
+
* @param tip 提示文本
|
|
117
|
+
* @param arr 选项数组
|
|
118
|
+
* @returns 用户选中的选项内容(Promise<string>)
|
|
119
|
+
*/
|
|
120
|
+
static select(tip, arr) {
|
|
121
|
+
let index = 0;
|
|
122
|
+
let win = false;
|
|
123
|
+
console.log(`\x1b[2K\x1b[47m\x1b[1m\x1b[30m${tip} (按 b 确认,n 键选择下一个) \x1b[0m\x1b[?25l`);
|
|
124
|
+
console.log(Input.render(arr, index) + "\n\x1b[1A");
|
|
125
|
+
const handlerNext = () => {
|
|
126
|
+
if (win)
|
|
127
|
+
return;
|
|
128
|
+
index++;
|
|
129
|
+
if (index >= arr.length)
|
|
130
|
+
index = 0;
|
|
131
|
+
console.log(`\x1b[1A${Input.render(arr, index)}\n\x1b[1A`);
|
|
132
|
+
click("n", {
|
|
133
|
+
ctrl: false,
|
|
134
|
+
alt: false,
|
|
135
|
+
}).then(handlerNext);
|
|
136
|
+
};
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
// 监听 n 按键来切换选项
|
|
139
|
+
click("n", {
|
|
140
|
+
ctrl: false,
|
|
141
|
+
alt: false,
|
|
142
|
+
}).then(handlerNext);
|
|
143
|
+
// 监听 b 按键来确认选择
|
|
144
|
+
click("b", {
|
|
145
|
+
ctrl: false,
|
|
146
|
+
alt: false,
|
|
147
|
+
}).then(() => {
|
|
148
|
+
win = true;
|
|
149
|
+
process.stdout.write("\x1b[?25h");
|
|
150
|
+
resolve(arr[index]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 注册一个全局任务,每次按键都会被调用
|
|
156
|
+
* @param task 回调函数
|
|
157
|
+
*/
|
|
158
|
+
static use(task) {
|
|
159
|
+
tasks.push(task);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// 监听键盘输入事件
|
|
163
|
+
process.stdin.on("keypress", (str, key) => {
|
|
164
|
+
const rawKeyName = key?.name || "";
|
|
165
|
+
const ctrl = Boolean(key?.ctrl);
|
|
166
|
+
const alt = Boolean(key?.alt);
|
|
167
|
+
handler(rawKeyName, {
|
|
168
|
+
ctrl,
|
|
169
|
+
alt,
|
|
170
|
+
}, str);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const templateMblerConfig = {
|
|
174
|
+
name: 'demo',
|
|
175
|
+
description: 'demo',
|
|
176
|
+
version: '0.0.0',
|
|
177
|
+
mcVersion: '1.21.100',
|
|
178
|
+
script: {
|
|
179
|
+
main: '',
|
|
180
|
+
},
|
|
181
|
+
minify: false,
|
|
182
|
+
outdir: {
|
|
183
|
+
behavior: '',
|
|
184
|
+
resources: '',
|
|
185
|
+
dist: '',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const BuildConfig = {
|
|
190
|
+
ConfigFile: "mbler.config.js",
|
|
191
|
+
salt: {
|
|
192
|
+
header: "d61e721d-a2c9-4535-8054-0183bce24767",
|
|
193
|
+
sapi: "33e2c698-908f-45ab-8a9f-66018f8486ed",
|
|
194
|
+
module: "cbbacfa4-8b1e-4a9c-9cbd-7a0d2e5f0b3c",
|
|
195
|
+
},
|
|
196
|
+
behavior: "behavior",
|
|
197
|
+
resources: "resources",
|
|
198
|
+
includes: {
|
|
199
|
+
public: {
|
|
200
|
+
"pack_icon.png": "file",
|
|
201
|
+
"manifest.json": "file",
|
|
202
|
+
},
|
|
203
|
+
resources: {
|
|
204
|
+
"biomes_client.json": "file",
|
|
205
|
+
"blocks.json": "file",
|
|
206
|
+
"bug_pack_icon.png": "file",
|
|
207
|
+
"contents.json": "file",
|
|
208
|
+
"loading_messages.json": "file",
|
|
209
|
+
"manifest_publish.json": "file",
|
|
210
|
+
"signatures.json": "file",
|
|
211
|
+
"sounds.json": "file",
|
|
212
|
+
"splashes.json": "file",
|
|
213
|
+
animation_controllers: "directory",
|
|
214
|
+
animations: "directory",
|
|
215
|
+
attachables: "directory",
|
|
216
|
+
biomes: "directory",
|
|
217
|
+
cameras: "directory",
|
|
218
|
+
entity: "directory",
|
|
219
|
+
fogs: "directory",
|
|
220
|
+
font: "directory",
|
|
221
|
+
items: "directory",
|
|
222
|
+
library: "directory",
|
|
223
|
+
materials: "directory",
|
|
224
|
+
models: "directory",
|
|
225
|
+
particles: "directory",
|
|
226
|
+
render_controllers: "directory",
|
|
227
|
+
sounds: "directory",
|
|
228
|
+
texts: "directory",
|
|
229
|
+
textures: "directory",
|
|
230
|
+
},
|
|
231
|
+
behavior: {
|
|
232
|
+
aim_assist: "directory",
|
|
233
|
+
animation_controllers: "directory",
|
|
234
|
+
animations: "directory",
|
|
235
|
+
behavior_trees: "directory",
|
|
236
|
+
biomes: "directory",
|
|
237
|
+
blocks: "directory",
|
|
238
|
+
cameras: "directory",
|
|
239
|
+
dimensions: "directory",
|
|
240
|
+
entities: "directory",
|
|
241
|
+
feature_rules: "directory",
|
|
242
|
+
features: "directory",
|
|
243
|
+
functions: "directory",
|
|
244
|
+
item_catalog: "directory",
|
|
245
|
+
items: "directory",
|
|
246
|
+
loot_tables: "directory",
|
|
247
|
+
recipes: "directory",
|
|
248
|
+
scripts: "skip", // special handling
|
|
249
|
+
spawn_rules: "directory",
|
|
250
|
+
structures: "directory",
|
|
251
|
+
texts: "directory",
|
|
252
|
+
trading: "directory",
|
|
253
|
+
worldgen: "directory",
|
|
254
|
+
"contents.json": "file",
|
|
255
|
+
"manifest_publish.json": "file",
|
|
256
|
+
"signatures.json": "file",
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
async function FileExsit(file) {
|
|
262
|
+
try {
|
|
263
|
+
const f = await fs__namespace.stat(file);
|
|
264
|
+
if (f)
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
function join(baseDir, inputPath) {
|
|
273
|
+
return path__namespace.isAbsolute(inputPath) ? inputPath : path__namespace.join(baseDir, inputPath);
|
|
274
|
+
}
|
|
275
|
+
async function ReadProjectMblerConfig(project) {
|
|
276
|
+
const fileExport = await import(path__namespace.join(project, BuildConfig.ConfigFile));
|
|
277
|
+
const file = fileExport.default || {};
|
|
278
|
+
for (const key in file) {
|
|
279
|
+
if (!(key in templateMblerConfig)) {
|
|
280
|
+
throw new Error(`[read config]: read config from '${project}' error: Unexpected '${key}'`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return file;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Print a single-line message to stdout with a trailing newline.
|
|
287
|
+
* Exported here so other modules (for example `build`) do not need
|
|
288
|
+
* to import from `cli`, avoiding a circular dependency.
|
|
289
|
+
*/
|
|
290
|
+
// IO缓冲队列,避免多线程写入冲突
|
|
291
|
+
let outputQueue = [];
|
|
292
|
+
let isFlushing = false;
|
|
293
|
+
async function flushOutputQueue() {
|
|
294
|
+
if (isFlushing || outputQueue.length === 0)
|
|
295
|
+
return;
|
|
296
|
+
isFlushing = true;
|
|
297
|
+
try {
|
|
298
|
+
while (outputQueue.length > 0) {
|
|
299
|
+
const chunk = outputQueue.shift();
|
|
300
|
+
if (chunk) {
|
|
301
|
+
process.stdout.write(chunk);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
finally {
|
|
306
|
+
isFlushing = false;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
process.on("exit", flushOutputQueue);
|
|
310
|
+
function showText(text, needNextLine = true) {
|
|
311
|
+
outputQueue.push(text + (needNextLine ? '\n' : ""));
|
|
312
|
+
if (!isFlushing) {
|
|
313
|
+
Promise.resolve().then(() => flushOutputQueue()).catch(() => {
|
|
314
|
+
outputQueue = [];
|
|
315
|
+
isFlushing = false;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function stringToNumberArray(str) {
|
|
320
|
+
return str
|
|
321
|
+
.split('.')
|
|
322
|
+
.map((s) => parseInt(s, 10))
|
|
323
|
+
.slice(0, 3);
|
|
324
|
+
}
|
|
325
|
+
async function writeJSON(filePath, data) {
|
|
326
|
+
const content = JSON.stringify(data, null, 2);
|
|
327
|
+
if (!(await FileExsit(path__namespace.dirname(filePath)))) {
|
|
328
|
+
await fs__namespace
|
|
329
|
+
.mkdir(path__namespace.dirname(filePath), { recursive: true })
|
|
330
|
+
.catch(() => void 0);
|
|
331
|
+
}
|
|
332
|
+
return await fs__namespace.writeFile(filePath, content, 'utf-8');
|
|
333
|
+
}
|
|
334
|
+
function compareVersion(a, b) {
|
|
335
|
+
const pa = a.split('.').map((x) => parseInt(x, 10) || 0);
|
|
336
|
+
const pb = b.split('.').map((x) => parseInt(x, 10) || 0);
|
|
337
|
+
for (let i = 0; i < 3; i++) {
|
|
338
|
+
const na = pa[i] || 0;
|
|
339
|
+
const nb = pb[i] || 0;
|
|
340
|
+
if (na !== nb)
|
|
341
|
+
return na - nb;
|
|
342
|
+
}
|
|
343
|
+
return 0;
|
|
344
|
+
}
|
|
345
|
+
((function () {
|
|
346
|
+
let curr;
|
|
347
|
+
let currstr = '';
|
|
348
|
+
let tip = '';
|
|
349
|
+
let show = true;
|
|
350
|
+
// 在输入时使用输入中间件
|
|
351
|
+
Input.use(function (raw, ctrl, alt, name) {
|
|
352
|
+
if (typeof curr !== 'function')
|
|
353
|
+
return;
|
|
354
|
+
if (ctrl || alt)
|
|
355
|
+
return;
|
|
356
|
+
if (raw) {
|
|
357
|
+
if (raw === 'return' || raw === 'enter') {
|
|
358
|
+
curr(currstr);
|
|
359
|
+
curr = null;
|
|
360
|
+
currstr = '';
|
|
361
|
+
console.log('');
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (raw === 'backspace') {
|
|
365
|
+
currstr = currstr.slice(0, -1);
|
|
366
|
+
refreshInput();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (name && typeof name === 'string') {
|
|
371
|
+
currstr += name;
|
|
372
|
+
refreshInput();
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
function refreshInput() {
|
|
376
|
+
const out = `\x1b[2K\r${tip}${show ? currstr : ''}`;
|
|
377
|
+
process.stdout.write(out);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 输入文本
|
|
381
|
+
* @param{string} tip 提示
|
|
382
|
+
* @param{boolean} show 是否显示输入
|
|
383
|
+
*/
|
|
384
|
+
return async function (t = '', g = true) {
|
|
385
|
+
return new Promise((resolve) => {
|
|
386
|
+
show = g;
|
|
387
|
+
tip = t;
|
|
388
|
+
refreshInput();
|
|
389
|
+
curr = resolve;
|
|
390
|
+
});
|
|
391
|
+
};
|
|
392
|
+
}))();
|
|
393
|
+
|
|
394
|
+
const logFile = path.join(os.homedir(), ".cache/mbler/latest.log");
|
|
395
|
+
function _clean(promise) {
|
|
396
|
+
return () => {
|
|
397
|
+
Logger.run = Logger.run.filter((item) => {
|
|
398
|
+
return item !== promise;
|
|
399
|
+
});
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function writeLog(logContent) {
|
|
403
|
+
async function write() {
|
|
404
|
+
try {
|
|
405
|
+
const dir = path.dirname(logFile);
|
|
406
|
+
if (!(await FileExsit(dir))) {
|
|
407
|
+
// ensure the directory exists, root-to-leaf
|
|
408
|
+
await fs.mkdir(dir, { recursive: true }).catch(() => void 0);
|
|
409
|
+
}
|
|
410
|
+
// if file does not exist, create it (touch)
|
|
411
|
+
if (!(await FileExsit(logFile))) {
|
|
412
|
+
await fs.writeFile(logFile, "");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
// if we can't prepare the log file, output to stderr but don't crash
|
|
417
|
+
console.error("[logger] unable to prepare log file:", err);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
await fs.appendFile(logFile, "\n" + logContent);
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
console.error("[logger] failed to append to log file:", err);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const asy = write();
|
|
428
|
+
Logger.run.push(asy.then(_clean(asy)));
|
|
429
|
+
}
|
|
430
|
+
class Logger {
|
|
431
|
+
// 写入日志池
|
|
432
|
+
static LogFile = logFile;
|
|
433
|
+
static run = [];
|
|
434
|
+
static _b(tag, msg, t) {
|
|
435
|
+
const date = new Date();
|
|
436
|
+
const logContent = [
|
|
437
|
+
`${date.toLocaleDateString()} ${date.toLocaleTimeString()}`,
|
|
438
|
+
`[${t} ${tag}]`,
|
|
439
|
+
msg,
|
|
440
|
+
].join(" ");
|
|
441
|
+
writeLog(logContent);
|
|
442
|
+
}
|
|
443
|
+
static w(tag, msg) {
|
|
444
|
+
Logger._b(tag, msg, "WARN");
|
|
445
|
+
}
|
|
446
|
+
static e(tag, msg) {
|
|
447
|
+
Logger._b(tag, msg, "ERROR");
|
|
448
|
+
}
|
|
449
|
+
static i(tag, msg) {
|
|
450
|
+
Logger._b(tag, msg, "INFO");
|
|
451
|
+
}
|
|
452
|
+
static d(tag, msg) {
|
|
453
|
+
Logger._b(tag, msg, "DEBUG");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 该模块用于从字符串生成哈希转 uuid
|
|
458
|
+
const fromString = (input, salt = '') => {
|
|
459
|
+
const combinedInput = salt + input;
|
|
460
|
+
const hash = crypto
|
|
461
|
+
.createHash('sha256')
|
|
462
|
+
.update(combinedInput)
|
|
463
|
+
.digest('hex');
|
|
464
|
+
const base = hash
|
|
465
|
+
.slice(0, 32); // 取前 32 个 hex 字符(16 字节)
|
|
466
|
+
const ls = '89ab';
|
|
467
|
+
const r = (t) => ls[(combinedInput.length + t + salt.length) % ls.length];
|
|
468
|
+
// 构造成标准 UUID v4 格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
|
469
|
+
const uuid = `${base.substring(0, 8)}-${base.substring(8, 12)}-4${base.substring(12, 15)}-8${r(1)}${r(2)}${r(3)}-${base.substring(18, 30)}`;
|
|
470
|
+
return uuid;
|
|
471
|
+
};
|
|
472
|
+
({
|
|
473
|
+
uuid: crypto.randomUUID
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const config = {
|
|
477
|
+
tmpdir: path__namespace.join(os.tmpdir(), ".mbler")};
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Compare two dotted version strings ("major.minor.patch").
|
|
481
|
+
* Returns negative if a < b, positive if a > b, zero if equal.
|
|
482
|
+
*/
|
|
483
|
+
const exp = (function () {
|
|
484
|
+
const cacheFile = path__namespace.join(config.tmpdir, "_sapi_version.json");
|
|
485
|
+
// cacheData is an array of entries keyed by the embedded mc version string
|
|
486
|
+
let cacheData = null;
|
|
487
|
+
/**
|
|
488
|
+
* Pull every published version for a package and reduce it to a mapping
|
|
489
|
+
* from the embedded Minecraft version (e.g. "1.21.60") to the most
|
|
490
|
+
* recent formal/beta release we were able to parse.
|
|
491
|
+
*/
|
|
492
|
+
async function fetchData(pkgName) {
|
|
493
|
+
const data = (await npmRegistryFetch.json(`/${pkgName}`));
|
|
494
|
+
const pkgVersions = Object.keys(data.versions);
|
|
495
|
+
const reValue = {};
|
|
496
|
+
// helper to extract the embedded MC version ("yyyy") from a full
|
|
497
|
+
// npm package version string. returns null when the expected pattern
|
|
498
|
+
// cannot be found.
|
|
499
|
+
const mcVersionFrom = (str) => {
|
|
500
|
+
const m = str.match(/-(?:rc|beta)(?:\.[^-.]+)*?\.((?:\d+\.){2}\d+)/);
|
|
501
|
+
return m ? m[1] : null;
|
|
502
|
+
};
|
|
503
|
+
for (const v of pkgVersions) {
|
|
504
|
+
const mcVersion = mcVersionFrom(v);
|
|
505
|
+
if (!mcVersion)
|
|
506
|
+
continue;
|
|
507
|
+
const isStable = /(?:-stable)(?:$|[-.])/.test(v);
|
|
508
|
+
let entry = reValue[mcVersion];
|
|
509
|
+
if (!entry) {
|
|
510
|
+
entry = { formal: "", beta: "", _v: -1 };
|
|
511
|
+
reValue[mcVersion] = entry;
|
|
512
|
+
}
|
|
513
|
+
if (isStable) {
|
|
514
|
+
// pick the lexically greatest stable version string
|
|
515
|
+
if (!entry.formal || v > entry.formal) {
|
|
516
|
+
entry.formal = v;
|
|
517
|
+
}
|
|
518
|
+
entry._v = Infinity;
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
// non-stable release; treat everything else as a beta candidate
|
|
522
|
+
if (!entry.beta || v > entry.beta) {
|
|
523
|
+
entry.beta = v;
|
|
524
|
+
}
|
|
525
|
+
if (entry._v !== Infinity)
|
|
526
|
+
entry._v = 1;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return reValue;
|
|
530
|
+
}
|
|
531
|
+
async function refresh() {
|
|
532
|
+
// grab the two packages we care about and merge the keys
|
|
533
|
+
const serverMap = await fetchData("@minecraft/server");
|
|
534
|
+
const uiMap = await fetchData("@minecraft/server-ui");
|
|
535
|
+
const versions = new Set([
|
|
536
|
+
...Object.keys(serverMap),
|
|
537
|
+
...Object.keys(uiMap),
|
|
538
|
+
]);
|
|
539
|
+
const arr = [];
|
|
540
|
+
for (const ver of Array.from(versions)) {
|
|
541
|
+
arr.push({
|
|
542
|
+
version: ver,
|
|
543
|
+
server: serverMap[ver]
|
|
544
|
+
? { formal: serverMap[ver].formal, beta: serverMap[ver].beta }
|
|
545
|
+
: { formal: "", beta: "" },
|
|
546
|
+
"server-ui": uiMap[ver]
|
|
547
|
+
? { formal: uiMap[ver].formal, beta: uiMap[ver].beta }
|
|
548
|
+
: { formal: "", beta: "" },
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
arr.sort((a, b) => compareVersion(a.version, b.version));
|
|
552
|
+
cacheData = arr;
|
|
553
|
+
await fs__namespace$1.promises
|
|
554
|
+
.mkdir(config.tmpdir, { recursive: true })
|
|
555
|
+
.catch(() => void 0);
|
|
556
|
+
await fs__namespace$1.promises.writeFile(cacheFile, JSON.stringify(arr, null, 2), "utf-8");
|
|
557
|
+
}
|
|
558
|
+
async function generateVersion(module, mcVersion, isBeta) {
|
|
559
|
+
if (!cacheData) {
|
|
560
|
+
try {
|
|
561
|
+
const txt = await fs__namespace$1.promises.readFile(cacheFile, "utf-8");
|
|
562
|
+
cacheData = JSON.parse(txt);
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
await refresh();
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (!cacheData) {
|
|
569
|
+
throw new Error("unable to load sapi cache data");
|
|
570
|
+
}
|
|
571
|
+
// try exact match first
|
|
572
|
+
let entry = cacheData.find((e) => e.version === mcVersion);
|
|
573
|
+
if (!entry) {
|
|
574
|
+
// find closest entry less than or equal to requested version
|
|
575
|
+
const sorted = cacheData.slice();
|
|
576
|
+
let candidate = null;
|
|
577
|
+
for (const e of sorted) {
|
|
578
|
+
if (compareVersion(e.version, mcVersion) <= 0) {
|
|
579
|
+
candidate = e;
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (!candidate) {
|
|
586
|
+
candidate = sorted[0];
|
|
587
|
+
}
|
|
588
|
+
entry = candidate;
|
|
589
|
+
}
|
|
590
|
+
const moduleKey = module === "@minecraft/server" ? "server" : "server-ui";
|
|
591
|
+
const entryModule = entry[moduleKey];
|
|
592
|
+
let result = isBeta ? entryModule.beta : entryModule.formal;
|
|
593
|
+
if (!result) {
|
|
594
|
+
// fall back to whatever is available
|
|
595
|
+
result = entryModule.formal || entryModule.beta;
|
|
596
|
+
}
|
|
597
|
+
return result || "";
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
refresh,
|
|
601
|
+
generateVersion,
|
|
602
|
+
};
|
|
603
|
+
})();
|
|
604
|
+
|
|
605
|
+
async function generateManifest(config, type) {
|
|
606
|
+
const manifest = {
|
|
607
|
+
format_version: 2,
|
|
608
|
+
header: {
|
|
609
|
+
name: config.name,
|
|
610
|
+
description: config.description,
|
|
611
|
+
uuid: fromString(config.name, BuildConfig.salt.header + type),
|
|
612
|
+
version: stringToNumberArray(config.version),
|
|
613
|
+
min_engine_version: stringToNumberArray(typeof config.mcVersion === "string"
|
|
614
|
+
? config.mcVersion
|
|
615
|
+
: (() => {
|
|
616
|
+
throw new Error("mcVersion in mblerconfig should be a string");
|
|
617
|
+
})()),
|
|
618
|
+
},
|
|
619
|
+
modules: [
|
|
620
|
+
{
|
|
621
|
+
type: type,
|
|
622
|
+
uuid: fromString(config.name, BuildConfig.salt.module + type),
|
|
623
|
+
description: `From Mbler(https://github.com/RuanhoR/mbler). welcome to star and contribute!`,
|
|
624
|
+
version: stringToNumberArray(config.version),
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
};
|
|
628
|
+
if (type === "data" && config.script) {
|
|
629
|
+
manifest.modules.push({
|
|
630
|
+
type: "script",
|
|
631
|
+
uuid: fromString(config.name, BuildConfig.salt.sapi + type),
|
|
632
|
+
description: `sapi generate by mbler, weclome to download and star at https://github.com/RuanhoR/mbler`,
|
|
633
|
+
version: stringToNumberArray(config.version),
|
|
634
|
+
});
|
|
635
|
+
manifest.capabilities = ["script_eval"];
|
|
636
|
+
manifest.dependencies = [
|
|
637
|
+
{
|
|
638
|
+
module_name: "@minecraft/server",
|
|
639
|
+
version: (await exp.generateVersion("@minecraft/server", config.mcVersion, config.script?.UseBeta || false)).split("-")[0], // only major.minor.patch, remove -beta or -rc
|
|
640
|
+
},
|
|
641
|
+
];
|
|
642
|
+
if (config.script.ui) {
|
|
643
|
+
manifest.dependencies.push({
|
|
644
|
+
module_name: "@minecraft/server-ui",
|
|
645
|
+
version: (await exp.generateVersion("@minecraft/server-ui", config.mcVersion, config.script?.UseBeta || false)).split("-")[0], // only major.minor.patch, remove -beta or -rc
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return manifest;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function createFullZip(dir) {
|
|
653
|
+
const zip = new AdmZip();
|
|
654
|
+
zip.addLocalFolder(dir);
|
|
655
|
+
return zip;
|
|
656
|
+
}
|
|
657
|
+
async function createZipWithMoreFolder(dir) {
|
|
658
|
+
const zip = new AdmZip();
|
|
659
|
+
for (const folder of dir) {
|
|
660
|
+
await zip.addLocalFolderPromise(folder[0], {
|
|
661
|
+
zipPath: folder[1]
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
return zip;
|
|
665
|
+
}
|
|
666
|
+
async function generateRelease(build) {
|
|
667
|
+
if (!build.srcDirs)
|
|
668
|
+
throw new Error("invaild Build");
|
|
669
|
+
if (node_process.env.BUILD_MODULE !== "release")
|
|
670
|
+
return;
|
|
671
|
+
let zip;
|
|
672
|
+
if (build.module == "all") {
|
|
673
|
+
zip = await createZipWithMoreFolder([
|
|
674
|
+
[build.srcDirs?.behavior, "behavior"],
|
|
675
|
+
[build.srcDirs.resources, "resources"]
|
|
676
|
+
]);
|
|
677
|
+
}
|
|
678
|
+
else if (build.module == "behavior") {
|
|
679
|
+
zip = createFullZip(build.srcDirs.behavior);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
zip = createFullZip(build.srcDirs.resources);
|
|
683
|
+
}
|
|
684
|
+
await zip.writeZipPromise(build.outdirs?.dist);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// cjs support
|
|
688
|
+
const chalk$1 = _chalk instanceof Function ? _chalk : _chalk.default;
|
|
689
|
+
class Postgress {
|
|
690
|
+
max;
|
|
691
|
+
constructor(max) {
|
|
692
|
+
this.max = max;
|
|
693
|
+
}
|
|
694
|
+
update(current) {
|
|
695
|
+
const percentage = Math.min(current, this.max) / this.max;
|
|
696
|
+
const barWidth = 30;
|
|
697
|
+
const filledWidth = Math.round(barWidth * percentage);
|
|
698
|
+
const emptyWidth = barWidth - filledWidth;
|
|
699
|
+
const filledBar = chalk$1.green('█'.repeat(filledWidth));
|
|
700
|
+
const emptyBar = chalk$1.white('█'.repeat(emptyWidth));
|
|
701
|
+
const progressBar = `${filledBar}${emptyBar}`;
|
|
702
|
+
const percentText = chalk$1.blue(`${Math.round(percentage * 100)}%`);
|
|
703
|
+
const progressText = `\n\u001B[1A\r[${progressBar}] ${percentText} (${current}/${this.max})`;
|
|
704
|
+
showText(progressText, false);
|
|
705
|
+
if (current == this.max) {
|
|
706
|
+
showText("", true);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* 运行 MCX TypeScript 编译器
|
|
713
|
+
* 为 .mcx 文件提供 TypeScript 类型检查支持
|
|
714
|
+
*/
|
|
715
|
+
function runTSC(tscpath = require.resolve("typescript/lib/tsc")) {
|
|
716
|
+
runTsc.runTsc(tscpath, {
|
|
717
|
+
extraSupportedExtensions: ['.mcx'],
|
|
718
|
+
extraExtensionsToRemove: ['.mcx'],
|
|
719
|
+
}, (ts) => {
|
|
720
|
+
return [mcxServer.createMCXLanguagePlugin(ts)];
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// cjs support
|
|
725
|
+
const chalk = _chalk instanceof Function ? _chalk : _chalk.default;
|
|
726
|
+
class Build {
|
|
727
|
+
baseBuildDir;
|
|
728
|
+
resolve;
|
|
729
|
+
isWatch;
|
|
730
|
+
currentConfig = null;
|
|
731
|
+
srcDirs = null;
|
|
732
|
+
outdirs = null;
|
|
733
|
+
mcxTs;
|
|
734
|
+
mcxLanguagePluginCreator = null;
|
|
735
|
+
constructor(opts, baseBuildDir, resolve, isWatch = false) {
|
|
736
|
+
this.baseBuildDir = baseBuildDir;
|
|
737
|
+
this.resolve = resolve;
|
|
738
|
+
this.isWatch = isWatch;
|
|
739
|
+
// 初始化 MCX 语言插件创建器,传入 tsHook.ts 使用
|
|
740
|
+
try {
|
|
741
|
+
const tsModule = require("typescript");
|
|
742
|
+
this.mcxLanguagePluginCreator = mcxServer.createMCXLanguagePlugin;
|
|
743
|
+
this.mcxTs = tsModule;
|
|
744
|
+
Logger.i("Build", "MCX Volar language plugin creator initialized successfully");
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
this.mcxTs = require("typescript");
|
|
748
|
+
Logger.w("Build", `Failed to initialize MCX language plugin: ${error}`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Start the watch mode.
|
|
753
|
+
* This will perform an initial build (if not already done) and then
|
|
754
|
+
* start filesystem and rollup watchers.
|
|
755
|
+
* Returns the watcher handles once they are created so that callers
|
|
756
|
+
* (for example tests) can clean them up later.
|
|
757
|
+
*/
|
|
758
|
+
async watch() {
|
|
759
|
+
try {
|
|
760
|
+
onEnd(() => {
|
|
761
|
+
if (this.watchers) {
|
|
762
|
+
this.watchers.chokidar.close();
|
|
763
|
+
this.watchers.rollup.close();
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
await this._watch();
|
|
767
|
+
}
|
|
768
|
+
catch (e) {
|
|
769
|
+
if (e instanceof Error) {
|
|
770
|
+
Logger.e('Watcher', e.stack || e.message);
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
Logger.e('Watcher', e + '');
|
|
774
|
+
}
|
|
775
|
+
showText('MBLER__ERR__WATCHER: ' + e + ' Log at ' + Logger.LogFile);
|
|
776
|
+
this.resolve(1);
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
async start() {
|
|
781
|
+
try {
|
|
782
|
+
return await this.build();
|
|
783
|
+
}
|
|
784
|
+
catch (e) {
|
|
785
|
+
if (e instanceof Error) {
|
|
786
|
+
Logger.e('Build', e.stack || e.message);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
Logger.e('Build', e + '');
|
|
790
|
+
}
|
|
791
|
+
showText('MBLER__ERR__BUILD: ' + e.stack + ' Log at ' + Logger.LogFile);
|
|
792
|
+
this.resolve(1);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Handles returned from the currently-active watchers.
|
|
797
|
+
* Set by {@link createWatcher} and exposed via {@link getWatchers}
|
|
798
|
+
* so that external callers can close them when necessary (e.g. tests).
|
|
799
|
+
*/
|
|
800
|
+
watchers = null;
|
|
801
|
+
/**
|
|
802
|
+
* Returns the watcher handles if watch mode has been started.
|
|
803
|
+
* Can be safely called even before `watch()` has been invoked.
|
|
804
|
+
*/
|
|
805
|
+
getWatchers() {
|
|
806
|
+
return this.watchers;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Close any active watchers. The build process does not automatically
|
|
810
|
+
* terminate the watchers unless the process exits; tests or CLI wrappers
|
|
811
|
+
* can call this method to clean up resources.
|
|
812
|
+
*/
|
|
813
|
+
closeWatchers() {
|
|
814
|
+
if (this.watchers) {
|
|
815
|
+
this.watchers.chokidar.close();
|
|
816
|
+
this.watchers.rollup.close();
|
|
817
|
+
this.watchers = null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
rollupPlugin = null;
|
|
821
|
+
init = false;
|
|
822
|
+
/**
|
|
823
|
+
* Which modules are present in the current project.
|
|
824
|
+
* - "behavior" when only behavior code exists
|
|
825
|
+
* - "resources" when only resource files exist
|
|
826
|
+
* - "all" when both are present
|
|
827
|
+
* This field is populated during `handlerOtherAddon`.
|
|
828
|
+
*/
|
|
829
|
+
module = null;
|
|
830
|
+
/**
|
|
831
|
+
* Determine whether a path refers to a regular file or a directory.
|
|
832
|
+
* Follows symbolic links recursively. Throws if the path exists but
|
|
833
|
+
* is not one of the expected types.
|
|
834
|
+
*
|
|
835
|
+
* @param filePath file system path to inspect
|
|
836
|
+
* @returns "file" or "directory"
|
|
837
|
+
*/
|
|
838
|
+
async fileType(filePath) {
|
|
839
|
+
const stat = await fs__namespace.lstat(filePath);
|
|
840
|
+
if (stat.isFile()) {
|
|
841
|
+
return 'file';
|
|
842
|
+
}
|
|
843
|
+
if (stat.isDirectory()) {
|
|
844
|
+
return 'directory';
|
|
845
|
+
}
|
|
846
|
+
if (stat.isSymbolicLink()) {
|
|
847
|
+
return await this.fileType(await fs__namespace.readlink(filePath));
|
|
848
|
+
}
|
|
849
|
+
throw new Error('[build addon]: invaild file type');
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Perform a single build of the project located at {@link baseBuildDir}.
|
|
853
|
+
* The process is roughly:
|
|
854
|
+
* 1. load and validate the configuration file
|
|
855
|
+
* 2. prepare source and output directory information
|
|
856
|
+
* 3. copy addon files (behavior/resources)
|
|
857
|
+
* 4. generate manifest.json files
|
|
858
|
+
* 5. run rollup to bundle any script entry point
|
|
859
|
+
*
|
|
860
|
+
* If anything goes wrong the promise returned by the public wrapper
|
|
861
|
+
* (`build()` function exported at the bottom of this file) will be
|
|
862
|
+
* resolved with a non-zero code and appropriate log entries will be
|
|
863
|
+
* emitted.
|
|
864
|
+
*/
|
|
865
|
+
async build() {
|
|
866
|
+
const progress = new Postgress(100);
|
|
867
|
+
this.init = true;
|
|
868
|
+
if (!path.isAbsolute(this.baseBuildDir)) {
|
|
869
|
+
throw new Error('[init build]: build dir is not absolute path');
|
|
870
|
+
}
|
|
871
|
+
this.currentConfig = await ReadProjectMblerConfig(this.baseBuildDir);
|
|
872
|
+
this.loadData();
|
|
873
|
+
if (!this.isWatch)
|
|
874
|
+
progress.update(10);
|
|
875
|
+
await this.handlerOtherAddon();
|
|
876
|
+
await this.handlerManifest();
|
|
877
|
+
if (!this.isWatch)
|
|
878
|
+
progress.update(30);
|
|
879
|
+
const rBuild = (await this.createRollup());
|
|
880
|
+
if (!this.rollupPlugin || !this.outdirs) {
|
|
881
|
+
throw new Error(`[build addon]: can't resolve rollup instance`);
|
|
882
|
+
}
|
|
883
|
+
if (!this.isWatch)
|
|
884
|
+
progress.update(50);
|
|
885
|
+
// write script
|
|
886
|
+
let output = this.currentConfig.script?.main;
|
|
887
|
+
if (!output)
|
|
888
|
+
output = "index.js";
|
|
889
|
+
if (path.extname(output) !== "js")
|
|
890
|
+
output = output.slice(0, output.length - path.extname(output).length) + ".js";
|
|
891
|
+
if (this.currentConfig.script)
|
|
892
|
+
await rBuild.write({
|
|
893
|
+
file: join(path.join(this.outdirs.behavior, "scripts"), output),
|
|
894
|
+
format: 'esm',
|
|
895
|
+
sourcemap: false,
|
|
896
|
+
});
|
|
897
|
+
if (!this.isWatch)
|
|
898
|
+
progress.update(70);
|
|
899
|
+
await generateRelease(this);
|
|
900
|
+
if (!this.isWatch)
|
|
901
|
+
progress.update(80);
|
|
902
|
+
if (!this.isWatch)
|
|
903
|
+
this.resolve(0);
|
|
904
|
+
if (!this.isWatch)
|
|
905
|
+
progress.update(100);
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Create and return a Rollup build instance configured for the
|
|
909
|
+
* project's script. The Rollup configuration mirrors the options
|
|
910
|
+
* used by the CLI when running manual builds.
|
|
911
|
+
*
|
|
912
|
+
* Returns undefined if the project does not define a script section
|
|
913
|
+
* (in which case nothing needs to be bundled).
|
|
914
|
+
*/
|
|
915
|
+
async createRollup() {
|
|
916
|
+
if (!this.currentConfig || !this.srcDirs || !this.outdirs)
|
|
917
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
918
|
+
if (!this.currentConfig.script)
|
|
919
|
+
return;
|
|
920
|
+
const main = path.join(this.srcDirs.behavior, 'scripts', this.currentConfig.script.main);
|
|
921
|
+
if (!(await FileExsit(main))) {
|
|
922
|
+
throw new Error(`[build addon]: main script ${main} is not exist: can't resolve entry`);
|
|
923
|
+
}
|
|
924
|
+
const plugin = [
|
|
925
|
+
jsonPlugin(),
|
|
926
|
+
resolvePlugin({
|
|
927
|
+
extensions: ['.ts', '.js', '.json'],
|
|
928
|
+
}),
|
|
929
|
+
commonjs()
|
|
930
|
+
];
|
|
931
|
+
const moduleDir = path.join(this.baseBuildDir, 'node_modules');
|
|
932
|
+
if (!(await FileExsit(moduleDir))) {
|
|
933
|
+
throw new Error(`[build addon]: node_modules is not exist in project root: can't resolve node_modules for rollup: ${moduleDir}`);
|
|
934
|
+
}
|
|
935
|
+
if (this.currentConfig.minify) {
|
|
936
|
+
plugin.push(minifyPlugin({
|
|
937
|
+
format: {
|
|
938
|
+
comments: false,
|
|
939
|
+
},
|
|
940
|
+
compress: {
|
|
941
|
+
unused: true,
|
|
942
|
+
},
|
|
943
|
+
}));
|
|
944
|
+
}
|
|
945
|
+
if (this.currentConfig.script.lang == "ts") {
|
|
946
|
+
const tsconfigPath = path.join(this.baseBuildDir, 'tsconfig.json');
|
|
947
|
+
if (!(await FileExsit(tsconfigPath))) {
|
|
948
|
+
throw new Error(`[build addon]: ts-lang: tsconfig.json is not exist in project root: can't resolve tsconfig for rollup: ${tsconfigPath}`);
|
|
949
|
+
}
|
|
950
|
+
plugin.push(typescript({
|
|
951
|
+
sourceMap: false,
|
|
952
|
+
tsconfig: tsconfigPath,
|
|
953
|
+
exclude: [
|
|
954
|
+
this.outdirs.behavior,
|
|
955
|
+
this.outdirs.resources
|
|
956
|
+
],
|
|
957
|
+
include: [
|
|
958
|
+
this.srcDirs.behavior
|
|
959
|
+
]
|
|
960
|
+
}));
|
|
961
|
+
}
|
|
962
|
+
if (this.currentConfig.script?.lang == 'mcx') {
|
|
963
|
+
try {
|
|
964
|
+
const tsconfigPath = path.join(this.baseBuildDir, 'tsconfig.json');
|
|
965
|
+
if (!(await FileExsit(tsconfigPath))) {
|
|
966
|
+
throw new Error(`[build addon]: ts-lang: tsconfig.json is not exist in project root: can't resolve tsconfig for rollup: ${tsconfigPath}`);
|
|
967
|
+
}
|
|
968
|
+
const pluginConfig = {
|
|
969
|
+
moduleDir: moduleDir,
|
|
970
|
+
tsconfigPath: tsconfigPath,
|
|
971
|
+
sourcemap: false,
|
|
972
|
+
ts: this.mcxTs,
|
|
973
|
+
mcxLanguagePlugin: this.mcxLanguagePluginCreator
|
|
974
|
+
};
|
|
975
|
+
if (this.mcxLanguagePluginCreator) {
|
|
976
|
+
pluginConfig.mcxLanguagePlugin = this.mcxLanguagePluginCreator;
|
|
977
|
+
}
|
|
978
|
+
plugin.push(mcxDef__namespace.plugin(pluginConfig, this.outdirs));
|
|
979
|
+
}
|
|
980
|
+
catch (err) {
|
|
981
|
+
throw new Error(`[build addon]: mcx plugin is required but '@mbler/mcx-core' could not be loaded: ${err}`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// save plugin array for watcher re-use
|
|
985
|
+
this.rollupPlugin = plugin;
|
|
986
|
+
return await rollup__namespace.rollup({
|
|
987
|
+
input: main,
|
|
988
|
+
external: ['@minecraft/server', '@minecraft/server-ui'],
|
|
989
|
+
plugins: plugin,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Internal helper invoked by {@link watch}.
|
|
994
|
+
* Ensures a build has been run before starting the watchers.
|
|
995
|
+
*/
|
|
996
|
+
async _watch() {
|
|
997
|
+
// init build
|
|
998
|
+
if (!this.init) {
|
|
999
|
+
await this.build();
|
|
1000
|
+
}
|
|
1001
|
+
this.createWatcher();
|
|
1002
|
+
// watchers field is populated by createWatcher
|
|
1003
|
+
}
|
|
1004
|
+
isParent(parent, dir) {
|
|
1005
|
+
const relative = path.relative(parent, dir);
|
|
1006
|
+
return (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
|
|
1007
|
+
}
|
|
1008
|
+
isChange(oldObj, newObj, checkKeys) {
|
|
1009
|
+
for (const key of checkKeys) {
|
|
1010
|
+
if (typeof oldObj[key] === 'object' &&
|
|
1011
|
+
typeof newObj[key] === 'object' &&
|
|
1012
|
+
oldObj[key] !== null &&
|
|
1013
|
+
newObj[key] !== null) {
|
|
1014
|
+
if (this.isChange(oldObj[key], newObj[key], Object.getOwnPropertyNames(oldObj[key]))) {
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
else if (oldObj[key] !== newObj[key]) {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return false;
|
|
1023
|
+
}
|
|
1024
|
+
createRollupWatcher() {
|
|
1025
|
+
if (!this.srcDirs ||
|
|
1026
|
+
!this.outdirs ||
|
|
1027
|
+
!this.currentConfig ||
|
|
1028
|
+
!this.rollupPlugin)
|
|
1029
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1030
|
+
let output = this.currentConfig.script?.main;
|
|
1031
|
+
if (!output)
|
|
1032
|
+
output = "index.js";
|
|
1033
|
+
if (path.extname(output) !== "js")
|
|
1034
|
+
output = output.slice(0, output.length - path.extname(output).length) + ".js";
|
|
1035
|
+
const rollupWatcher = rollup__namespace.watch({
|
|
1036
|
+
input: path.join(this.srcDirs.behavior, 'scripts', this.currentConfig?.script?.main || ''),
|
|
1037
|
+
external: ['@minecraft/server', '@minecraft/server-ui'],
|
|
1038
|
+
plugins: this.rollupPlugin,
|
|
1039
|
+
output: {
|
|
1040
|
+
file: join(path.join(this.outdirs.behavior, "scripts"), output),
|
|
1041
|
+
format: 'esm',
|
|
1042
|
+
sourcemap: false,
|
|
1043
|
+
},
|
|
1044
|
+
cache: true,
|
|
1045
|
+
watch: {
|
|
1046
|
+
clearScreen: false,
|
|
1047
|
+
include: path.join(this.srcDirs.behavior, 'scripts/**/*'),
|
|
1048
|
+
exclude: [
|
|
1049
|
+
path.join(this.baseBuildDir, 'node_modules/**/*'),
|
|
1050
|
+
this.outdirs.behavior,
|
|
1051
|
+
this.outdirs.resources,
|
|
1052
|
+
this.outdirs.dist,
|
|
1053
|
+
],
|
|
1054
|
+
},
|
|
1055
|
+
});
|
|
1056
|
+
rollupWatcher.on('change', async (filePath) => {
|
|
1057
|
+
Logger.i('Watcher', `file changed: ${filePath}, start rebuild`);
|
|
1058
|
+
});
|
|
1059
|
+
rollupWatcher.on('event', async (event) => {
|
|
1060
|
+
if (event.code === 'ERROR') {
|
|
1061
|
+
Logger.e('Watcher', `rollup error: ${event.error.stack || event.error}`);
|
|
1062
|
+
showText('MBLER__ERR__ROLLUP: ' +
|
|
1063
|
+
(event.error.stack || event.error) +
|
|
1064
|
+
' Log at ' +
|
|
1065
|
+
Logger.LogFile);
|
|
1066
|
+
}
|
|
1067
|
+
else if (event.code === 'END') {
|
|
1068
|
+
Logger.i('Watcher', `rebuild success`);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
return rollupWatcher;
|
|
1072
|
+
}
|
|
1073
|
+
async onChange(filePath) {
|
|
1074
|
+
if (!this.srcDirs ||
|
|
1075
|
+
!this.outdirs ||
|
|
1076
|
+
!this.currentConfig ||
|
|
1077
|
+
!this.rollupPlugin ||
|
|
1078
|
+
!this.watchers)
|
|
1079
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1080
|
+
const isConfigChange = path.relative(path.join(this.baseBuildDir, 'mbler.config.json'), filePath) === '';
|
|
1081
|
+
const isBehaviorChange = this.isParent(this.srcDirs.behavior, filePath) && !this.isParent(path.join(this.srcDirs.behavior, 'scripts'), filePath);
|
|
1082
|
+
const isResourcesChange = this.isParent(this.srcDirs.resources, filePath);
|
|
1083
|
+
if (isConfigChange) {
|
|
1084
|
+
const oldConfig = this.currentConfig;
|
|
1085
|
+
Logger.i('Watcher', 'detected mbler.config.json change, reload config');
|
|
1086
|
+
this.currentConfig = await ReadProjectMblerConfig(this.baseBuildDir);
|
|
1087
|
+
this.loadData();
|
|
1088
|
+
if (this.isChange(oldConfig, this.currentConfig, [
|
|
1089
|
+
'name',
|
|
1090
|
+
'version',
|
|
1091
|
+
'description',
|
|
1092
|
+
'mcVersion',
|
|
1093
|
+
])) {
|
|
1094
|
+
await this.handlerManifest();
|
|
1095
|
+
}
|
|
1096
|
+
if (this.isChange(oldConfig, this.currentConfig, ['script', 'outdir'])) {
|
|
1097
|
+
this.watchers.rollup.close();
|
|
1098
|
+
await this.createRollup();
|
|
1099
|
+
this.watchers.rollup = rollup__namespace.watch({
|
|
1100
|
+
input: path.join(this.srcDirs.behavior, 'scripts', this.currentConfig?.script?.main || ''),
|
|
1101
|
+
external: ['@minecraft/server', '@minecraft/server-ui'],
|
|
1102
|
+
plugins: this.rollupPlugin,
|
|
1103
|
+
output: {
|
|
1104
|
+
file: path.join(this.outdirs.behavior, 'scripts', this.currentConfig?.script?.main || ''),
|
|
1105
|
+
format: 'esm',
|
|
1106
|
+
},
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
// if behavior or resources change, we can just copy the changed file instead of copy all files again.
|
|
1111
|
+
if (isBehaviorChange || isResourcesChange) {
|
|
1112
|
+
const handlerBP = async () => {
|
|
1113
|
+
if (!this.srcDirs || !this.outdirs)
|
|
1114
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1115
|
+
const relativePath = path.relative(this.srcDirs.behavior, filePath);
|
|
1116
|
+
await fs__namespace.cp(path.join(this.srcDirs.behavior, relativePath), path.join(this.outdirs.behavior, relativePath), {
|
|
1117
|
+
recursive: true,
|
|
1118
|
+
force: true,
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
1121
|
+
const handlerRP = async () => {
|
|
1122
|
+
if (!this.srcDirs || !this.outdirs)
|
|
1123
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1124
|
+
const relativePath = path.relative(this.srcDirs.resources, filePath);
|
|
1125
|
+
await fs__namespace.cp(path.join(this.srcDirs.resources, relativePath), path.join(this.outdirs.resources, relativePath), {
|
|
1126
|
+
recursive: true,
|
|
1127
|
+
force: true,
|
|
1128
|
+
});
|
|
1129
|
+
};
|
|
1130
|
+
if (isBehaviorChange) {
|
|
1131
|
+
await handlerBP();
|
|
1132
|
+
}
|
|
1133
|
+
if (isResourcesChange) {
|
|
1134
|
+
await handlerRP();
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
showText(`[${chalk.green('mbler')}] ${chalk.bgYellow(`file changed: ${filePath}`)}`);
|
|
1138
|
+
}
|
|
1139
|
+
createWatcher() {
|
|
1140
|
+
if (!this.srcDirs || !this.outdirs || !this.rollupPlugin)
|
|
1141
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1142
|
+
const chokidar$1 = chokidar.watch(this.baseBuildDir, {
|
|
1143
|
+
ignored: [
|
|
1144
|
+
this.outdirs.behavior,
|
|
1145
|
+
this.outdirs.resources,
|
|
1146
|
+
this.outdirs.dist,
|
|
1147
|
+
path.join(this.baseBuildDir, 'node_modules'),
|
|
1148
|
+
],
|
|
1149
|
+
ignoreInitial: true,
|
|
1150
|
+
interval: 100,
|
|
1151
|
+
});
|
|
1152
|
+
const onChange = async (filePath) => {
|
|
1153
|
+
await this.onChange(filePath);
|
|
1154
|
+
};
|
|
1155
|
+
chokidar$1.on('change', onChange);
|
|
1156
|
+
const rollupWatcher = this.createRollupWatcher();
|
|
1157
|
+
this.watchers = {
|
|
1158
|
+
chokidar: chokidar$1,
|
|
1159
|
+
rollup: rollupWatcher,
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
async handlerManifest() {
|
|
1163
|
+
if (!this.currentConfig || !this.outdirs || !this.srcDirs || !this.module)
|
|
1164
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1165
|
+
const otherManifestOption = {
|
|
1166
|
+
behavior: {},
|
|
1167
|
+
resources: {},
|
|
1168
|
+
};
|
|
1169
|
+
const handlerBP = async () => {
|
|
1170
|
+
if (!this.outdirs || !this.currentConfig)
|
|
1171
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1172
|
+
const manifest = await generateManifest(this.currentConfig, 'data');
|
|
1173
|
+
await writeJSON(path.join(this.outdirs.behavior, 'manifest.json'), {
|
|
1174
|
+
...manifest,
|
|
1175
|
+
...otherManifestOption.behavior,
|
|
1176
|
+
});
|
|
1177
|
+
};
|
|
1178
|
+
const handlerRP = async () => {
|
|
1179
|
+
if (!this.outdirs || !this.currentConfig)
|
|
1180
|
+
throw new Error(`[build addon]: can't first can this method`);
|
|
1181
|
+
const manifest = await generateManifest(this.currentConfig, 'resources');
|
|
1182
|
+
await writeJSON(path.join(this.outdirs.resources, 'manifest.json'), {
|
|
1183
|
+
...manifest,
|
|
1184
|
+
...otherManifestOption.resources,
|
|
1185
|
+
});
|
|
1186
|
+
};
|
|
1187
|
+
if (this.module == 'behavior' || this.module == 'all') {
|
|
1188
|
+
const filePath = path.join(this.srcDirs.behavior, 'manifest.json');
|
|
1189
|
+
if (await FileExsit(filePath)) {
|
|
1190
|
+
try {
|
|
1191
|
+
const content = await fs__namespace.readFile(filePath, 'utf-8');
|
|
1192
|
+
const json = JSON.parse(content);
|
|
1193
|
+
otherManifestOption.behavior = json;
|
|
1194
|
+
}
|
|
1195
|
+
catch (err) {
|
|
1196
|
+
Logger.w('Build', 'invalid manifest.json in behavior');
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
await handlerBP();
|
|
1200
|
+
}
|
|
1201
|
+
if (this.module == 'resources' || this.module == 'all') {
|
|
1202
|
+
const filePath = path.join(this.srcDirs.resources, 'manifest.json');
|
|
1203
|
+
if (await FileExsit(filePath)) {
|
|
1204
|
+
try {
|
|
1205
|
+
const content = await fs__namespace.readFile(filePath, 'utf-8');
|
|
1206
|
+
const json = JSON.parse(content);
|
|
1207
|
+
otherManifestOption.resources = json;
|
|
1208
|
+
}
|
|
1209
|
+
catch (err) {
|
|
1210
|
+
Logger.w('Build', 'invalid manifest.json in resources');
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
await handlerRP();
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
loadData() {
|
|
1217
|
+
// check run time
|
|
1218
|
+
if (!this.currentConfig || !this.baseBuildDir)
|
|
1219
|
+
throw new Error("[build data]: can't resolve again");
|
|
1220
|
+
// source code dir
|
|
1221
|
+
this.srcDirs = {
|
|
1222
|
+
behavior: path.join(this.baseBuildDir, BuildConfig.behavior),
|
|
1223
|
+
resources: path.join(this.baseBuildDir, BuildConfig.resources), // res
|
|
1224
|
+
};
|
|
1225
|
+
// output dir
|
|
1226
|
+
this.outdirs = {
|
|
1227
|
+
behavior: this.currentConfig.outdir?.behavior
|
|
1228
|
+
? join(this.baseBuildDir, this.currentConfig.outdir.behavior)
|
|
1229
|
+
: path.join(this.baseBuildDir, 'dist/dep'),
|
|
1230
|
+
resources: this.currentConfig.outdir?.resources
|
|
1231
|
+
? join(this.baseBuildDir, this.currentConfig.outdir.resources)
|
|
1232
|
+
: path.join(this.baseBuildDir, 'dist/res'),
|
|
1233
|
+
dist: this.currentConfig.outdir?.dist
|
|
1234
|
+
? join(this.baseBuildDir, this.currentConfig.outdir.dist)
|
|
1235
|
+
: path.join(this.baseBuildDir, 'dist-pkg'),
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Copy the various files (behavior/resources) into the corresponding
|
|
1240
|
+
* output directories and determine which modules exist in the project
|
|
1241
|
+
* by inspecting the source directories.
|
|
1242
|
+
*/
|
|
1243
|
+
async handlerOtherAddon() {
|
|
1244
|
+
if (!this.srcDirs)
|
|
1245
|
+
throw new Error("[build addon]: can't first can this method");
|
|
1246
|
+
const isHasBp = await FileExsit(this.srcDirs.behavior);
|
|
1247
|
+
if (!isHasBp)
|
|
1248
|
+
throw new Error("[build addon]: can't resolve behavior");
|
|
1249
|
+
// init copy resources
|
|
1250
|
+
const handlerBP = async () => {
|
|
1251
|
+
if (!this.srcDirs || !this.outdirs)
|
|
1252
|
+
throw new Error("[build addon]: can't first can this method");
|
|
1253
|
+
for (const f of await fs__namespace.readdir(this.srcDirs.behavior)) {
|
|
1254
|
+
const fType = await this.fileType(path.join(this.srcDirs.behavior, f));
|
|
1255
|
+
const includeType = BuildConfig.includes.behavior[f] || BuildConfig.includes.public[f];
|
|
1256
|
+
if (includeType == fType) {
|
|
1257
|
+
await fs__namespace.cp(path.join(this.srcDirs.behavior, f), path.join(this.outdirs.behavior, f), {
|
|
1258
|
+
recursive: true,
|
|
1259
|
+
force: true,
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
else if (includeType == 'skip') {
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
else {
|
|
1266
|
+
throw new Error(`[build addon]: invaild file: ${path.join(this.srcDirs.behavior, f)}: type: ${fType}`);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
const handlerRP = async () => {
|
|
1271
|
+
if (!this.srcDirs || !this.outdirs)
|
|
1272
|
+
throw new Error("[build addon]: can't first can this method");
|
|
1273
|
+
for (const f of await fs__namespace.readdir(this.srcDirs.resources)) {
|
|
1274
|
+
const fType = await this.fileType(path.join(this.srcDirs.resources, f));
|
|
1275
|
+
const includeType = BuildConfig.includes.resources[f] || BuildConfig.includes.public[f];
|
|
1276
|
+
if (includeType == fType) {
|
|
1277
|
+
await fs__namespace.cp(path.join(this.srcDirs.resources, f), path.join(this.outdirs.resources, f), {
|
|
1278
|
+
recursive: true,
|
|
1279
|
+
force: true,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
else if (includeType == 'skip') {
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
throw new Error(`[build addon]: invaild file: ${path.join(this.srcDirs.resources, f)}: type: ${fType}`);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
const tasks = [];
|
|
1291
|
+
if (await FileExsit(this.srcDirs.behavior)) {
|
|
1292
|
+
this.module = 'behavior';
|
|
1293
|
+
tasks.push(handlerBP());
|
|
1294
|
+
}
|
|
1295
|
+
if (await FileExsit(this.srcDirs.resources)) {
|
|
1296
|
+
if (this.module == 'behavior') {
|
|
1297
|
+
this.module = 'all';
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
this.module = 'resources';
|
|
1301
|
+
}
|
|
1302
|
+
tasks.push(handlerRP());
|
|
1303
|
+
}
|
|
1304
|
+
if (!this.module) {
|
|
1305
|
+
throw new Error("[build addon]: couldn't resolve source code(your behaivor or reources code is not found)");
|
|
1306
|
+
}
|
|
1307
|
+
await Promise.all(tasks);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
function build(cliParam, work) {
|
|
1311
|
+
return new Promise((resolve) => {
|
|
1312
|
+
new Build(cliParam.opts, work, resolve).start();
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
function watch(cliParam, work) {
|
|
1316
|
+
return new Promise((resolve, reject) => {
|
|
1317
|
+
try {
|
|
1318
|
+
const build = new Build(cliParam.opts, work, resolve, true);
|
|
1319
|
+
build.start().then(() => {
|
|
1320
|
+
build.watch();
|
|
1321
|
+
showText(`[${chalk.green('mbler')}] ${chalk.bgYellow('watching for file changes...')}`);
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
catch (err) {
|
|
1325
|
+
if (err instanceof Error) {
|
|
1326
|
+
reject(`[watcher]: error ${err.stack || err.message}`);
|
|
1327
|
+
}
|
|
1328
|
+
else {
|
|
1329
|
+
reject(err);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
exports.Build = Build;
|
|
1336
|
+
exports.McxTsc = runTSC;
|
|
1337
|
+
exports.build = build;
|
|
1338
|
+
exports.watch = watch;
|
|
1339
|
+
//# sourceMappingURL=build.js.map
|