@mbler/mcx-core 0.1.2-rc.1 → 0.1.2-rc.10
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/README.md +40 -5
- package/dist/index.d.ts +7 -7
- package/dist/index.js +87 -49
- package/dist/index.js.map +1 -1
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
|
-
# mcx
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# @mbler/mcx-core
|
|
2
|
+
|
|
3
|
+
The MCX DSL compiler — the core of the MCX ecosystem. Compiles `.mcx` source files into MCBE-compatible JSON components, UI forms, and event systems.
|
|
4
|
+
|
|
5
|
+
## Pipeline
|
|
6
|
+
|
|
7
|
+
`.mcx` file → **parser** → AST → **transform** (Babel) → compiled JS → MCBE JSON
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **MCX Parser** — Parses `.mcx` source into a tokenized AST (`tag`, `prop`)
|
|
12
|
+
- **Transform Pipeline** — Detects file type (`event`, `ui`, `component`, `app`) and generates compiled Babel AST
|
|
13
|
+
- **Component Compilation** — Runs component scripts in a sandboxed VM, generates MCBE JSON, executes file edit operations
|
|
14
|
+
- **CJS Transform** — Rewrites ESM imports to CommonJS `require()` for VM execution
|
|
15
|
+
- **Image Assets** — Compiles PNG, JPG, SVG, GIF image references into MCBE texture assets
|
|
16
|
+
- **Rollup/Rolldown Plugin** — Seamless `.mcx` file resolution and transformation in your build pipeline
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add -D @mbler/mcx-core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
Add the plugin to your Rollup or Rolldown config:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { rollupPlugin } from '@mbler/mcx-core'
|
|
30
|
+
|
|
31
|
+
export default {
|
|
32
|
+
plugins: [rollupPlugin({ moduleDir: './modules', tsconfigPath: './tsconfig.json' })],
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For full documentation, visit the **[Docs](https://mbler-docs.ruanhor.dpdns.org/guide/mcx)**.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as t from "@babel/types";
|
|
2
2
|
import { ArgumentPlaceholder, CallExpression, ExportAllDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, Expression, ImportDeclaration, SpreadElement } from "@babel/types";
|
|
3
3
|
import * as Parser from "@babel/parser";
|
|
4
|
-
import { BlockComponent, BlockComponent as BlockComponent$1, EnchantableSlot, EntityComponent, EntityComponent as EntityComponent$1, EntityComponentOptions,
|
|
4
|
+
import { BlockComponent, BlockComponent as BlockComponent$1, EnchantableSlot, EntityComponent, EntityComponent as EntityComponent$1, EntityComponentOptions, FoodEffect, GIFImageComponent, GIFImageComponent as GIFImageComponent$1, ItemComponent, ItemComponent as ItemComponent$1, ItemComponentOptions, JPGImageComponent, JPGImageComponent as JPGImageComponent$1, PNGImageComponent, PNGImageComponent as PNGImageComponent$1, ParticleType, Rarity, SVGImageComponent, SVGImageComponent as SVGImageComponent$1, SoundEvent } from "@mbler/mcx-component";
|
|
5
5
|
import { Plugin, TransformPluginContext } from "rollup";
|
|
6
6
|
import { CompileOpt, CompileOpt as CompileOpt$1 } from "@mbler/mcx-types";
|
|
7
7
|
import { Plugin as Plugin$1 } from "rolldown";
|
|
@@ -207,7 +207,7 @@ declare class Utils {
|
|
|
207
207
|
static FileExist(path: string): Promise<boolean>;
|
|
208
208
|
static readFile(filePath: string, opt?: ReadFileOpt): Promise<object | string>;
|
|
209
209
|
static sleep(time: number): Promise<void>;
|
|
210
|
-
static TypeVerify<T extends Record<string,
|
|
210
|
+
static TypeVerify<T extends Record<string, unknown>, U extends TypeVerifyBody>(obj: T, types: U): obj is T & { [P in keyof U]: {
|
|
211
211
|
boolean: boolean;
|
|
212
212
|
number: number;
|
|
213
213
|
string: string;
|
|
@@ -263,7 +263,7 @@ declare const compileMCXFn: ((mcxCode: string) => MCXCompileData) & {
|
|
|
263
263
|
cache: Record<string, MCXCompileData>;
|
|
264
264
|
};
|
|
265
265
|
declare namespace types_d_exports {
|
|
266
|
-
export { BaseJson, DefineEntry, EnchantableSlot, EntityComponentOptions,
|
|
266
|
+
export { BaseJson, DefineEntry, EnchantableSlot, EntityComponentOptions, FileBindSource, FileEditExpression, FileEditOption, FilePoint, FoodEffect, ItemComponentOptions, ParticleType, Rarity, SoundEvent, createFileEdit };
|
|
267
267
|
}
|
|
268
268
|
interface FilePoint {
|
|
269
269
|
base: 'behavior' | 'resources' | 'root';
|
|
@@ -293,7 +293,7 @@ type FileEditOption = {
|
|
|
293
293
|
type: 'edit';
|
|
294
294
|
id?: string;
|
|
295
295
|
source: FilePoint | FileBindSource;
|
|
296
|
-
expression: FileEditExpression<
|
|
296
|
+
expression: FileEditExpression<Record<string, DefineEntry>>;
|
|
297
297
|
} | {
|
|
298
298
|
type: 'copy_assets';
|
|
299
299
|
id?: string;
|
|
@@ -353,7 +353,7 @@ declare function clearCachedOptions(): void;
|
|
|
353
353
|
* Resolve a FilePoint to an absolute path on disk.
|
|
354
354
|
*
|
|
355
355
|
* - `base: 'root'` is only allowed when the calling component originates from
|
|
356
|
-
* @mbler/mcx-
|
|
356
|
+
* @mbler/mcx-component (the `sourceIsMcxCore` flag). This prevents third-party
|
|
357
357
|
* components from reading arbitrary filesystem locations.
|
|
358
358
|
* - For `behavior` / `resources`, the file is resolved relative to the
|
|
359
359
|
* corresponding output directory. A path-traversal check ensures the resolved
|
|
@@ -365,7 +365,7 @@ declare function resolveFilePoint(point: FilePoint, ctx: transformCtx, sourceIsM
|
|
|
365
365
|
* Delegates to execEditInternal with a fresh limits counter.
|
|
366
366
|
*
|
|
367
367
|
* @param isMcxCoreSource - when true, the component originates from
|
|
368
|
-
* @mbler/mcx-
|
|
368
|
+
* @mbler/mcx-component and is exempt from file I/O limits and root base
|
|
369
369
|
* restrictions.
|
|
370
370
|
*/
|
|
371
371
|
declare function execEdit(option: BaseJson['_meta']['file_edit'], ctx: transformCtx, isMcxCoreSource?: boolean): Promise<void>;
|
|
@@ -388,7 +388,7 @@ declare function generateItemTextureJson(output: {
|
|
|
388
388
|
*
|
|
389
389
|
* Per-component restrictions (checked after VM execution):
|
|
390
390
|
* - path traversal guard on the output file point
|
|
391
|
-
* - root base: only allowed if the export came from @mbler/mcx-
|
|
391
|
+
* - root base: only allowed if the export came from @mbler/mcx-component
|
|
392
392
|
* - file write limit (≤5) and read limit (≤1): only enforced for
|
|
393
393
|
* non-mcx-core components
|
|
394
394
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { n as __require, t as __exportAll } from "./chunk-DEq-mXcV.js";
|
|
2
2
|
import * as Module from "node:module";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { extname } from "node:path";
|
|
5
6
|
import * as t from "@babel/types";
|
|
6
7
|
import { program } from "@babel/types";
|
|
7
8
|
import * as fs from "node:fs/promises";
|
|
@@ -115,7 +116,7 @@ var Lexer$1 = class {
|
|
|
115
116
|
}
|
|
116
117
|
*tokenStream() {
|
|
117
118
|
const tokenizer = new Tokenizer(this.text);
|
|
118
|
-
for (const token of tokenizer.splitTokens()) {
|
|
119
|
+
for (const token of Array.from(tokenizer.splitTokens())) {
|
|
119
120
|
if (!this.includeComments && token.type === "Comment") continue;
|
|
120
121
|
yield token;
|
|
121
122
|
}
|
|
@@ -192,8 +193,11 @@ var Parser$1 = class {
|
|
|
192
193
|
inKey = false;
|
|
193
194
|
inValue = true;
|
|
194
195
|
const nextIndex = i + 1;
|
|
195
|
-
nextIndex < tagContent.length
|
|
196
|
-
|
|
196
|
+
const nextChar = nextIndex < tagContent.length ? tagContent[nextIndex] : " ";
|
|
197
|
+
if (nextChar === "\"" || nextChar === "'") {
|
|
198
|
+
quoteChar = nextChar;
|
|
199
|
+
i = nextIndex;
|
|
200
|
+
} else quoteChar = " ";
|
|
197
201
|
} else if (char === " " && inKey && currentKey) {
|
|
198
202
|
attributes[currentKey.trim()] = "true";
|
|
199
203
|
currentKey = "";
|
|
@@ -256,9 +260,11 @@ var Parser$1 = class {
|
|
|
256
260
|
else stack.push(node);
|
|
257
261
|
} else if (token.type === "TagEnd") {
|
|
258
262
|
const name = token.data.replace(/^<\/\s*/, "").replace(/\s*>$/, "").trim();
|
|
263
|
+
let matched = false;
|
|
259
264
|
for (let s = stack.length - 1; s >= 0; s--) {
|
|
260
265
|
const candidate = stack[s];
|
|
261
266
|
if (candidate && candidate.name === name) {
|
|
267
|
+
matched = true;
|
|
262
268
|
candidate.end = token;
|
|
263
269
|
candidate.loc.end = { ...token.end };
|
|
264
270
|
stack.splice(s, 1);
|
|
@@ -267,6 +273,7 @@ var Parser$1 = class {
|
|
|
267
273
|
break;
|
|
268
274
|
}
|
|
269
275
|
}
|
|
276
|
+
if (!matched) throw new Error(`Unmatched closing tag </${name}> at line ${token.start.line}, column ${token.start.column}`);
|
|
270
277
|
}
|
|
271
278
|
}
|
|
272
279
|
while (stack.length > 0) {
|
|
@@ -453,9 +460,9 @@ var Utils$1 = class Utils$1 {
|
|
|
453
460
|
const file = await readFile(fileDir, "utf-8");
|
|
454
461
|
if (typeof file !== "string") throw new Error("[read file]: not found file " + fileDir);
|
|
455
462
|
try {
|
|
456
|
-
return Parser.parse(file).program;
|
|
463
|
+
return Parser.parse(file, parserOpt).program;
|
|
457
464
|
} catch (err) {
|
|
458
|
-
throw new Error("[compiler]: babel error" + err.stack);
|
|
465
|
+
throw new Error("[compiler]: babel error" + (err instanceof Error ? err.stack : String(err)));
|
|
459
466
|
}
|
|
460
467
|
}
|
|
461
468
|
static async FileContent(fileDir) {
|
|
@@ -544,21 +551,26 @@ var CompileError = class extends Error {
|
|
|
544
551
|
}
|
|
545
552
|
};
|
|
546
553
|
function extractLoc(node) {
|
|
547
|
-
if (!node) return {
|
|
554
|
+
if (!node || typeof node !== "object") return {
|
|
548
555
|
line: -1,
|
|
549
556
|
column: -1
|
|
550
557
|
};
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
+
const n = node;
|
|
559
|
+
const loc = n.loc;
|
|
560
|
+
if (loc?.start) {
|
|
561
|
+
const start = loc.start;
|
|
562
|
+
return {
|
|
563
|
+
line: typeof start.line === "number" ? start.line : -1,
|
|
564
|
+
column: typeof start.column === "number" ? start.column : -1
|
|
565
|
+
};
|
|
566
|
+
} else if (loc && loc.column !== void 0) return {
|
|
567
|
+
line: typeof loc.line === "number" ? loc.line : -1,
|
|
568
|
+
column: loc.column
|
|
558
569
|
};
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
570
|
+
const start = n.start;
|
|
571
|
+
if (start && typeof start.line === "number") return {
|
|
572
|
+
line: start.line,
|
|
573
|
+
column: typeof start.column === "number" ? start.column : -1
|
|
562
574
|
};
|
|
563
575
|
return {
|
|
564
576
|
line: -1,
|
|
@@ -913,7 +925,7 @@ var Utils = class Utils {
|
|
|
913
925
|
return true;
|
|
914
926
|
}
|
|
915
927
|
static AbsoluteJoin(baseDir, inputPath) {
|
|
916
|
-
return path
|
|
928
|
+
return path.isAbsolute(inputPath) ? inputPath : path.join(baseDir, inputPath);
|
|
917
929
|
}
|
|
918
930
|
};
|
|
919
931
|
//#endregion
|
|
@@ -1054,16 +1066,32 @@ async function Comp$2(ctx) {
|
|
|
1054
1066
|
column: uiClientTag.loc.start.column,
|
|
1055
1067
|
line: uiClientTag.loc.start.line
|
|
1056
1068
|
} : void 0);
|
|
1069
|
+
let _for;
|
|
1070
|
+
if (typeof uiClientTag.arr.for === "string") {
|
|
1071
|
+
const match = uiClientTag.arr.for.match(/^(\w+)\s+in\s+(\w+)$/);
|
|
1072
|
+
if (match) _for = {
|
|
1073
|
+
variable: match[1],
|
|
1074
|
+
useProp: match[2]
|
|
1075
|
+
};
|
|
1076
|
+
else internalCtx.rollupContext.error("[UI]: invalid for syntax, expected 'variable in propName'", uiClientTag.loc ? {
|
|
1077
|
+
column: uiClientTag.loc.start.column,
|
|
1078
|
+
line: uiClientTag.loc.start.line
|
|
1079
|
+
} : void 0);
|
|
1080
|
+
}
|
|
1081
|
+
let _if;
|
|
1082
|
+
if (typeof uiClientTag.arr.if === "string") _if = { useProp: uiClientTag.arr.if };
|
|
1057
1083
|
UITree.push({
|
|
1058
1084
|
arr: uiClientTag.arr,
|
|
1059
1085
|
content: uiClientTag.content.map((i) => i.type == "TagContent" && i.data || "").join(""),
|
|
1060
1086
|
type: uiClientTag.name,
|
|
1061
|
-
loc: uiClientTag.loc
|
|
1087
|
+
loc: uiClientTag.loc,
|
|
1088
|
+
..._for ? { for: _for } : {},
|
|
1089
|
+
..._if ? { if: _if } : {}
|
|
1062
1090
|
});
|
|
1063
1091
|
}
|
|
1064
1092
|
const parsedObj = [];
|
|
1065
|
-
function pushToTree(name, params, content) {
|
|
1066
|
-
|
|
1093
|
+
function pushToTree(name, params, content, _for, _if) {
|
|
1094
|
+
const props = [
|
|
1067
1095
|
t.objectProperty(t.identifier("type"), t.stringLiteral(name)),
|
|
1068
1096
|
t.objectProperty(t.identifier("params"), t.objectExpression(Object.entries(params).map(([key, value]) => {
|
|
1069
1097
|
const isDynamic = key.startsWith(":");
|
|
@@ -1071,10 +1099,16 @@ async function Comp$2(ctx) {
|
|
|
1071
1099
|
return t.objectProperty(t.identifier(paramName), isDynamic ? t.objectExpression([t.objectProperty(t.identifier("useProp"), t.stringLiteral(String(value)))]) : typeof value == "boolean" ? t.booleanLiteral(value) : t.stringLiteral(value));
|
|
1072
1100
|
}))),
|
|
1073
1101
|
t.objectProperty(t.identifier("content"), content.startsWith("{{ ") && content.endsWith(" }}") ? t.objectExpression([t.objectProperty(t.identifier("useProp"), t.stringLiteral(content.slice(3, content.length - 3).trim()))]) : t.stringLiteral(content))
|
|
1074
|
-
]
|
|
1102
|
+
];
|
|
1103
|
+
if (_for) props.push(t.objectProperty(t.identifier("for"), t.objectExpression([t.objectProperty(t.identifier("variable"), t.stringLiteral(_for.variable)), t.objectProperty(t.identifier("useProp"), t.stringLiteral(_for.useProp))])));
|
|
1104
|
+
if (_if) props.push(t.objectProperty(t.identifier("if"), t.objectExpression([t.objectProperty(t.identifier("useProp"), t.stringLiteral(_if.useProp))])));
|
|
1105
|
+
parsedObj.push(t.objectExpression(props));
|
|
1075
1106
|
}
|
|
1076
1107
|
for (const tp of UITree) {
|
|
1077
1108
|
const name = tp.type;
|
|
1109
|
+
const cleanedArr = { ...tp.arr };
|
|
1110
|
+
delete cleanedArr.for;
|
|
1111
|
+
delete cleanedArr.if;
|
|
1078
1112
|
if ([
|
|
1079
1113
|
"input",
|
|
1080
1114
|
"dropdown",
|
|
@@ -1087,26 +1121,26 @@ async function Comp$2(ctx) {
|
|
|
1087
1121
|
column: tp.loc.start.column
|
|
1088
1122
|
} : void 0);
|
|
1089
1123
|
MCXUIType = "ModalFormData";
|
|
1090
|
-
pushToTree(name, tp.
|
|
1124
|
+
pushToTree(name, cleanedArr, tp.content, tp.for, tp.if);
|
|
1091
1125
|
} else if (["button-m"].includes(name)) {
|
|
1092
1126
|
if (MCXUIType && MCXUIType !== "MessageFormData") internalCtx.rollupContext.error("[UI]: ", tp.loc ? {
|
|
1093
1127
|
line: tp.loc.start.line,
|
|
1094
1128
|
column: tp.loc.start.column
|
|
1095
1129
|
} : void 0);
|
|
1096
1130
|
MCXUIType = "MessageFormData";
|
|
1097
|
-
pushToTree(name, tp.
|
|
1131
|
+
pushToTree(name, cleanedArr, tp.content, tp.for, tp.if);
|
|
1098
1132
|
} else if ([
|
|
1099
1133
|
"body",
|
|
1100
1134
|
"divider",
|
|
1101
1135
|
"title",
|
|
1102
1136
|
"label"
|
|
1103
|
-
].includes(name)) pushToTree(name, tp.
|
|
1137
|
+
].includes(name)) pushToTree(name, cleanedArr, tp.content, tp.for, tp.if);
|
|
1104
1138
|
else if (name == "button") {
|
|
1105
1139
|
if (MCXUIType !== "ActionFormData" && MCXUIType) internalCtx.rollupContext.error("[UI]: don't support use button for messageFormData", tp.loc ? {
|
|
1106
1140
|
line: tp.loc.start.line,
|
|
1107
1141
|
column: tp.loc.start.column
|
|
1108
1142
|
} : void 0);
|
|
1109
|
-
pushToTree(name, tp.
|
|
1143
|
+
pushToTree(name, cleanedArr, tp.content, tp.for, tp.if);
|
|
1110
1144
|
MCXUIType = "ActionFormData";
|
|
1111
1145
|
} else internalCtx.rollupContext.error("[UI]: don't support tag: " + name, tp.loc ? {
|
|
1112
1146
|
line: tp.loc.start.line,
|
|
@@ -1391,7 +1425,7 @@ var mcx_component_exports = /* @__PURE__ */ __exportAll({
|
|
|
1391
1425
|
let cachedOption = {};
|
|
1392
1426
|
/**
|
|
1393
1427
|
* Security limits for file I/O operations inside file_edit expressions.
|
|
1394
|
-
* Components from @mbler/mcx-
|
|
1428
|
+
* Components from @mbler/mcx-component are exempt from these limits.
|
|
1395
1429
|
*/
|
|
1396
1430
|
const MAX_FILE_WRITES = 5;
|
|
1397
1431
|
const MAX_FILE_READS = 1;
|
|
@@ -1403,7 +1437,7 @@ function clearCachedOptions() {
|
|
|
1403
1437
|
* Resolve a FilePoint to an absolute path on disk.
|
|
1404
1438
|
*
|
|
1405
1439
|
* - `base: 'root'` is only allowed when the calling component originates from
|
|
1406
|
-
* @mbler/mcx-
|
|
1440
|
+
* @mbler/mcx-component (the `sourceIsMcxCore` flag). This prevents third-party
|
|
1407
1441
|
* components from reading arbitrary filesystem locations.
|
|
1408
1442
|
* - For `behavior` / `resources`, the file is resolved relative to the
|
|
1409
1443
|
* corresponding output directory. A path-traversal check ensures the resolved
|
|
@@ -1411,7 +1445,7 @@ function clearCachedOptions() {
|
|
|
1411
1445
|
*/
|
|
1412
1446
|
function resolveFilePoint(point, ctx, sourceIsMcxCore = false) {
|
|
1413
1447
|
if (point.base === "root") {
|
|
1414
|
-
if (!sourceIsMcxCore) throw new Error("[mcx component]: \"root\" base is only allowed for components imported from @mbler/mcx-
|
|
1448
|
+
if (!sourceIsMcxCore) throw new Error("[mcx component]: \"root\" base is only allowed for components imported from @mbler/mcx-component");
|
|
1415
1449
|
return path.resolve(point.file);
|
|
1416
1450
|
}
|
|
1417
1451
|
let baseDir;
|
|
@@ -1469,13 +1503,13 @@ function collectExportSources(code) {
|
|
|
1469
1503
|
return sources;
|
|
1470
1504
|
}
|
|
1471
1505
|
/**
|
|
1472
|
-
* Validate that the component only imports from @mbler/mcx-
|
|
1506
|
+
* Validate that the component only imports from @mbler/mcx-component.
|
|
1473
1507
|
* Non-mcx-core imports are collected and each unique offending package
|
|
1474
1508
|
* triggers a console warning (once per package per file).
|
|
1475
1509
|
* Relative imports ('./...', '../...') are silently allowed.
|
|
1476
1510
|
*/
|
|
1477
1511
|
function checkComponentImports(sources, filePath) {
|
|
1478
|
-
const allowedPackage = "@mbler/mcx-
|
|
1512
|
+
const allowedPackage = "@mbler/mcx-component";
|
|
1479
1513
|
const warned = /* @__PURE__ */ new Set();
|
|
1480
1514
|
for (const [, pkg] of Object.entries(sources)) if (pkg && !pkg.startsWith(allowedPackage) && !pkg.startsWith(".") && !warned.has(pkg)) {
|
|
1481
1515
|
warned.add(pkg);
|
|
@@ -1487,7 +1521,7 @@ function checkComponentImports(sources, filePath) {
|
|
|
1487
1521
|
* Delegates to execEditInternal with a fresh limits counter.
|
|
1488
1522
|
*
|
|
1489
1523
|
* @param isMcxCoreSource - when true, the component originates from
|
|
1490
|
-
* @mbler/mcx-
|
|
1524
|
+
* @mbler/mcx-component and is exempt from file I/O limits and root base
|
|
1491
1525
|
* restrictions.
|
|
1492
1526
|
*/
|
|
1493
1527
|
async function execEdit(option, ctx, isMcxCoreSource = false) {
|
|
@@ -1579,7 +1613,7 @@ async function generateItemTextureJson(output) {
|
|
|
1579
1613
|
*
|
|
1580
1614
|
* Per-component restrictions (checked after VM execution):
|
|
1581
1615
|
* - path traversal guard on the output file point
|
|
1582
|
-
* - root base: only allowed if the export came from @mbler/mcx-
|
|
1616
|
+
* - root base: only allowed if the export came from @mbler/mcx-component
|
|
1583
1617
|
* - file write limit (≤5) and read limit (≤1): only enforced for
|
|
1584
1618
|
* non-mcx-core components
|
|
1585
1619
|
*/
|
|
@@ -1593,7 +1627,7 @@ async function compileComponent(compiledCode, ctx) {
|
|
|
1593
1627
|
const arg = data.arguments[0].arguments[0];
|
|
1594
1628
|
if (arg && arg.type == "StringLiteral") {
|
|
1595
1629
|
if (/^.+?\.(png|svg|jpg|jpeg|gif)$/.test(arg.value)) {
|
|
1596
|
-
const imageComponentRequire = t.memberExpression(t.callExpression(t.identifier("require"), [t.stringLiteral("@mbler/mcx-
|
|
1630
|
+
const imageComponentRequire = t.memberExpression(t.callExpression(t.identifier("require"), [t.stringLiteral("@mbler/mcx-component")]), t.identifier({
|
|
1597
1631
|
png: "PNGImageComponent",
|
|
1598
1632
|
svg: "SVGImageComponent",
|
|
1599
1633
|
jpg: "JPGImageComponent",
|
|
@@ -1617,7 +1651,7 @@ async function compileComponent(compiledCode, ctx) {
|
|
|
1617
1651
|
const json = pointData.toJSON();
|
|
1618
1652
|
if (!json._meta || !json._meta.type || !["item", "entity"].includes(json._meta.type)) throw new Error("[mcx component]: not mcx json component: unknown type");
|
|
1619
1653
|
if (json._meta.file_edit) {
|
|
1620
|
-
const isMcxCoreSource = (exportSources
|
|
1654
|
+
const isMcxCoreSource = Object.values(exportSources).some((src) => src && src.startsWith("@mbler/mcx-component"));
|
|
1621
1655
|
await execEdit(json._meta.file_edit, ctx, isMcxCoreSource);
|
|
1622
1656
|
}
|
|
1623
1657
|
delete json["_meta"];
|
|
@@ -1701,6 +1735,7 @@ async function transform(code, cache, id, context, opt, output) {
|
|
|
1701
1735
|
});
|
|
1702
1736
|
} catch (err) {
|
|
1703
1737
|
context.error(createErrorProxy(err, id));
|
|
1738
|
+
return;
|
|
1704
1739
|
}
|
|
1705
1740
|
}
|
|
1706
1741
|
//#endregion
|
|
@@ -1761,17 +1796,19 @@ function createMcxPlugin(opt, output) {
|
|
|
1761
1796
|
if (exports) {
|
|
1762
1797
|
const subImport = subPath.startsWith("./") ? subPath : `./${subPath}`;
|
|
1763
1798
|
if (typeof exports === "object" && exports !== null) {
|
|
1764
|
-
|
|
1765
|
-
|
|
1799
|
+
const exp = exports;
|
|
1800
|
+
if (exp[subImport]) {
|
|
1801
|
+
const target = exp[subImport];
|
|
1766
1802
|
if (typeof target === "string") return path.join(pkgDir, target);
|
|
1767
1803
|
else if (typeof target === "object" && target !== null) {
|
|
1768
|
-
|
|
1769
|
-
return path.join(pkgDir,
|
|
1804
|
+
const targetObj = target;
|
|
1805
|
+
if (targetObj.import) return path.join(pkgDir, targetObj.import);
|
|
1806
|
+
return path.join(pkgDir, targetObj.default || Object.values(targetObj)[0]);
|
|
1770
1807
|
}
|
|
1771
1808
|
}
|
|
1772
1809
|
if (subImport.endsWith("/") || subImport.endsWith("/*")) {
|
|
1773
1810
|
const dirMapping = subImport.slice(0, -1);
|
|
1774
|
-
for (const [key, value] of Object.entries(
|
|
1811
|
+
for (const [key, value] of Object.entries(exp)) if (key.startsWith(dirMapping) && key !== dirMapping) {
|
|
1775
1812
|
const target = value;
|
|
1776
1813
|
return path.join(pkgDir, target);
|
|
1777
1814
|
}
|
|
@@ -1792,6 +1829,10 @@ function createMcxPlugin(opt, output) {
|
|
|
1792
1829
|
}
|
|
1793
1830
|
return null;
|
|
1794
1831
|
} else {
|
|
1832
|
+
if (imp) try {
|
|
1833
|
+
const resolved = createRequire(imp).resolve(id);
|
|
1834
|
+
if (resolved) return resolved;
|
|
1835
|
+
} catch {}
|
|
1795
1836
|
const isScopedPackage = id.startsWith("@");
|
|
1796
1837
|
const parts = id.split("/");
|
|
1797
1838
|
const pkgName = isScopedPackage ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
@@ -1827,14 +1868,11 @@ function createMcxPlugin(opt, output) {
|
|
|
1827
1868
|
compileData = cache.has(id) ? cache.get(id) : compileMCXFn(code);
|
|
1828
1869
|
cache.set(id, compileData);
|
|
1829
1870
|
} catch (err) {
|
|
1830
|
-
if (err instanceof CompileError) {
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
this.error(err instanceof Error ? `${err.message} : ${err.stack}` : String(err));
|
|
1871
|
+
if (err instanceof CompileError) this.error(err.message, {
|
|
1872
|
+
column: err.loc.column,
|
|
1873
|
+
line: err.loc.line
|
|
1874
|
+
});
|
|
1875
|
+
else this.error(err instanceof Error ? `${err.message} : ${err.stack}` : String(err));
|
|
1838
1876
|
return;
|
|
1839
1877
|
}
|
|
1840
1878
|
compileData.setFilePath(id);
|