kustom-mc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +809 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.js +447 -0
- package/dist/commands/bundle.d.ts +2 -0
- package/dist/commands/bundle.js +134 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +219 -0
- package/dist/commands/list.d.ts +10 -0
- package/dist/commands/list.js +167 -0
- package/dist/commands/login.d.ts +9 -0
- package/dist/commands/login.js +167 -0
- package/dist/commands/new.d.ts +2 -0
- package/dist/commands/new.js +132 -0
- package/dist/commands/prepare.d.ts +9 -0
- package/dist/commands/prepare.js +267 -0
- package/dist/commands/push.d.ts +9 -0
- package/dist/commands/push.js +205 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.js +191 -0
- package/dist/compiler/async-transform.d.ts +21 -0
- package/dist/compiler/async-transform.js +158 -0
- package/dist/compiler/inline.d.ts +32 -0
- package/dist/compiler/inline.js +87 -0
- package/dist/compiler/postprocess.d.ts +19 -0
- package/dist/compiler/postprocess.js +134 -0
- package/dist/compiler/rhino-plugin.d.ts +17 -0
- package/dist/compiler/rhino-plugin.js +324 -0
- package/dist/compiler/transform.d.ts +18 -0
- package/dist/compiler/transform.js +59 -0
- package/dist/config.d.ts +86 -0
- package/dist/config.js +166 -0
- package/dist/credentials.d.ts +65 -0
- package/dist/credentials.js +136 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +28 -0
- package/dist/runtime.d.ts +116 -0
- package/dist/runtime.js +96 -0
- package/dist/types/globals.d.ts +80 -0
- package/dist/types/globals.js +10 -0
- package/dist/types/index.d.ts +2094 -0
- package/dist/types/index.js +9 -0
- package/package.json +57 -0
- package/templates/project/kustom.config.json +26 -0
- package/templates/project/scripts/example.ts +17 -0
- package/templates/project/scripts/lib/utils.ts +19 -0
- package/templates/project/tsconfig.json +27 -0
- package/templates/scripts/block.ts.hbs +14 -0
- package/templates/scripts/gui.ts.hbs +28 -0
- package/templates/scripts/item.ts.hbs +13 -0
- package/templates/scripts/script.ts.hbs +18 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* esbuild plugin for Rhino compatibility.
|
|
3
|
+
*
|
|
4
|
+
* Transforms bundled JavaScript output to be compatible with the Rhino JS engine:
|
|
5
|
+
* 1. Transforms relative script imports to __kustom_import() calls
|
|
6
|
+
* 2. Strips kustom-mc imports (resolved to Java-injected globals)
|
|
7
|
+
* 3. Injects __type property into defineScript/defineBlock/defineItem calls
|
|
8
|
+
* 4. Converts ESM export syntax to CommonJS exports
|
|
9
|
+
* 5. Transforms destructuring function parameters
|
|
10
|
+
* 6. Converts modern JS features to ES5-compatible code
|
|
11
|
+
*/
|
|
12
|
+
export function rhinoCompatPlugin() {
|
|
13
|
+
return {
|
|
14
|
+
name: 'rhino-compat',
|
|
15
|
+
setup(build) {
|
|
16
|
+
build.onEnd(async (result) => {
|
|
17
|
+
if (result.errors.length > 0)
|
|
18
|
+
return;
|
|
19
|
+
if (!result.outputFiles)
|
|
20
|
+
return;
|
|
21
|
+
for (const file of result.outputFiles) {
|
|
22
|
+
if (file.path.endsWith('.js')) {
|
|
23
|
+
const transformed = transformForRhino(file.text);
|
|
24
|
+
file.contents = new TextEncoder().encode(transformed);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Transform JavaScript code for Rhino compatibility.
|
|
33
|
+
*/
|
|
34
|
+
export function transformForRhino(code) {
|
|
35
|
+
let result = code;
|
|
36
|
+
// 1. Transform relative script imports to __kustom_import() calls
|
|
37
|
+
// This must happen BEFORE removing other imports
|
|
38
|
+
result = transformScriptImports(result);
|
|
39
|
+
// 2. Remove kustom-mc imports (resolved to Java-injected globals)
|
|
40
|
+
result = result.replace(/^import\s+.*?from\s+['"]kustom-mc['"];?\s*$/gm, '');
|
|
41
|
+
result = result.replace(/^import\s+['"]kustom-mc['"];?\s*$/gm, '');
|
|
42
|
+
// 3. Remove any remaining import statements (shouldn't be any, but just in case)
|
|
43
|
+
result = result.replace(/^import\s+.*?from\s+['"][^'"]+['"];?\s*$/gm, '');
|
|
44
|
+
result = result.replace(/^import\s+['"][^'"]+['"];?\s*$/gm, '');
|
|
45
|
+
// 4. Transform defineScript/defineBlock/defineItem calls
|
|
46
|
+
// Pattern: var xxx = defineScript({ ... });
|
|
47
|
+
// Result: var xxx = { __type: "script", ... };
|
|
48
|
+
result = transformDefineCall(result, 'defineScript', 'script');
|
|
49
|
+
result = transformDefineCall(result, 'defineBlock', 'block');
|
|
50
|
+
result = transformDefineCall(result, 'defineItem', 'item');
|
|
51
|
+
// 5. Handle ESM export: export { xxx as default };
|
|
52
|
+
result = result.replace(/export\s*\{\s*(\w+)\s+as\s+default\s*\}\s*;?/g, 'exports.default = $1;');
|
|
53
|
+
// 6. Handle other ESM exports: export { foo, bar };
|
|
54
|
+
result = result.replace(/export\s*\{\s*([^}]+)\s*\}\s*;?/g, (match, exports) => {
|
|
55
|
+
const exportList = exports.split(',').map((e) => e.trim());
|
|
56
|
+
return exportList
|
|
57
|
+
.map((e) => {
|
|
58
|
+
const parts = e.split(/\s+as\s+/);
|
|
59
|
+
const localName = parts[0].trim();
|
|
60
|
+
const exportName = parts[1]?.trim() || localName;
|
|
61
|
+
return `exports.${exportName} = ${localName};`;
|
|
62
|
+
})
|
|
63
|
+
.join('\n');
|
|
64
|
+
});
|
|
65
|
+
// 7. Transform destructuring in function params (including shorthand methods)
|
|
66
|
+
result = transformDestructuringParams(result);
|
|
67
|
+
// 8. Transform shorthand methods: onClick(event) { -> onClick: function(event) {
|
|
68
|
+
result = transformShorthandMethods(result);
|
|
69
|
+
// 9. Convert optional chaining ?. to regular access
|
|
70
|
+
result = result.replace(/\?\./g, '.');
|
|
71
|
+
// 10. Convert nullish coalescing ?? to ||
|
|
72
|
+
result = result.replace(/\?\?/g, '||');
|
|
73
|
+
// 11. Convert const/let to var
|
|
74
|
+
result = result.replace(/\bconst\s+/g, 'var ');
|
|
75
|
+
result = result.replace(/\blet\s+/g, 'var ');
|
|
76
|
+
// 12. Ensure exports object exists at the top
|
|
77
|
+
if (!result.includes('var exports =') && !result.startsWith('var exports')) {
|
|
78
|
+
result = 'var exports = exports || {};\n' + result;
|
|
79
|
+
}
|
|
80
|
+
// 13. Clean up empty lines and multiple semicolons
|
|
81
|
+
result = result.replace(/^\s*\n/gm, '');
|
|
82
|
+
result = result.replace(/;;+/g, ';');
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Transform a define call (defineScript, defineBlock, defineItem) to inject __type.
|
|
87
|
+
*
|
|
88
|
+
* Input: var xxx = defineScript({ props: {}, run() {} });
|
|
89
|
+
* Output: var xxx = { __type: "script", props: {}, run() {} };
|
|
90
|
+
*/
|
|
91
|
+
function transformDefineCall(code, funcName, typeName) {
|
|
92
|
+
// Match: var xxx = defineScript(
|
|
93
|
+
const pattern = new RegExp(`(var\\s+\\w+\\s*=\\s*)${funcName}\\s*\\(\\s*\\{`, 'g');
|
|
94
|
+
let result = code.replace(pattern, `$1{ __type: "${typeName}",`);
|
|
95
|
+
// Now we need to remove the extra closing ) from defineScript({...});
|
|
96
|
+
// This is tricky because we need to find the matching closing brace
|
|
97
|
+
// Simple approach: find defineScript pattern and track braces
|
|
98
|
+
// After the above replacement, we have patterns like:
|
|
99
|
+
// var xxx = { __type: "script", ... });
|
|
100
|
+
// We need to change }); to };
|
|
101
|
+
// Look for the pattern where we have __type followed eventually by });
|
|
102
|
+
// This is a simplified approach - find }); that follows __type declaration
|
|
103
|
+
if (result.includes(`__type: "${typeName}"`)) {
|
|
104
|
+
// Find all occurrences and fix the closing
|
|
105
|
+
result = fixClosingParens(result, typeName);
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Fix the closing parenthesis after transforming defineX calls.
|
|
111
|
+
* Changes }); to }; for objects that have __type property.
|
|
112
|
+
*/
|
|
113
|
+
function fixClosingParens(code, typeName) {
|
|
114
|
+
// Find positions where __type: "typeName" appears
|
|
115
|
+
const typePattern = new RegExp(`__type:\\s*"${typeName}"`, 'g');
|
|
116
|
+
let match;
|
|
117
|
+
let result = code;
|
|
118
|
+
// Process one at a time, recalculating position each time
|
|
119
|
+
while ((match = typePattern.exec(result)) !== null) {
|
|
120
|
+
const pos = match.index;
|
|
121
|
+
// Find the opening brace before __type
|
|
122
|
+
let braceStart = result.lastIndexOf('{', pos);
|
|
123
|
+
if (braceStart === -1)
|
|
124
|
+
continue;
|
|
125
|
+
// Find matching closing brace
|
|
126
|
+
let depth = 1;
|
|
127
|
+
let i = braceStart + 1;
|
|
128
|
+
// Track if we're inside a string to avoid matching braces in strings
|
|
129
|
+
let inString = false;
|
|
130
|
+
let stringChar = '';
|
|
131
|
+
while (i < result.length && depth > 0) {
|
|
132
|
+
const char = result[i];
|
|
133
|
+
const prevChar = result[i - 1];
|
|
134
|
+
// Handle string detection (skip escaped quotes)
|
|
135
|
+
if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
|
|
136
|
+
if (!inString) {
|
|
137
|
+
inString = true;
|
|
138
|
+
stringChar = char;
|
|
139
|
+
}
|
|
140
|
+
else if (char === stringChar) {
|
|
141
|
+
inString = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!inString) {
|
|
145
|
+
if (char === '{')
|
|
146
|
+
depth++;
|
|
147
|
+
else if (char === '}')
|
|
148
|
+
depth--;
|
|
149
|
+
}
|
|
150
|
+
i++;
|
|
151
|
+
}
|
|
152
|
+
// i now points to just after the closing }
|
|
153
|
+
// Check if followed by );
|
|
154
|
+
const after = result.substring(i).match(/^\s*\)\s*;/);
|
|
155
|
+
if (after) {
|
|
156
|
+
// Replace }); with };
|
|
157
|
+
result = result.substring(0, i) + ';' + result.substring(i + after[0].length);
|
|
158
|
+
// Reset the regex lastIndex since we modified the string
|
|
159
|
+
typePattern.lastIndex = 0;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Transform destructuring in function parameters.
|
|
166
|
+
*
|
|
167
|
+
* Input: run({ executor, props }) { ... }
|
|
168
|
+
* Output: run: function(ctx) { var executor = ctx.executor; var props = ctx.props; ... }
|
|
169
|
+
*
|
|
170
|
+
* Also handles generator functions from Babel's async/await transform:
|
|
171
|
+
* Input: function* ({ process, props }) { ... }
|
|
172
|
+
* Output: function* (_ctx) { var process = _ctx.process; var props = _ctx.props; ... }
|
|
173
|
+
*/
|
|
174
|
+
function transformDestructuringParams(code) {
|
|
175
|
+
let result = code;
|
|
176
|
+
// First, handle generator functions with destructuring (from Babel async transform)
|
|
177
|
+
// Match: function* ({ param1, param2 }) {
|
|
178
|
+
result = result.replace(/function\*\s*\(\s*\{\s*([^}]+)\s*\}\s*\)\s*\{/g, (match, params) => {
|
|
179
|
+
const paramList = params.split(',').map((p) => p.trim()).filter((p) => p);
|
|
180
|
+
const assignments = paramList
|
|
181
|
+
.map((p) => {
|
|
182
|
+
const [name] = p.split('=').map(s => s.trim());
|
|
183
|
+
return `var ${name} = _ctx.${name};`;
|
|
184
|
+
})
|
|
185
|
+
.join(' ');
|
|
186
|
+
return `function* (_ctx) { ${assignments}`;
|
|
187
|
+
});
|
|
188
|
+
// Handle regular function keyword with destructuring
|
|
189
|
+
// Match: function ({ param1, param2 }) {
|
|
190
|
+
result = result.replace(/function\s*\(\s*\{\s*([^}]+)\s*\}\s*\)\s*\{/g, (match, params) => {
|
|
191
|
+
const paramList = params.split(',').map((p) => p.trim()).filter((p) => p);
|
|
192
|
+
const assignments = paramList
|
|
193
|
+
.map((p) => {
|
|
194
|
+
const [name] = p.split('=').map(s => s.trim());
|
|
195
|
+
return `var ${name} = _ctx.${name};`;
|
|
196
|
+
})
|
|
197
|
+
.join(' ');
|
|
198
|
+
return `function(_ctx) { ${assignments}`;
|
|
199
|
+
});
|
|
200
|
+
// Handle methods with destructuring
|
|
201
|
+
// Match: methodName({ param1, param2 }) {
|
|
202
|
+
result = result.replace(/(\w+)\s*\(\s*\{\s*([^}]+)\s*\}\s*\)\s*\{/g, (match, methodName, params) => {
|
|
203
|
+
// Skip keywords that we've already handled or shouldn't transform
|
|
204
|
+
const keywords = ['function', 'if', 'while', 'for', 'switch', 'catch', 'with'];
|
|
205
|
+
if (keywords.includes(methodName)) {
|
|
206
|
+
return match; // Already handled above
|
|
207
|
+
}
|
|
208
|
+
// For methods, convert to function syntax and transform params
|
|
209
|
+
const paramList = params.split(',').map((p) => p.trim()).filter((p) => p);
|
|
210
|
+
const assignments = paramList
|
|
211
|
+
.map((p) => {
|
|
212
|
+
const [name] = p.split('=').map(s => s.trim());
|
|
213
|
+
return `var ${name} = _ctx.${name};`;
|
|
214
|
+
})
|
|
215
|
+
.join(' ');
|
|
216
|
+
return `${methodName}: function(_ctx) { ${assignments}`;
|
|
217
|
+
});
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Transform shorthand methods to function syntax.
|
|
222
|
+
*
|
|
223
|
+
* Input: onClick(event) { ... }
|
|
224
|
+
* Output: onClick: function(event) { ... }
|
|
225
|
+
*
|
|
226
|
+
* Only transforms methods inside object literals (preceded by { or ,)
|
|
227
|
+
*/
|
|
228
|
+
function transformShorthandMethods(code) {
|
|
229
|
+
// Match: methodName(params) { preceded by { or , (indicating object literal method)
|
|
230
|
+
// We need to be careful to only transform actual shorthand methods, not function declarations
|
|
231
|
+
return code.replace(/([{,]\s*)(\w+)\s*\(([^)]*)\)\s*\{/g, (match, prefix, methodName, params) => {
|
|
232
|
+
// Skip keywords (shouldn't happen but just in case)
|
|
233
|
+
const keywords = ['function', 'if', 'while', 'for', 'switch', 'catch', 'with', 'return'];
|
|
234
|
+
if (keywords.includes(methodName)) {
|
|
235
|
+
return match;
|
|
236
|
+
}
|
|
237
|
+
// Skip if params contain destructuring (already handled by transformDestructuringParams)
|
|
238
|
+
if (params.includes('{')) {
|
|
239
|
+
return match;
|
|
240
|
+
}
|
|
241
|
+
return `${prefix}${methodName}: function(${params}) {`;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Transform script imports to __kustom_import() or __kustom_cross_import() calls.
|
|
246
|
+
*
|
|
247
|
+
* Handles:
|
|
248
|
+
* - Relative imports: import foo from "./foo" → var foo = __kustom_import("./foo");
|
|
249
|
+
* - Cross-pack imports: import foo from "otherpack:scripts/foo" → var foo = __kustom_cross_import("otherpack", "scripts/foo");
|
|
250
|
+
* - Named imports: import { run } from "./foo" → var { run } = __kustom_import("./foo");
|
|
251
|
+
* - Namespace imports: import * as foo from "./foo" → var foo = __kustom_import("./foo");
|
|
252
|
+
*
|
|
253
|
+
* Cross-pack import format: packId:path (e.g., "core-pack:scripts/utils")
|
|
254
|
+
*/
|
|
255
|
+
function transformScriptImports(code) {
|
|
256
|
+
let result = code;
|
|
257
|
+
// Pattern 1a: Default import from cross-pack - import foo from "packId:path"
|
|
258
|
+
// Transform to: var foo = __kustom_cross_import("packId", "path");
|
|
259
|
+
result = result.replace(/^import\s+(\w+)\s+from\s+['"]([a-z0-9-]+):([^'"]+)['"];?\s*$/gm, (match, varName, packId, importPath) => {
|
|
260
|
+
const normalizedPath = normalizeImportPath(importPath);
|
|
261
|
+
return `var ${varName} = __kustom_cross_import("${packId}", "${normalizedPath}");`;
|
|
262
|
+
});
|
|
263
|
+
// Pattern 1b: Default import from relative path - import foo from "./path"
|
|
264
|
+
// Transform to: var foo = __kustom_import("./path");
|
|
265
|
+
result = result.replace(/^import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"];?\s*$/gm, (match, varName, importPath) => {
|
|
266
|
+
// Normalize path: remove .ts/.js extension if present
|
|
267
|
+
const normalizedPath = normalizeImportPath(importPath);
|
|
268
|
+
return `var ${varName} = __kustom_import("${normalizedPath}");`;
|
|
269
|
+
});
|
|
270
|
+
// Pattern 2a: Named imports from cross-pack - import { foo, bar } from "packId:path"
|
|
271
|
+
// Transform to: var __import = __kustom_cross_import("packId", "path"); var foo = __import.foo; var bar = __import.bar;
|
|
272
|
+
result = result.replace(/^import\s+\{\s*([^}]+)\s*\}\s+from\s+['"]([a-z0-9-]+):([^'"]+)['"];?\s*$/gm, (match, imports, packId, importPath) => {
|
|
273
|
+
const normalizedPath = normalizeImportPath(importPath);
|
|
274
|
+
const importList = imports.split(',').map((i) => i.trim()).filter((i) => i);
|
|
275
|
+
// Generate a unique variable name for the module
|
|
276
|
+
const moduleVar = `__crossimport_${packId}_${normalizedPath.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
277
|
+
const lines = [`var ${moduleVar} = __kustom_cross_import("${packId}", "${normalizedPath}");`];
|
|
278
|
+
for (const imp of importList) {
|
|
279
|
+
// Handle "foo as bar" syntax
|
|
280
|
+
const parts = imp.split(/\s+as\s+/);
|
|
281
|
+
const originalName = parts[0].trim();
|
|
282
|
+
const localName = parts[1]?.trim() || originalName;
|
|
283
|
+
lines.push(`var ${localName} = ${moduleVar}.${originalName};`);
|
|
284
|
+
}
|
|
285
|
+
return lines.join('\n');
|
|
286
|
+
});
|
|
287
|
+
// Pattern 2b: Named imports from relative path - import { foo, bar } from "./path"
|
|
288
|
+
// Transform to: var __import_path = __kustom_import("./path"); var foo = __import_path.foo; var bar = __import_path.bar;
|
|
289
|
+
result = result.replace(/^import\s+\{\s*([^}]+)\s*\}\s+from\s+['"](\.[^'"]+)['"];?\s*$/gm, (match, imports, importPath) => {
|
|
290
|
+
const normalizedPath = normalizeImportPath(importPath);
|
|
291
|
+
const importList = imports.split(',').map((i) => i.trim()).filter((i) => i);
|
|
292
|
+
// Generate a unique variable name for the module
|
|
293
|
+
const moduleVar = `__import_${normalizedPath.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
294
|
+
const lines = [`var ${moduleVar} = __kustom_import("${normalizedPath}");`];
|
|
295
|
+
for (const imp of importList) {
|
|
296
|
+
// Handle "foo as bar" syntax
|
|
297
|
+
const parts = imp.split(/\s+as\s+/);
|
|
298
|
+
const originalName = parts[0].trim();
|
|
299
|
+
const localName = parts[1]?.trim() || originalName;
|
|
300
|
+
lines.push(`var ${localName} = ${moduleVar}.${originalName};`);
|
|
301
|
+
}
|
|
302
|
+
return lines.join('\n');
|
|
303
|
+
});
|
|
304
|
+
// Pattern 3a: Namespace import from cross-pack - import * as foo from "packId:path"
|
|
305
|
+
// Transform to: var foo = __kustom_cross_import("packId", "path");
|
|
306
|
+
result = result.replace(/^import\s+\*\s+as\s+(\w+)\s+from\s+['"]([a-z0-9-]+):([^'"]+)['"];?\s*$/gm, (match, varName, packId, importPath) => {
|
|
307
|
+
const normalizedPath = normalizeImportPath(importPath);
|
|
308
|
+
return `var ${varName} = __kustom_cross_import("${packId}", "${normalizedPath}");`;
|
|
309
|
+
});
|
|
310
|
+
// Pattern 3b: Namespace import from relative path - import * as foo from "./path"
|
|
311
|
+
// Transform to: var foo = __kustom_import("./path");
|
|
312
|
+
result = result.replace(/^import\s+\*\s+as\s+(\w+)\s+from\s+['"](\.[^'"]+)['"];?\s*$/gm, (match, varName, importPath) => {
|
|
313
|
+
const normalizedPath = normalizeImportPath(importPath);
|
|
314
|
+
return `var ${varName} = __kustom_import("${normalizedPath}");`;
|
|
315
|
+
});
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Normalize import path by removing .ts or .js extension.
|
|
320
|
+
* The Java runtime will handle extension resolution.
|
|
321
|
+
*/
|
|
322
|
+
function normalizeImportPath(path) {
|
|
323
|
+
return path.replace(/\.(ts|js)$/, '');
|
|
324
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform pipeline for TypeScript to Rhino-compatible JavaScript.
|
|
3
|
+
*/
|
|
4
|
+
export interface TransformOptions {
|
|
5
|
+
/** Whether to inline imports from local files */
|
|
6
|
+
inlineImports?: boolean;
|
|
7
|
+
/** Directories containing library files to inline */
|
|
8
|
+
libDirs?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface TransformResult {
|
|
11
|
+
code: string;
|
|
12
|
+
errors: string[];
|
|
13
|
+
warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Transform TypeScript source code to Rhino-compatible JavaScript.
|
|
17
|
+
*/
|
|
18
|
+
export declare function transformScript(sourcePath: string, options?: TransformOptions): Promise<TransformResult>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform pipeline for TypeScript to Rhino-compatible JavaScript.
|
|
3
|
+
*/
|
|
4
|
+
import * as esbuild from 'esbuild';
|
|
5
|
+
import { postProcessForRhino } from './postprocess.js';
|
|
6
|
+
import { transformAsyncAwait } from './async-transform.js';
|
|
7
|
+
/**
|
|
8
|
+
* Transform TypeScript source code to Rhino-compatible JavaScript.
|
|
9
|
+
*/
|
|
10
|
+
export async function transformScript(sourcePath, options = {}) {
|
|
11
|
+
const errors = [];
|
|
12
|
+
const warnings = [];
|
|
13
|
+
try {
|
|
14
|
+
// Use esbuild to bundle the script with its local imports
|
|
15
|
+
const result = await esbuild.build({
|
|
16
|
+
entryPoints: [sourcePath],
|
|
17
|
+
bundle: true,
|
|
18
|
+
write: false,
|
|
19
|
+
format: 'esm',
|
|
20
|
+
target: 'es2020',
|
|
21
|
+
platform: 'neutral',
|
|
22
|
+
external: ['kustom-mc'], // Don't bundle the SDK
|
|
23
|
+
loader: { '.ts': 'ts' },
|
|
24
|
+
logLevel: 'silent',
|
|
25
|
+
});
|
|
26
|
+
// Collect errors and warnings
|
|
27
|
+
for (const error of result.errors) {
|
|
28
|
+
errors.push(formatEsbuildMessage(error));
|
|
29
|
+
}
|
|
30
|
+
for (const warning of result.warnings) {
|
|
31
|
+
warnings.push(formatEsbuildMessage(warning));
|
|
32
|
+
}
|
|
33
|
+
if (result.outputFiles.length === 0) {
|
|
34
|
+
errors.push('No output generated from esbuild');
|
|
35
|
+
return { code: '', errors, warnings };
|
|
36
|
+
}
|
|
37
|
+
const bundledCode = result.outputFiles[0].text;
|
|
38
|
+
// Transform async/await to generators
|
|
39
|
+
const asyncTransformed = await transformAsyncAwait(bundledCode);
|
|
40
|
+
// Post-process for Rhino compatibility
|
|
41
|
+
const rhinoCode = postProcessForRhino(asyncTransformed);
|
|
42
|
+
return { code: rhinoCode, errors, warnings };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
errors.push(error.message);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
errors.push(String(error));
|
|
50
|
+
}
|
|
51
|
+
return { code: '', errors, warnings };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function formatEsbuildMessage(msg) {
|
|
55
|
+
if (msg.location) {
|
|
56
|
+
return `${msg.location.file}:${msg.location.line}:${msg.location.column}: ${msg.text}`;
|
|
57
|
+
}
|
|
58
|
+
return msg.text;
|
|
59
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack manifest configuration (embedded in kustom.config.json)
|
|
3
|
+
*/
|
|
4
|
+
export interface ManifestConfig {
|
|
5
|
+
/** Unique pack identifier (required) */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Human-readable pack name */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** Pack version (semver) */
|
|
10
|
+
version?: string;
|
|
11
|
+
/** Pack description */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Pack author */
|
|
14
|
+
author?: string;
|
|
15
|
+
/** Pack scope: "global", "world", or "player" */
|
|
16
|
+
scope?: 'global' | 'world' | 'player';
|
|
17
|
+
/** Worlds this pack applies to (for scope: "world") */
|
|
18
|
+
worlds?: string[];
|
|
19
|
+
/** Pack priority (higher = loaded later, can override) */
|
|
20
|
+
priority?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Server connection configuration
|
|
24
|
+
*/
|
|
25
|
+
export interface ServerConfig {
|
|
26
|
+
/** Server URL for registry API */
|
|
27
|
+
url?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface KustomConfig {
|
|
30
|
+
include?: string[];
|
|
31
|
+
exclude?: string[];
|
|
32
|
+
outDir?: string;
|
|
33
|
+
lib?: string[];
|
|
34
|
+
/** Pack manifest configuration */
|
|
35
|
+
manifest?: ManifestConfig;
|
|
36
|
+
/** Pack dependencies (e.g., ["core-pack@^1.0.0"]) */
|
|
37
|
+
dependencies?: string[];
|
|
38
|
+
/** Server connection settings */
|
|
39
|
+
server?: ServerConfig;
|
|
40
|
+
deploy?: {
|
|
41
|
+
target?: string;
|
|
42
|
+
reloadCommand?: string;
|
|
43
|
+
};
|
|
44
|
+
bundle?: {
|
|
45
|
+
output?: string;
|
|
46
|
+
include?: string[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export declare function loadConfig(cwd: string): KustomConfig;
|
|
50
|
+
/**
|
|
51
|
+
* Generate the kustompack.json manifest content from config
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateManifest(config: KustomConfig): string;
|
|
54
|
+
/**
|
|
55
|
+
* Validate the manifest configuration
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateManifest(config: KustomConfig): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Parse a namespaced ID string.
|
|
60
|
+
* Format: packId:type/name (e.g., "mypack:item/sword")
|
|
61
|
+
*/
|
|
62
|
+
export declare function parseNamespacedId(fullId: string): {
|
|
63
|
+
packId: string;
|
|
64
|
+
type: string;
|
|
65
|
+
name: string;
|
|
66
|
+
} | null;
|
|
67
|
+
/**
|
|
68
|
+
* Check if a string is a fully qualified namespaced ID.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isNamespacedId(id: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Check if a string is a cross-pack import (packId:path format).
|
|
73
|
+
*/
|
|
74
|
+
export declare function isCrossPackImport(importPath: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Parse a cross-pack import path.
|
|
77
|
+
* Format: packId:scripts/path or packId:type/name
|
|
78
|
+
*/
|
|
79
|
+
export declare function parseCrossPackImport(importPath: string): {
|
|
80
|
+
packId: string;
|
|
81
|
+
path: string;
|
|
82
|
+
} | null;
|
|
83
|
+
/**
|
|
84
|
+
* Create a fully qualified namespaced ID.
|
|
85
|
+
*/
|
|
86
|
+
export declare function createNamespacedId(packId: string, type: string, name: string): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
const defaultConfig = {
|
|
4
|
+
include: ['scripts/**/*.ts', 'blocks/**/*.ts', 'items/**/*.ts'],
|
|
5
|
+
exclude: ['**/*.test.ts', '**/*.spec.ts'],
|
|
6
|
+
outDir: '.',
|
|
7
|
+
lib: ['scripts/lib'],
|
|
8
|
+
manifest: {
|
|
9
|
+
id: 'my-pack',
|
|
10
|
+
version: '1.0.0',
|
|
11
|
+
scope: 'global',
|
|
12
|
+
priority: 0
|
|
13
|
+
},
|
|
14
|
+
dependencies: [],
|
|
15
|
+
server: {
|
|
16
|
+
url: 'http://localhost:8765'
|
|
17
|
+
},
|
|
18
|
+
deploy: {
|
|
19
|
+
target: '../run/plugins/kustom-plugin/packs',
|
|
20
|
+
reloadCommand: '/kustom pack reload'
|
|
21
|
+
},
|
|
22
|
+
bundle: {
|
|
23
|
+
output: 'dist/kustompack.zip',
|
|
24
|
+
include: ['**/*.js', 'textures/**/*', 'gui/**/*', 'models/**/*', 'sounds/**/*']
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
export function loadConfig(cwd) {
|
|
28
|
+
const configPath = path.join(cwd, 'kustom.config.json');
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
32
|
+
const userConfig = JSON.parse(content);
|
|
33
|
+
// Deep merge for nested objects
|
|
34
|
+
const merged = {
|
|
35
|
+
...defaultConfig,
|
|
36
|
+
...userConfig,
|
|
37
|
+
manifest: {
|
|
38
|
+
...defaultConfig.manifest,
|
|
39
|
+
...userConfig.manifest,
|
|
40
|
+
// Ensure id is always present
|
|
41
|
+
id: userConfig.manifest?.id || defaultConfig.manifest?.id || 'my-pack'
|
|
42
|
+
},
|
|
43
|
+
server: { ...defaultConfig.server, ...userConfig.server },
|
|
44
|
+
deploy: { ...defaultConfig.deploy, ...userConfig.deploy },
|
|
45
|
+
bundle: { ...defaultConfig.bundle, ...userConfig.bundle }
|
|
46
|
+
};
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn(`Warning: Could not parse kustom.config.json: ${error}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return defaultConfig;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate the kustompack.json manifest content from config
|
|
57
|
+
*/
|
|
58
|
+
export function generateManifest(config) {
|
|
59
|
+
const manifest = config.manifest;
|
|
60
|
+
if (!manifest?.id) {
|
|
61
|
+
throw new Error('manifest.id is required in kustom.config.json');
|
|
62
|
+
}
|
|
63
|
+
const output = {
|
|
64
|
+
id: manifest.id,
|
|
65
|
+
name: manifest.name || manifest.id,
|
|
66
|
+
version: manifest.version || '1.0.0',
|
|
67
|
+
description: manifest.description || '',
|
|
68
|
+
author: manifest.author || '',
|
|
69
|
+
dependencies: config.dependencies || [],
|
|
70
|
+
scope: {
|
|
71
|
+
type: manifest.scope || 'global',
|
|
72
|
+
worlds: manifest.worlds || [],
|
|
73
|
+
priority: manifest.priority || 0
|
|
74
|
+
},
|
|
75
|
+
resources: {
|
|
76
|
+
merge: true,
|
|
77
|
+
priority: manifest.priority || 0
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return JSON.stringify(output, null, 2);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate the manifest configuration
|
|
84
|
+
*/
|
|
85
|
+
export function validateManifest(config) {
|
|
86
|
+
const errors = [];
|
|
87
|
+
if (!config.manifest?.id) {
|
|
88
|
+
errors.push('manifest.id is required');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Validate pack ID format (lowercase, alphanumeric, hyphens)
|
|
92
|
+
if (!/^[a-z0-9-]+$/.test(config.manifest.id)) {
|
|
93
|
+
errors.push('manifest.id must be lowercase alphanumeric with hyphens only');
|
|
94
|
+
}
|
|
95
|
+
// Check for reserved IDs
|
|
96
|
+
const reservedIds = ['default', 'minecraft', 'kustom', 'global'];
|
|
97
|
+
if (reservedIds.includes(config.manifest.id)) {
|
|
98
|
+
errors.push(`manifest.id cannot be a reserved name: ${reservedIds.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (config.manifest?.version) {
|
|
102
|
+
// Basic semver validation
|
|
103
|
+
if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(config.manifest.version)) {
|
|
104
|
+
errors.push('manifest.version must be valid semver (e.g., 1.0.0)');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (config.manifest?.scope && !['global', 'world', 'player'].includes(config.manifest.scope)) {
|
|
108
|
+
errors.push('manifest.scope must be "global", "world", or "player"');
|
|
109
|
+
}
|
|
110
|
+
// Validate dependencies format
|
|
111
|
+
if (config.dependencies) {
|
|
112
|
+
for (const dep of config.dependencies) {
|
|
113
|
+
if (!/^[a-z0-9-]+@.+$/.test(dep)) {
|
|
114
|
+
errors.push(`Invalid dependency format: ${dep}. Expected: pack-id@version-range`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Validate server URL if provided
|
|
119
|
+
if (config.server?.url) {
|
|
120
|
+
try {
|
|
121
|
+
new URL(config.server.url);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
errors.push(`Invalid server URL: ${config.server.url}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return errors;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parse a namespaced ID string.
|
|
131
|
+
* Format: packId:type/name (e.g., "mypack:item/sword")
|
|
132
|
+
*/
|
|
133
|
+
export function parseNamespacedId(fullId) {
|
|
134
|
+
const match = fullId.match(/^([a-z0-9-]+):([a-z]+)\/(.+)$/);
|
|
135
|
+
if (!match)
|
|
136
|
+
return null;
|
|
137
|
+
return { packId: match[1], type: match[2], name: match[3] };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if a string is a fully qualified namespaced ID.
|
|
141
|
+
*/
|
|
142
|
+
export function isNamespacedId(id) {
|
|
143
|
+
return /^[a-z0-9-]+:[a-z]+\/.+$/.test(id);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if a string is a cross-pack import (packId:path format).
|
|
147
|
+
*/
|
|
148
|
+
export function isCrossPackImport(importPath) {
|
|
149
|
+
return /^[a-z0-9-]+:/.test(importPath) && !importPath.startsWith('./') && !importPath.startsWith('../');
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Parse a cross-pack import path.
|
|
153
|
+
* Format: packId:scripts/path or packId:type/name
|
|
154
|
+
*/
|
|
155
|
+
export function parseCrossPackImport(importPath) {
|
|
156
|
+
const match = importPath.match(/^([a-z0-9-]+):(.+)$/);
|
|
157
|
+
if (!match)
|
|
158
|
+
return null;
|
|
159
|
+
return { packId: match[1], path: match[2] };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create a fully qualified namespaced ID.
|
|
163
|
+
*/
|
|
164
|
+
export function createNamespacedId(packId, type, name) {
|
|
165
|
+
return `${packId}:${type}/${name}`;
|
|
166
|
+
}
|