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,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;
|