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,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
+ }
@@ -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
+ }