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.
Files changed (50) hide show
  1. package/README.md +809 -0
  2. package/dist/commands/build.d.ts +2 -0
  3. package/dist/commands/build.js +447 -0
  4. package/dist/commands/bundle.d.ts +2 -0
  5. package/dist/commands/bundle.js +134 -0
  6. package/dist/commands/init.d.ts +2 -0
  7. package/dist/commands/init.js +219 -0
  8. package/dist/commands/list.d.ts +10 -0
  9. package/dist/commands/list.js +167 -0
  10. package/dist/commands/login.d.ts +9 -0
  11. package/dist/commands/login.js +167 -0
  12. package/dist/commands/new.d.ts +2 -0
  13. package/dist/commands/new.js +132 -0
  14. package/dist/commands/prepare.d.ts +9 -0
  15. package/dist/commands/prepare.js +267 -0
  16. package/dist/commands/push.d.ts +9 -0
  17. package/dist/commands/push.js +205 -0
  18. package/dist/commands/validate.d.ts +2 -0
  19. package/dist/commands/validate.js +191 -0
  20. package/dist/compiler/async-transform.d.ts +21 -0
  21. package/dist/compiler/async-transform.js +158 -0
  22. package/dist/compiler/inline.d.ts +32 -0
  23. package/dist/compiler/inline.js +87 -0
  24. package/dist/compiler/postprocess.d.ts +19 -0
  25. package/dist/compiler/postprocess.js +134 -0
  26. package/dist/compiler/rhino-plugin.d.ts +17 -0
  27. package/dist/compiler/rhino-plugin.js +324 -0
  28. package/dist/compiler/transform.d.ts +18 -0
  29. package/dist/compiler/transform.js +59 -0
  30. package/dist/config.d.ts +86 -0
  31. package/dist/config.js +166 -0
  32. package/dist/credentials.d.ts +65 -0
  33. package/dist/credentials.js +136 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.js +28 -0
  36. package/dist/runtime.d.ts +116 -0
  37. package/dist/runtime.js +96 -0
  38. package/dist/types/globals.d.ts +80 -0
  39. package/dist/types/globals.js +10 -0
  40. package/dist/types/index.d.ts +2094 -0
  41. package/dist/types/index.js +9 -0
  42. package/package.json +57 -0
  43. package/templates/project/kustom.config.json +26 -0
  44. package/templates/project/scripts/example.ts +17 -0
  45. package/templates/project/scripts/lib/utils.ts +19 -0
  46. package/templates/project/tsconfig.json +27 -0
  47. package/templates/scripts/block.ts.hbs +14 -0
  48. package/templates/scripts/gui.ts.hbs +28 -0
  49. package/templates/scripts/item.ts.hbs +13 -0
  50. package/templates/scripts/script.ts.hbs +18 -0
@@ -0,0 +1,191 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { glob } from 'glob';
5
+ import * as ts from 'typescript';
6
+ import { loadConfig, validateManifest, isCrossPackImport, parseCrossPackImport } from '../config.js';
7
+ export const validateCommand = new Command('validate')
8
+ .description('Validate scripts for errors')
9
+ .option('--strict', 'Fail on warnings')
10
+ .option('-v, --verbose', 'Verbose output')
11
+ .action(async (options) => {
12
+ const config = loadConfig(process.cwd());
13
+ console.log('Validating kustompack...\n');
14
+ // Validate manifest configuration
15
+ console.log('Checking manifest...');
16
+ const manifestErrors = validateManifest(config);
17
+ if (manifestErrors.length > 0) {
18
+ console.log(' Manifest errors:');
19
+ for (const error of manifestErrors) {
20
+ console.log(` - ${error}`);
21
+ }
22
+ }
23
+ else {
24
+ console.log(` Pack ID: ${config.manifest?.id || 'unknown'}`);
25
+ console.log(` Version: ${config.manifest?.version || '1.0.0'}`);
26
+ if (config.dependencies && config.dependencies.length > 0) {
27
+ console.log(` Dependencies: ${config.dependencies.join(', ')}`);
28
+ }
29
+ console.log(' Manifest OK\n');
30
+ }
31
+ console.log('Validating scripts...');
32
+ // Find all TypeScript files
33
+ const patterns = config.include || ['scripts/**/*.ts', 'blocks/**/*.ts', 'items/**/*.ts'];
34
+ const excludePatterns = config.exclude || [];
35
+ const files = [];
36
+ for (const pattern of patterns) {
37
+ const matches = await glob(pattern, {
38
+ cwd: process.cwd(),
39
+ ignore: excludePatterns
40
+ });
41
+ files.push(...matches);
42
+ }
43
+ if (files.length === 0) {
44
+ console.log('No TypeScript files found to validate.');
45
+ return;
46
+ }
47
+ console.log(`Found ${files.length} file(s) to validate...\n`);
48
+ const results = [];
49
+ let totalErrors = 0;
50
+ let totalWarnings = 0;
51
+ // TypeScript validation
52
+ const tsconfigPath = path.resolve(process.cwd(), 'tsconfig.json');
53
+ let compilerOptions = {
54
+ target: ts.ScriptTarget.ES2020,
55
+ module: ts.ModuleKind.ESNext,
56
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
57
+ strict: true,
58
+ noEmit: true,
59
+ isolatedModules: true,
60
+ esModuleInterop: true,
61
+ skipLibCheck: true,
62
+ };
63
+ if (fs.existsSync(tsconfigPath)) {
64
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
65
+ if (!configFile.error) {
66
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, process.cwd());
67
+ compilerOptions = parsed.options;
68
+ }
69
+ }
70
+ const program = ts.createProgram(files.map(f => path.resolve(process.cwd(), f)), compilerOptions);
71
+ const diagnostics = ts.getPreEmitDiagnostics(program);
72
+ for (const diagnostic of diagnostics) {
73
+ if (diagnostic.file && diagnostic.start !== undefined) {
74
+ const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
75
+ const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
76
+ const relativePath = path.relative(process.cwd(), diagnostic.file.fileName);
77
+ let result = results.find(r => r.file === relativePath);
78
+ if (!result) {
79
+ result = { file: relativePath, errors: [], warnings: [] };
80
+ results.push(result);
81
+ }
82
+ if (diagnostic.category === ts.DiagnosticCategory.Error) {
83
+ result.errors.push({ line: line + 1, column: character + 1, message });
84
+ totalErrors++;
85
+ }
86
+ else if (diagnostic.category === ts.DiagnosticCategory.Warning) {
87
+ result.warnings.push({ line: line + 1, column: character + 1, message });
88
+ totalWarnings++;
89
+ }
90
+ }
91
+ }
92
+ // Build set of declared dependencies for cross-pack import validation
93
+ const declaredDependencies = new Set((config.dependencies || []).map(dep => dep.split('@')[0]));
94
+ const crossPackImportsUsed = new Map();
95
+ // Resource and cross-pack import validation
96
+ for (const file of files) {
97
+ const filePath = path.resolve(process.cwd(), file);
98
+ const content = fs.readFileSync(filePath, 'utf-8');
99
+ let result = results.find(r => r.file === file);
100
+ if (!result) {
101
+ result = { file, errors: [], warnings: [] };
102
+ results.push(result);
103
+ }
104
+ // Check for resource references (textures, models, gui)
105
+ const resourcePatterns = [
106
+ { regex: /["']gui\/([^"']+)["']/g, type: 'GUI texture', dir: 'gui' },
107
+ { regex: /["']textures\/([^"']+)["']/g, type: 'Texture', dir: 'textures' },
108
+ { regex: /["']models\/([^"']+)["']/g, type: 'Model', dir: 'models' },
109
+ ];
110
+ const lines = content.split('\n');
111
+ for (let i = 0; i < lines.length; i++) {
112
+ const line = lines[i];
113
+ // Check resource references
114
+ for (const { regex, type, dir } of resourcePatterns) {
115
+ regex.lastIndex = 0;
116
+ let match;
117
+ while ((match = regex.exec(line)) !== null) {
118
+ const resourcePath = path.join(process.cwd(), dir, match[1]);
119
+ if (!fs.existsSync(resourcePath)) {
120
+ result.warnings.push({
121
+ line: i + 1,
122
+ column: match.index + 1,
123
+ message: `${type} not found: ${dir}/${match[1]}`
124
+ });
125
+ totalWarnings++;
126
+ }
127
+ }
128
+ }
129
+ // Check for cross-pack imports
130
+ const importMatch = line.match(/import\s+.*?\s+from\s+['"]([^'"]+)['"]/);
131
+ if (importMatch) {
132
+ const importPath = importMatch[1];
133
+ if (isCrossPackImport(importPath)) {
134
+ const parsed = parseCrossPackImport(importPath);
135
+ if (parsed) {
136
+ // Track cross-pack import usage
137
+ if (!crossPackImportsUsed.has(parsed.packId)) {
138
+ crossPackImportsUsed.set(parsed.packId, []);
139
+ }
140
+ crossPackImportsUsed.get(parsed.packId).push({ file, line: i + 1 });
141
+ // Warn if importing from undeclared dependency
142
+ if (!declaredDependencies.has(parsed.packId)) {
143
+ result.warnings.push({
144
+ line: i + 1,
145
+ column: importMatch.index || 0,
146
+ message: `Cross-pack import from "${parsed.packId}" but it's not declared in dependencies`
147
+ });
148
+ totalWarnings++;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ // Report cross-pack import summary
156
+ if (crossPackImportsUsed.size > 0) {
157
+ console.log('\nCross-pack imports:');
158
+ for (const [packId, usages] of crossPackImportsUsed) {
159
+ const isDeclared = declaredDependencies.has(packId);
160
+ const status = isDeclared ? '(declared)' : '(NOT DECLARED)';
161
+ console.log(` ${packId} ${status}: ${usages.length} import(s)`);
162
+ if (options.verbose) {
163
+ for (const usage of usages) {
164
+ console.log(` - ${usage.file}:${usage.line}`);
165
+ }
166
+ }
167
+ }
168
+ console.log('');
169
+ }
170
+ // Print results
171
+ for (const result of results) {
172
+ if (result.errors.length > 0 || result.warnings.length > 0) {
173
+ console.log(`${result.file}:`);
174
+ for (const error of result.errors) {
175
+ console.log(` ${error.line}:${error.column} - error: ${error.message}`);
176
+ }
177
+ for (const warning of result.warnings) {
178
+ console.log(` ${warning.line}:${warning.column} - warning: ${warning.message}`);
179
+ }
180
+ console.log('');
181
+ }
182
+ else if (options.verbose) {
183
+ console.log(`${result.file}: OK`);
184
+ }
185
+ }
186
+ // Summary
187
+ console.log(`Validation complete: ${totalErrors} error(s), ${totalWarnings} warning(s)`);
188
+ if (totalErrors > 0 || (options.strict && totalWarnings > 0)) {
189
+ process.exit(1);
190
+ }
191
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Transform async/await to generator-based code for Rhino compatibility.
3
+ * Uses Babel's @babel/plugin-transform-async-to-generator.
4
+ *
5
+ * IMPORTANT: Rhino has a bug where generator.apply(obj, args) fails with
6
+ * "Cannot find default value for object". We work around this by replacing
7
+ * Babel's helper with a custom one that uses direct function calls.
8
+ */
9
+ /**
10
+ * Transform async functions to generator-based equivalents.
11
+ * This allows async/await syntax to work in Rhino, which supports generators
12
+ * but not native async/await.
13
+ *
14
+ * @param code JavaScript code potentially containing async/await
15
+ * @returns Transformed code with async functions converted to generators
16
+ */
17
+ export declare function transformAsyncAwait(code: string): Promise<string>;
18
+ /**
19
+ * Check if code contains async functions that need transformation.
20
+ */
21
+ export declare function needsAsyncTransform(code: string): boolean;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Transform async/await to generator-based code for Rhino compatibility.
3
+ * Uses Babel's @babel/plugin-transform-async-to-generator.
4
+ *
5
+ * IMPORTANT: Rhino has a bug where generator.apply(obj, args) fails with
6
+ * "Cannot find default value for object". We work around this by replacing
7
+ * Babel's helper with a custom one that uses direct function calls.
8
+ */
9
+ import * as babel from '@babel/core';
10
+ import { createRequire } from 'module';
11
+ import { fileURLToPath } from 'url';
12
+ import * as path from 'path';
13
+ // Get the directory of this module to resolve plugins correctly
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const require = createRequire(import.meta.url);
17
+ // Resolve the plugin path from kustom-mc's node_modules
18
+ const asyncToGeneratorPlugin = require.resolve('@babel/plugin-transform-async-to-generator');
19
+ /**
20
+ * Custom asyncToGenerator helper that works with Rhino.
21
+ *
22
+ * PROBLEM: Rhino's generator functions don't work with .apply() or .call()
23
+ * when the thisArg is an object. It throws "Cannot find default value for object".
24
+ *
25
+ * SOLUTION: We return a wrapper function that, when called, captures arguments
26
+ * and calls the generator directly without using .apply().
27
+ *
28
+ * Babel expects _asyncToGenerator to return a FUNCTION that returns a Promise.
29
+ * So __rhino_asyncToGen(generatorFn) must return a function.
30
+ */
31
+ const RHINO_ASYNC_STEP = `function __rhino_asyncStep(gen, resolve, reject, _next, _throw, key, arg) {
32
+ try {
33
+ var info = gen[key](arg);
34
+ var value = info.value;
35
+ } catch (error) {
36
+ reject(error);
37
+ return;
38
+ }
39
+ if (info.done) {
40
+ resolve(value);
41
+ } else {
42
+ Promise.resolve(value).then(_next, _throw);
43
+ }
44
+ }`;
45
+ // Returns a FUNCTION that when called returns a Promise
46
+ // This matches Babel's expected interface: _asyncToGenerator(fn)() or _asyncToGenerator(fn).apply(...)
47
+ const RHINO_ASYNC_TO_GEN = `function __rhino_asyncToGen(fn) {
48
+ return function() {
49
+ var args = arguments;
50
+ return new Promise(function(resolve, reject) {
51
+ var gen;
52
+ if (args && args.length > 0) {
53
+ switch(args.length) {
54
+ case 1: gen = fn(args[0]); break;
55
+ case 2: gen = fn(args[0], args[1]); break;
56
+ case 3: gen = fn(args[0], args[1], args[2]); break;
57
+ case 4: gen = fn(args[0], args[1], args[2], args[3]); break;
58
+ case 5: gen = fn(args[0], args[1], args[2], args[3], args[4]); break;
59
+ default: gen = fn(args[0], args[1], args[2], args[3], args[4]); break;
60
+ }
61
+ } else {
62
+ gen = fn();
63
+ }
64
+ function _next(value) {
65
+ __rhino_asyncStep(gen, resolve, reject, _next, _throw, "next", value);
66
+ }
67
+ function _throw(err) {
68
+ __rhino_asyncStep(gen, resolve, reject, _next, _throw, "throw", err);
69
+ }
70
+ _next(undefined);
71
+ });
72
+ };
73
+ }`;
74
+ /**
75
+ * Transform async functions to generator-based equivalents.
76
+ * This allows async/await syntax to work in Rhino, which supports generators
77
+ * but not native async/await.
78
+ *
79
+ * @param code JavaScript code potentially containing async/await
80
+ * @returns Transformed code with async functions converted to generators
81
+ */
82
+ export async function transformAsyncAwait(code) {
83
+ // Skip transformation if no async functions are present
84
+ if (!code.includes('async ') && !code.includes('async(')) {
85
+ return code;
86
+ }
87
+ try {
88
+ const result = await babel.transformAsync(code, {
89
+ plugins: [
90
+ asyncToGeneratorPlugin
91
+ ],
92
+ // Parse as module since esbuild outputs ESM before rhino-plugin transforms it
93
+ sourceType: 'module',
94
+ // Disable other Babel features we don't need
95
+ presets: [],
96
+ configFile: false,
97
+ babelrc: false,
98
+ // Preserve formatting as much as possible
99
+ compact: false,
100
+ retainLines: true,
101
+ });
102
+ if (!result?.code) {
103
+ console.warn('[async-transform] Babel returned no code, using original');
104
+ return code;
105
+ }
106
+ // Post-process: Replace Babel's asyncToGenerator with our Rhino-compatible version
107
+ let transformed = result.code;
108
+ transformed = replaceAsyncHelper(transformed);
109
+ return transformed;
110
+ }
111
+ catch (error) {
112
+ console.error('[async-transform] Babel transformation failed:', error);
113
+ // Return original code if transformation fails
114
+ return code;
115
+ }
116
+ }
117
+ /**
118
+ * Replace Babel's asyncToGenerator helper with our Rhino-compatible version.
119
+ *
120
+ * Strategy:
121
+ * 1. FIRST remove Babel's helper definitions (before any renaming)
122
+ * 2. Add our custom helper functions at the top
123
+ * 3. Replace _asyncToGenerator(fn).apply(this, arguments) with __rhino_asyncToGen(fn, arguments)
124
+ * 4. Replace standalone _asyncToGenerator calls
125
+ */
126
+ function replaceAsyncHelper(code) {
127
+ // Check if Babel's helper was added
128
+ if (!code.includes('_asyncToGenerator')) {
129
+ return code;
130
+ }
131
+ // Step 1: FIRST remove Babel's helper definitions (before any renaming!)
132
+ // Babel outputs on a single line (with spaces after commas):
133
+ // function asyncGeneratorStep(n, t, e, r, o, a, c) {try {var i = n[a](c),u = i.value;} catch (n) {return void e(n);}i.done ? t(u) : Promise.resolve(u).then(r, o);}function _asyncToGenerator(n) {return function () {var t = this,e = arguments;return new Promise(function (r, o) {var a = n.apply(t, e);function _next(n) {asyncGeneratorStep(a, r, o, _next, _throw, "next", n);}function _throw(n) {asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);}_next(void 0);});};} // scripts/...
134
+ // Use string manipulation instead of complex regex
135
+ // Find "function asyncGeneratorStep" and remove everything up to (but not including) "// scripts"
136
+ const asyncGenStart = code.indexOf('function asyncGeneratorStep');
137
+ if (asyncGenStart !== -1) {
138
+ // Find the comment that follows the helper block
139
+ const commentStart = code.indexOf('// scripts/', asyncGenStart);
140
+ if (commentStart !== -1) {
141
+ // Remove the helper block (everything from asyncGeneratorStep to the comment)
142
+ code = code.substring(0, asyncGenStart) + code.substring(commentStart);
143
+ }
144
+ }
145
+ // Step 2: Add our helpers at the top
146
+ code = RHINO_ASYNC_STEP + '\n' + RHINO_ASYNC_TO_GEN + '\n' + code;
147
+ // Step 3: Simply replace _asyncToGenerator with __rhino_asyncToGen
148
+ // Our helper returns a function (just like Babel's), so .apply() on that function is fine.
149
+ // The key difference is that OUR function doesn't use .apply() internally to call the generator.
150
+ code = code.replace(/_asyncToGenerator/g, '__rhino_asyncToGen');
151
+ return code;
152
+ }
153
+ /**
154
+ * Check if code contains async functions that need transformation.
155
+ */
156
+ export function needsAsyncTransform(code) {
157
+ return code.includes('async ') || code.includes('async(');
158
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Import inlining utilities.
3
+ *
4
+ * These are largely handled by esbuild now, but this module provides
5
+ * utilities for custom import resolution if needed.
6
+ */
7
+ export interface InlineOptions {
8
+ /** Base directory for resolving relative imports */
9
+ baseDir: string;
10
+ /** Directories to treat as library directories (imports get inlined) */
11
+ libDirs: string[];
12
+ }
13
+ /**
14
+ * Check if an import path should be inlined.
15
+ */
16
+ export declare function shouldInlineImport(importPath: string, options: InlineOptions): boolean;
17
+ /**
18
+ * Resolve an import path to an absolute file path.
19
+ */
20
+ export declare function resolveImportPath(importPath: string, fromFile: string, options: InlineOptions): string | null;
21
+ /**
22
+ * Extract import statements from source code.
23
+ */
24
+ export interface ImportInfo {
25
+ statement: string;
26
+ path: string;
27
+ names: string[];
28
+ isTypeOnly: boolean;
29
+ startIndex: number;
30
+ endIndex: number;
31
+ }
32
+ export declare function extractImports(code: string): ImportInfo[];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Import inlining utilities.
3
+ *
4
+ * These are largely handled by esbuild now, but this module provides
5
+ * utilities for custom import resolution if needed.
6
+ */
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ /**
10
+ * Check if an import path should be inlined.
11
+ */
12
+ export function shouldInlineImport(importPath, options) {
13
+ // External packages (node_modules) should not be inlined
14
+ if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
15
+ return false;
16
+ }
17
+ // SDK import should not be inlined
18
+ if (importPath === 'kustom-mc') {
19
+ return false;
20
+ }
21
+ return true;
22
+ }
23
+ /**
24
+ * Resolve an import path to an absolute file path.
25
+ */
26
+ export function resolveImportPath(importPath, fromFile, options) {
27
+ const fromDir = path.dirname(fromFile);
28
+ // Handle relative imports
29
+ if (importPath.startsWith('.')) {
30
+ const resolved = path.resolve(fromDir, importPath);
31
+ // Try with different extensions
32
+ const extensions = ['.ts', '.js', '/index.ts', '/index.js'];
33
+ for (const ext of extensions) {
34
+ const fullPath = resolved + ext;
35
+ if (fs.existsSync(fullPath)) {
36
+ return fullPath;
37
+ }
38
+ }
39
+ // Try without extension (might already have it)
40
+ if (fs.existsSync(resolved)) {
41
+ return resolved;
42
+ }
43
+ }
44
+ // Handle @lib/* alias
45
+ if (importPath.startsWith('@lib/')) {
46
+ const relativePath = importPath.slice('@lib/'.length);
47
+ for (const libDir of options.libDirs) {
48
+ const resolved = path.resolve(options.baseDir, libDir, relativePath);
49
+ const extensions = ['.ts', '.js', '/index.ts', '/index.js'];
50
+ for (const ext of extensions) {
51
+ const fullPath = resolved + ext;
52
+ if (fs.existsSync(fullPath)) {
53
+ return fullPath;
54
+ }
55
+ }
56
+ }
57
+ }
58
+ return null;
59
+ }
60
+ export function extractImports(code) {
61
+ const imports = [];
62
+ // Match import statements
63
+ const importRegex = /import\s+(type\s+)?(?:{([^}]+)}|(\w+))\s+from\s+['"]([^'"]+)['"];?/g;
64
+ let match;
65
+ while ((match = importRegex.exec(code)) !== null) {
66
+ const isTypeOnly = !!match[1];
67
+ const namedImports = match[2];
68
+ const defaultImport = match[3];
69
+ const importPath = match[4];
70
+ const names = [];
71
+ if (namedImports) {
72
+ names.push(...namedImports.split(',').map(n => n.trim()));
73
+ }
74
+ if (defaultImport) {
75
+ names.push(defaultImport);
76
+ }
77
+ imports.push({
78
+ statement: match[0],
79
+ path: importPath,
80
+ names,
81
+ isTypeOnly,
82
+ startIndex: match.index,
83
+ endIndex: match.index + match[0].length
84
+ });
85
+ }
86
+ return imports;
87
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Post-process bundled JavaScript for Rhino compatibility.
3
+ *
4
+ * Transforms:
5
+ * 1. Remove kustom-mc import
6
+ * 2. Convert export default defineX({...}) to exports.default = { __type: "x", ... }
7
+ * 3. Convert arrow functions to regular functions (for older Rhino)
8
+ * 4. Convert destructuring in function params to variable assignments
9
+ * 5. Convert const/let to var (for older Rhino)
10
+ * 6. Convert template literals to string concatenation
11
+ */
12
+ export declare function postProcessForRhino(code: string): string;
13
+ /**
14
+ * Transform destructuring in function parameters to manual assignments.
15
+ *
16
+ * Input: function({ executor, props }) { ... }
17
+ * Output: function(ctx) { var executor = ctx.executor; var props = ctx.props; ... }
18
+ */
19
+ export declare function transformDestructuringParams(code: string): string;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Post-process bundled JavaScript for Rhino compatibility.
3
+ *
4
+ * Transforms:
5
+ * 1. Remove kustom-mc import
6
+ * 2. Convert export default defineX({...}) to exports.default = { __type: "x", ... }
7
+ * 3. Convert arrow functions to regular functions (for older Rhino)
8
+ * 4. Convert destructuring in function params to variable assignments
9
+ * 5. Convert const/let to var (for older Rhino)
10
+ * 6. Convert template literals to string concatenation
11
+ */
12
+ export function postProcessForRhino(code) {
13
+ let result = code;
14
+ // Remove import statements for kustom-mc (handle all define functions)
15
+ result = result.replace(/import\s*{\s*(?:defineScript|defineBlock|defineItem)(?:\s*,\s*(?:defineScript|defineBlock|defineItem))*\s*}\s*from\s*['"]kustom-mc['"];?\s*/g, '');
16
+ result = result.replace(/import\s+type\s*{[^}]*}\s*from\s*['"]kustom-mc['"];?\s*/g, '');
17
+ // Convert export default defineScript({...}) to exports.default = { __type: "script", ...}
18
+ result = result.replace(/export\s+default\s+defineScript\s*\(\s*({[\s\S]*})\s*\)\s*;?/, (match, content) => injectTypeProperty(content, 'script'));
19
+ // Convert export default defineBlock({...}) to exports.default = { __type: "block", ...}
20
+ result = result.replace(/export\s+default\s+defineBlock\s*\(\s*({[\s\S]*})\s*\)\s*;?/, (match, content) => injectTypeProperty(content, 'block'));
21
+ // Convert export default defineItem({...}) to exports.default = { __type: "item", ...}
22
+ result = result.replace(/export\s+default\s+defineItem\s*\(\s*({[\s\S]*})\s*\)\s*;?/, (match, content) => injectTypeProperty(content, 'item'));
23
+ // Convert any remaining export default to exports.default
24
+ result = result.replace(/export\s+default\s+/g, 'exports.default = ');
25
+ // Convert const/let to var for maximum Rhino compatibility
26
+ result = result.replace(/\bconst\s+/g, 'var ');
27
+ result = result.replace(/\blet\s+/g, 'var ');
28
+ // Convert arrow functions to regular functions
29
+ // Handle: ({ a, b }) => { ... }
30
+ result = convertArrowFunctions(result);
31
+ // Convert template literals to string concatenation
32
+ result = convertTemplateLiterals(result);
33
+ // Ensure exports object exists at the top
34
+ if (!result.includes('var exports =') && !result.includes('exports =')) {
35
+ result = 'var exports = exports || {};\n' + result;
36
+ }
37
+ return result;
38
+ }
39
+ /**
40
+ * Inject __type property into the definition object.
41
+ *
42
+ * Input: { id: "foo", model: "bar" }
43
+ * Output: exports.default = { __type: "block", id: "foo", model: "bar" };
44
+ */
45
+ function injectTypeProperty(objectContent, type) {
46
+ // objectContent starts with { and ends with }
47
+ // We need to insert __type as the first property
48
+ const trimmed = objectContent.trim();
49
+ if (trimmed.startsWith('{')) {
50
+ // Insert __type right after the opening brace
51
+ const inner = trimmed.slice(1).trimStart();
52
+ if (inner.startsWith('}')) {
53
+ // Empty object
54
+ return `exports.default = { __type: "${type}" };`;
55
+ }
56
+ return `exports.default = { __type: "${type}", ${inner};`;
57
+ }
58
+ // Fallback - shouldn't happen but be safe
59
+ return `exports.default = ${objectContent};`;
60
+ }
61
+ function convertArrowFunctions(code) {
62
+ let result = code;
63
+ // Convert arrow functions with block body: (params) => { ... }
64
+ // This regex handles various parameter patterns
65
+ result = result.replace(/(\([^)]*\)|\w+)\s*=>\s*{/g, (match, params) => {
66
+ // Convert destructuring params if needed
67
+ if (params.startsWith('(') && params.includes('{')) {
68
+ // Has destructuring, we'll convert to function with manual destructuring
69
+ return `function${params} {`;
70
+ }
71
+ return `function${params.startsWith('(') ? params : `(${params})`} {`;
72
+ });
73
+ // Convert arrow functions with expression body: (params) => expr
74
+ // This is trickier - we need to wrap in { return expr; }
75
+ result = result.replace(/(\([^)]*\)|\w+)\s*=>\s*([^{][^,;\n]*)/g, (match, params, body) => {
76
+ // Skip if this looks like it's already been converted
77
+ if (body.trim().startsWith('function'))
78
+ return match;
79
+ // Skip if body ends with { (block body)
80
+ if (body.trim().endsWith('{'))
81
+ return match;
82
+ const funcParams = params.startsWith('(') ? params : `(${params})`;
83
+ return `function${funcParams} { return ${body.trim()}; }`;
84
+ });
85
+ return result;
86
+ }
87
+ function convertTemplateLiterals(code) {
88
+ // Convert template literals to string concatenation
89
+ // Handle: `text ${expr} more text`
90
+ return code.replace(/`([^`]*)`/g, (match, content) => {
91
+ // If no interpolation, just use regular string
92
+ if (!content.includes('${')) {
93
+ return `"${content.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
94
+ }
95
+ // Split by interpolation and join with +
96
+ const parts = [];
97
+ let remaining = content;
98
+ let lastIndex = 0;
99
+ const regex = /\$\{([^}]+)\}/g;
100
+ let match2;
101
+ while ((match2 = regex.exec(content)) !== null) {
102
+ // Add text before this interpolation
103
+ const textBefore = content.slice(lastIndex, match2.index);
104
+ if (textBefore) {
105
+ parts.push(`"${textBefore.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`);
106
+ }
107
+ // Add the expression
108
+ parts.push(`(${match2[1]})`);
109
+ lastIndex = regex.lastIndex;
110
+ }
111
+ // Add any remaining text
112
+ const textAfter = content.slice(lastIndex);
113
+ if (textAfter) {
114
+ parts.push(`"${textAfter.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`);
115
+ }
116
+ return parts.join(' + ');
117
+ });
118
+ }
119
+ /**
120
+ * Transform destructuring in function parameters to manual assignments.
121
+ *
122
+ * Input: function({ executor, props }) { ... }
123
+ * Output: function(ctx) { var executor = ctx.executor; var props = ctx.props; ... }
124
+ */
125
+ export function transformDestructuringParams(code) {
126
+ // This is a simplified version - a full implementation would need a proper parser
127
+ return code.replace(/function\s*\(\s*{\s*([^}]+)\s*}\s*\)\s*{/g, (match, params) => {
128
+ const paramList = params.split(',').map((p) => p.trim());
129
+ const assignments = paramList
130
+ .map((p) => `var ${p} = ctx.${p};`)
131
+ .join(' ');
132
+ return `function(ctx) { ${assignments}`;
133
+ });
134
+ }
@@ -0,0 +1,17 @@
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
+ import type { Plugin } from 'esbuild';
13
+ export declare function rhinoCompatPlugin(): Plugin;
14
+ /**
15
+ * Transform JavaScript code for Rhino compatibility.
16
+ */
17
+ export declare function transformForRhino(code: string): string;