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,447 @@
|
|
|
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 esbuild from 'esbuild';
|
|
6
|
+
import chokidar from 'chokidar';
|
|
7
|
+
import { transformForRhino } from '../compiler/rhino-plugin.js';
|
|
8
|
+
import { transformAsyncAwait } from '../compiler/async-transform.js';
|
|
9
|
+
import { loadConfig, validateManifest, generateManifest } from '../config.js';
|
|
10
|
+
import { prepare } from './prepare.js';
|
|
11
|
+
export const buildCommand = new Command('build')
|
|
12
|
+
.description('Compile TypeScript scripts to Rhino-compatible JavaScript')
|
|
13
|
+
.option('-w, --watch', 'Watch for changes and rebuild')
|
|
14
|
+
.option('-d, --deploy', 'Copy compiled files to deploy target')
|
|
15
|
+
.option('--no-validate', 'Skip validation')
|
|
16
|
+
.option('-v, --verbose', 'Verbose output')
|
|
17
|
+
.option('--clean', 'Clean output directory before building')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
const config = loadConfig(process.cwd());
|
|
20
|
+
// Clean output directory if requested
|
|
21
|
+
if (options.clean) {
|
|
22
|
+
const outDir = config.outDir || 'dist';
|
|
23
|
+
const outPath = path.resolve(process.cwd(), outDir);
|
|
24
|
+
if (fs.existsSync(outPath)) {
|
|
25
|
+
fs.rmSync(outPath, { recursive: true });
|
|
26
|
+
console.log(`Cleaned output directory: ${outDir}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (options.watch) {
|
|
30
|
+
await watchMode(config, options);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await buildAll(config, options);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Get the output path for a source file based on config.outDir
|
|
38
|
+
*/
|
|
39
|
+
function getOutputPath(sourceFile, config) {
|
|
40
|
+
const outDir = config.outDir || 'dist';
|
|
41
|
+
const relativePath = sourceFile.replace(/\.ts$/, '.js');
|
|
42
|
+
return path.resolve(process.cwd(), outDir, relativePath);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the output directory from config
|
|
46
|
+
*/
|
|
47
|
+
function getOutDir(config) {
|
|
48
|
+
return path.resolve(process.cwd(), config.outDir || 'dist');
|
|
49
|
+
}
|
|
50
|
+
async function buildAll(config, options) {
|
|
51
|
+
// Validate manifest configuration
|
|
52
|
+
const manifestErrors = validateManifest(config);
|
|
53
|
+
if (manifestErrors.length > 0) {
|
|
54
|
+
console.error('Manifest validation errors:');
|
|
55
|
+
for (const error of manifestErrors) {
|
|
56
|
+
console.error(` - ${error}`);
|
|
57
|
+
}
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// Run prepare first to generate types
|
|
61
|
+
console.log('Preparing types...');
|
|
62
|
+
await prepare(process.cwd());
|
|
63
|
+
console.log('Building kustompack scripts...');
|
|
64
|
+
console.log(` Pack ID: ${config.manifest?.id || 'unknown'}`);
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
let successCount = 0;
|
|
67
|
+
let errorCount = 0;
|
|
68
|
+
// Find all TypeScript files
|
|
69
|
+
const patterns = config.include || ['scripts/**/*.ts'];
|
|
70
|
+
const excludePatterns = config.exclude || [];
|
|
71
|
+
const files = [];
|
|
72
|
+
for (const pattern of patterns) {
|
|
73
|
+
const matches = await glob(pattern, {
|
|
74
|
+
cwd: process.cwd(),
|
|
75
|
+
ignore: excludePatterns
|
|
76
|
+
});
|
|
77
|
+
files.push(...matches);
|
|
78
|
+
}
|
|
79
|
+
// Filter out lib files (they get inlined)
|
|
80
|
+
const libDirs = config.lib || ['scripts/lib'];
|
|
81
|
+
const scriptFiles = files.filter(file => {
|
|
82
|
+
return !libDirs.some(lib => file.startsWith(lib + '/') || file.startsWith(lib + '\\'));
|
|
83
|
+
});
|
|
84
|
+
if (scriptFiles.length === 0) {
|
|
85
|
+
console.log('No TypeScript files found to compile.');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const outDir = getOutDir(config);
|
|
89
|
+
console.log(`Found ${scriptFiles.length} script(s) to compile...`);
|
|
90
|
+
console.log(`Output directory: ${outDir}`);
|
|
91
|
+
for (const file of scriptFiles) {
|
|
92
|
+
try {
|
|
93
|
+
await buildFile(file, config, options);
|
|
94
|
+
successCount++;
|
|
95
|
+
if (options.verbose) {
|
|
96
|
+
console.log(` [OK] ${file}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
errorCount++;
|
|
101
|
+
console.error(` [ERROR] ${file}: ${error instanceof Error ? error.message : error}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Generate manifest in output directory
|
|
105
|
+
const manifestPath = path.resolve(outDir, 'kustompack.json');
|
|
106
|
+
try {
|
|
107
|
+
const manifestContent = generateManifest(config);
|
|
108
|
+
fs.writeFileSync(manifestPath, manifestContent);
|
|
109
|
+
if (options.verbose) {
|
|
110
|
+
console.log(` [OK] kustompack.json`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error(` [ERROR] kustompack.json: ${error instanceof Error ? error.message : error}`);
|
|
115
|
+
}
|
|
116
|
+
const elapsed = Date.now() - startTime;
|
|
117
|
+
console.log(`\nBuild completed in ${elapsed}ms: ${successCount} succeeded, ${errorCount} failed`);
|
|
118
|
+
if (options.deploy && config.deploy?.target) {
|
|
119
|
+
await deployFiles(scriptFiles, config, options);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* esbuild plugin to mark relative script imports and cross-pack imports as external.
|
|
124
|
+
* This prevents esbuild from bundling imported scripts, allowing
|
|
125
|
+
* our rhino-plugin to transform them to __kustom_import() or __kustom_cross_import() calls.
|
|
126
|
+
*/
|
|
127
|
+
function externalScriptImportsPlugin(config) {
|
|
128
|
+
const libDirs = config.lib || ['scripts/lib'];
|
|
129
|
+
const declaredDependencies = new Set((config.dependencies || []).map(dep => dep.split('@')[0]));
|
|
130
|
+
const crossPackImportsUsed = new Set();
|
|
131
|
+
return {
|
|
132
|
+
name: 'external-script-imports',
|
|
133
|
+
setup(build) {
|
|
134
|
+
// Mark cross-pack imports as external (packId:path format)
|
|
135
|
+
build.onResolve({ filter: /^[a-z0-9-]+:/ }, (args) => {
|
|
136
|
+
// Extract pack ID from the import path
|
|
137
|
+
const match = args.path.match(/^([a-z0-9-]+):/);
|
|
138
|
+
if (match) {
|
|
139
|
+
const packId = match[1];
|
|
140
|
+
crossPackImportsUsed.add(packId);
|
|
141
|
+
// Warn if importing from undeclared dependency
|
|
142
|
+
if (!declaredDependencies.has(packId)) {
|
|
143
|
+
console.warn(` Warning: Cross-pack import from "${packId}" but it's not declared in dependencies`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Mark as external - will be transformed by rhino-plugin
|
|
147
|
+
return { path: args.path, external: true };
|
|
148
|
+
});
|
|
149
|
+
// Mark relative imports as external, except for lib files
|
|
150
|
+
build.onResolve({ filter: /^\.\.?\// }, (args) => {
|
|
151
|
+
// Check if this is a lib import (should be bundled)
|
|
152
|
+
const isLibImport = libDirs.some(lib => {
|
|
153
|
+
const normalizedLib = lib.replace(/\\/g, '/');
|
|
154
|
+
const normalizedPath = args.path.replace(/\\/g, '/');
|
|
155
|
+
// Check if the resolved path would be in a lib directory
|
|
156
|
+
const resolvedDir = path.dirname(args.importer).replace(/\\/g, '/');
|
|
157
|
+
const fullPath = path.join(resolvedDir, normalizedPath).replace(/\\/g, '/');
|
|
158
|
+
return fullPath.includes(`/${normalizedLib}/`) || fullPath.startsWith(`${normalizedLib}/`);
|
|
159
|
+
});
|
|
160
|
+
if (isLibImport) {
|
|
161
|
+
// Let esbuild bundle lib files
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
// Mark as external - will be transformed by rhino-plugin
|
|
165
|
+
return { path: args.path, external: true };
|
|
166
|
+
});
|
|
167
|
+
// Report cross-pack imports at end of build
|
|
168
|
+
build.onEnd(() => {
|
|
169
|
+
if (crossPackImportsUsed.size > 0) {
|
|
170
|
+
console.log(` Cross-pack imports: ${Array.from(crossPackImportsUsed).join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function buildFile(file, config, options) {
|
|
177
|
+
const inputPath = path.resolve(process.cwd(), file);
|
|
178
|
+
const outputPath = getOutputPath(file, config);
|
|
179
|
+
// Ensure output directory exists
|
|
180
|
+
const outputDir = path.dirname(outputPath);
|
|
181
|
+
if (!fs.existsSync(outputDir)) {
|
|
182
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
// Use esbuild to bundle and transform
|
|
185
|
+
const result = await esbuild.build({
|
|
186
|
+
entryPoints: [inputPath],
|
|
187
|
+
bundle: true,
|
|
188
|
+
write: false, // We'll write after transforming
|
|
189
|
+
format: 'esm',
|
|
190
|
+
target: 'es2020',
|
|
191
|
+
platform: 'neutral',
|
|
192
|
+
external: ['kustom-mc'],
|
|
193
|
+
loader: { '.ts': 'ts' },
|
|
194
|
+
plugins: [externalScriptImportsPlugin(config)],
|
|
195
|
+
});
|
|
196
|
+
if (result.errors.length > 0) {
|
|
197
|
+
throw new Error(result.errors.map(e => e.text).join('\n'));
|
|
198
|
+
}
|
|
199
|
+
const bundledCode = result.outputFiles[0].text;
|
|
200
|
+
// Transform async/await to generators (for Rhino compatibility)
|
|
201
|
+
const asyncTransformed = await transformAsyncAwait(bundledCode);
|
|
202
|
+
// Transform for Rhino compatibility
|
|
203
|
+
const rhinoCode = transformForRhino(asyncTransformed);
|
|
204
|
+
// Write output
|
|
205
|
+
fs.writeFileSync(outputPath, rhinoCode);
|
|
206
|
+
return outputPath;
|
|
207
|
+
}
|
|
208
|
+
async function watchMode(config, options) {
|
|
209
|
+
console.log('Starting watch mode...');
|
|
210
|
+
// Initial build (without deploy - we'll deploy separately)
|
|
211
|
+
await buildAll(config, { ...options, deploy: false });
|
|
212
|
+
// Initial deploy if requested (scripts + assets)
|
|
213
|
+
if (options.deploy && config.deploy?.target) {
|
|
214
|
+
const patterns = config.include || ['scripts/**/*.ts'];
|
|
215
|
+
const files = [];
|
|
216
|
+
for (const pattern of patterns) {
|
|
217
|
+
const matches = await glob(pattern, {
|
|
218
|
+
cwd: process.cwd(),
|
|
219
|
+
ignore: config.exclude || []
|
|
220
|
+
});
|
|
221
|
+
files.push(...matches);
|
|
222
|
+
}
|
|
223
|
+
const libDirs = config.lib || ['scripts/lib'];
|
|
224
|
+
const scriptFiles = files.filter(file => {
|
|
225
|
+
return !libDirs.some(lib => file.startsWith(lib + '/') || file.startsWith(lib + '\\'));
|
|
226
|
+
});
|
|
227
|
+
await deployFiles(scriptFiles, config, options);
|
|
228
|
+
}
|
|
229
|
+
// Watch for changes
|
|
230
|
+
const patterns = config.include || ['scripts/**/*.ts'];
|
|
231
|
+
const libDirs = config.lib || ['scripts/lib'];
|
|
232
|
+
const watcher = chokidar.watch(patterns, {
|
|
233
|
+
cwd: process.cwd(),
|
|
234
|
+
ignoreInitial: true,
|
|
235
|
+
ignored: config.exclude
|
|
236
|
+
});
|
|
237
|
+
// Watch sounds folder for type regeneration and deployment
|
|
238
|
+
const soundsWatcher = chokidar.watch('sounds/**/*.ogg', {
|
|
239
|
+
cwd: process.cwd(),
|
|
240
|
+
ignoreInitial: true
|
|
241
|
+
});
|
|
242
|
+
soundsWatcher.on('add', async (file) => {
|
|
243
|
+
console.log(`\nSound added: ${file}`);
|
|
244
|
+
console.log('Regenerating types...');
|
|
245
|
+
await prepare(process.cwd());
|
|
246
|
+
if (options.deploy && config.deploy?.target) {
|
|
247
|
+
await deploySingleAsset(file, config);
|
|
248
|
+
console.log(` Deployed: ${file}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
soundsWatcher.on('change', async (file) => {
|
|
252
|
+
console.log(`\nSound changed: ${file}`);
|
|
253
|
+
if (options.deploy && config.deploy?.target) {
|
|
254
|
+
await deploySingleAsset(file, config);
|
|
255
|
+
console.log(` Deployed: ${file}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
soundsWatcher.on('unlink', async (file) => {
|
|
259
|
+
console.log(`\nSound removed: ${file}`);
|
|
260
|
+
console.log('Regenerating types...');
|
|
261
|
+
await prepare(process.cwd());
|
|
262
|
+
});
|
|
263
|
+
// Watch other asset folders (textures, models, gui)
|
|
264
|
+
const assetPatterns = [
|
|
265
|
+
'textures/**/*',
|
|
266
|
+
'models/**/*',
|
|
267
|
+
'gui/**/*',
|
|
268
|
+
'items/**/*',
|
|
269
|
+
'blocks/**/*'
|
|
270
|
+
];
|
|
271
|
+
const assetsWatcher = chokidar.watch(assetPatterns, {
|
|
272
|
+
cwd: process.cwd(),
|
|
273
|
+
ignoreInitial: true
|
|
274
|
+
});
|
|
275
|
+
assetsWatcher.on('add', async (file) => {
|
|
276
|
+
if (options.deploy && config.deploy?.target) {
|
|
277
|
+
console.log(`\nAsset added: ${file}`);
|
|
278
|
+
await deploySingleAsset(file, config);
|
|
279
|
+
console.log(` Deployed: ${file}`);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
assetsWatcher.on('change', async (file) => {
|
|
283
|
+
if (options.deploy && config.deploy?.target) {
|
|
284
|
+
console.log(`\nAsset changed: ${file}`);
|
|
285
|
+
await deploySingleAsset(file, config);
|
|
286
|
+
console.log(` Deployed: ${file}`);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
const rebuild = async (changedFile) => {
|
|
290
|
+
const isLibFile = libDirs.some(lib => changedFile.startsWith(lib + '/') || changedFile.startsWith(lib + '\\'));
|
|
291
|
+
if (isLibFile) {
|
|
292
|
+
// Rebuild all files if a lib file changed
|
|
293
|
+
console.log(`\nLib file changed: ${changedFile}`);
|
|
294
|
+
console.log('Rebuilding all scripts...');
|
|
295
|
+
await buildAll(config, { ...options, deploy: options.deploy });
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Rebuild just the changed file
|
|
299
|
+
console.log(`\nFile changed: ${changedFile}`);
|
|
300
|
+
try {
|
|
301
|
+
const outputPath = await buildFile(changedFile, config, options);
|
|
302
|
+
console.log(` [OK] ${changedFile} -> ${path.relative(process.cwd(), outputPath)}`);
|
|
303
|
+
if (options.deploy && config.deploy?.target) {
|
|
304
|
+
await deploySingleFile(changedFile, config);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error(` [ERROR] ${changedFile}: ${error instanceof Error ? error.message : error}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
watcher.on('change', rebuild);
|
|
313
|
+
watcher.on('add', rebuild);
|
|
314
|
+
watcher.on('unlink', (file) => {
|
|
315
|
+
console.log(`\nFile deleted: ${file}`);
|
|
316
|
+
const outputPath = getOutputPath(file, config);
|
|
317
|
+
if (fs.existsSync(outputPath)) {
|
|
318
|
+
fs.unlinkSync(outputPath);
|
|
319
|
+
console.log(` Deleted: ${path.relative(process.cwd(), outputPath)}`);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
console.log('\nWatching for changes... (Ctrl+C to stop)');
|
|
323
|
+
// Keep process alive
|
|
324
|
+
process.on('SIGINT', () => {
|
|
325
|
+
console.log('\nStopping watch mode...');
|
|
326
|
+
watcher.close();
|
|
327
|
+
soundsWatcher.close();
|
|
328
|
+
assetsWatcher.close();
|
|
329
|
+
process.exit(0);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
async function deployFiles(sourceFiles, config, options = {}) {
|
|
333
|
+
if (!config.deploy?.target)
|
|
334
|
+
return;
|
|
335
|
+
const targetDir = path.resolve(process.cwd(), config.deploy.target);
|
|
336
|
+
console.log(`\nDeploying to ${targetDir}...`);
|
|
337
|
+
// Ensure target directory exists
|
|
338
|
+
if (!fs.existsSync(targetDir)) {
|
|
339
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
340
|
+
}
|
|
341
|
+
// Deploy manifest
|
|
342
|
+
const outDir = getOutDir(config);
|
|
343
|
+
const manifestSource = path.resolve(outDir, 'kustompack.json');
|
|
344
|
+
const manifestTarget = path.join(targetDir, 'kustompack.json');
|
|
345
|
+
if (fs.existsSync(manifestSource)) {
|
|
346
|
+
fs.copyFileSync(manifestSource, manifestTarget);
|
|
347
|
+
console.log(` Deployed kustompack.json`);
|
|
348
|
+
}
|
|
349
|
+
// Deploy compiled scripts
|
|
350
|
+
let deployedScripts = 0;
|
|
351
|
+
for (const sourceFile of sourceFiles) {
|
|
352
|
+
await deploySingleFile(sourceFile, config);
|
|
353
|
+
deployedScripts++;
|
|
354
|
+
}
|
|
355
|
+
console.log(` Deployed ${deployedScripts} script(s)`);
|
|
356
|
+
// Deploy asset folders (sounds, textures, models, gui, etc.)
|
|
357
|
+
const deployedAssets = await deployAssets(config, options);
|
|
358
|
+
if (deployedAssets > 0) {
|
|
359
|
+
console.log(` Deployed ${deployedAssets} asset folder(s)`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Deploy a single file from outDir to deploy target
|
|
364
|
+
*/
|
|
365
|
+
async function deploySingleFile(sourceFile, config) {
|
|
366
|
+
if (!config.deploy?.target)
|
|
367
|
+
return;
|
|
368
|
+
// Source is in outDir
|
|
369
|
+
const outputPath = getOutputPath(sourceFile, config);
|
|
370
|
+
// Target preserves relative path structure
|
|
371
|
+
const relativePath = sourceFile.replace(/\.ts$/, '.js');
|
|
372
|
+
const targetDir = path.resolve(process.cwd(), config.deploy.target);
|
|
373
|
+
const targetPath = path.join(targetDir, relativePath);
|
|
374
|
+
// Ensure target subdirectory exists
|
|
375
|
+
const targetSubDir = path.dirname(targetPath);
|
|
376
|
+
if (!fs.existsSync(targetSubDir)) {
|
|
377
|
+
fs.mkdirSync(targetSubDir, { recursive: true });
|
|
378
|
+
}
|
|
379
|
+
// Copy file from outDir to deploy target
|
|
380
|
+
if (fs.existsSync(outputPath)) {
|
|
381
|
+
fs.copyFileSync(outputPath, targetPath);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Asset folders that should be deployed alongside scripts
|
|
386
|
+
*/
|
|
387
|
+
const ASSET_FOLDERS = ['sounds', 'textures', 'models', 'gui', 'items', 'blocks'];
|
|
388
|
+
/**
|
|
389
|
+
* Copy a folder recursively
|
|
390
|
+
*/
|
|
391
|
+
function copyFolderRecursive(source, target) {
|
|
392
|
+
// Ensure target directory exists
|
|
393
|
+
if (!fs.existsSync(target)) {
|
|
394
|
+
fs.mkdirSync(target, { recursive: true });
|
|
395
|
+
}
|
|
396
|
+
const entries = fs.readdirSync(source, { withFileTypes: true });
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
const sourcePath = path.join(source, entry.name);
|
|
399
|
+
const targetPath = path.join(target, entry.name);
|
|
400
|
+
if (entry.isDirectory()) {
|
|
401
|
+
copyFolderRecursive(sourcePath, targetPath);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Deploy asset folders (sounds, textures, models, gui, etc.) to deploy target
|
|
410
|
+
*/
|
|
411
|
+
async function deployAssets(config, options) {
|
|
412
|
+
if (!config.deploy?.target)
|
|
413
|
+
return 0;
|
|
414
|
+
const targetDir = path.resolve(process.cwd(), config.deploy.target);
|
|
415
|
+
let deployedCount = 0;
|
|
416
|
+
for (const folder of ASSET_FOLDERS) {
|
|
417
|
+
const sourceDir = path.resolve(process.cwd(), folder);
|
|
418
|
+
if (fs.existsSync(sourceDir)) {
|
|
419
|
+
const targetSubDir = path.join(targetDir, folder);
|
|
420
|
+
copyFolderRecursive(sourceDir, targetSubDir);
|
|
421
|
+
deployedCount++;
|
|
422
|
+
if (options.verbose) {
|
|
423
|
+
console.log(` Deployed ${folder}/`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return deployedCount;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Deploy a single asset file to deploy target
|
|
431
|
+
*/
|
|
432
|
+
async function deploySingleAsset(assetFile, config) {
|
|
433
|
+
if (!config.deploy?.target)
|
|
434
|
+
return;
|
|
435
|
+
const sourcePath = path.resolve(process.cwd(), assetFile);
|
|
436
|
+
const targetDir = path.resolve(process.cwd(), config.deploy.target);
|
|
437
|
+
const targetPath = path.join(targetDir, assetFile);
|
|
438
|
+
// Ensure target subdirectory exists
|
|
439
|
+
const targetSubDir = path.dirname(targetPath);
|
|
440
|
+
if (!fs.existsSync(targetSubDir)) {
|
|
441
|
+
fs.mkdirSync(targetSubDir, { recursive: true });
|
|
442
|
+
}
|
|
443
|
+
// Copy file
|
|
444
|
+
if (fs.existsSync(sourcePath)) {
|
|
445
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { glob } from 'glob';
|
|
5
|
+
import archiver from 'archiver';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { loadConfig, generateManifest, validateManifest } from '../config.js';
|
|
8
|
+
export const bundleCommand = new Command('bundle')
|
|
9
|
+
.description('Create a distributable zip file with manifest')
|
|
10
|
+
.option('-o, --output <file>', 'Output file name')
|
|
11
|
+
.option('--include-source', 'Include TypeScript source files')
|
|
12
|
+
.option('--no-manifest', 'Skip manifest generation (use existing kustompack.json)')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
const config = loadConfig(process.cwd());
|
|
15
|
+
// Validate manifest configuration
|
|
16
|
+
const errors = validateManifest(config);
|
|
17
|
+
if (errors.length > 0) {
|
|
18
|
+
console.error(chalk.red('Manifest validation failed:'));
|
|
19
|
+
for (const error of errors) {
|
|
20
|
+
console.error(chalk.red(` - ${error}`));
|
|
21
|
+
}
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const packId = config.manifest?.id || 'kustompack';
|
|
25
|
+
const outputFile = options.output || config.bundle?.output || `dist/${packId}.zip`;
|
|
26
|
+
const outputPath = path.resolve(process.cwd(), outputFile);
|
|
27
|
+
console.log(chalk.blue(`Creating bundle for pack: ${chalk.bold(packId)}`));
|
|
28
|
+
console.log(` Version: ${config.manifest?.version || '1.0.0'}`);
|
|
29
|
+
console.log(` Output: ${outputFile}`);
|
|
30
|
+
// Ensure output directory exists
|
|
31
|
+
const outputDir = path.dirname(outputPath);
|
|
32
|
+
if (!fs.existsSync(outputDir)) {
|
|
33
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
// Generate manifest if enabled
|
|
36
|
+
let manifestContent = null;
|
|
37
|
+
if (options.manifest !== false) {
|
|
38
|
+
try {
|
|
39
|
+
manifestContent = generateManifest(config);
|
|
40
|
+
console.log(chalk.green(' Generated kustompack.json'));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(chalk.red(`Failed to generate manifest: ${error}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Create zip archive
|
|
48
|
+
const output = fs.createWriteStream(outputPath);
|
|
49
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
50
|
+
let fileCount = 0;
|
|
51
|
+
output.on('close', () => {
|
|
52
|
+
const sizeKB = (archive.pointer() / 1024).toFixed(2);
|
|
53
|
+
console.log(chalk.green(`\nBundle created successfully!`));
|
|
54
|
+
console.log(` File: ${outputFile}`);
|
|
55
|
+
console.log(` Size: ${sizeKB} KB`);
|
|
56
|
+
console.log(` Files: ${fileCount}`);
|
|
57
|
+
});
|
|
58
|
+
archive.on('warning', (err) => {
|
|
59
|
+
if (err.code === 'ENOENT') {
|
|
60
|
+
console.warn(chalk.yellow('Warning:'), err.message);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
archive.on('error', (err) => {
|
|
67
|
+
throw err;
|
|
68
|
+
});
|
|
69
|
+
archive.pipe(output);
|
|
70
|
+
// Add manifest first
|
|
71
|
+
if (manifestContent) {
|
|
72
|
+
archive.append(manifestContent, { name: 'kustompack.json' });
|
|
73
|
+
fileCount++;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Check for existing manifest
|
|
77
|
+
const existingManifest = path.resolve(process.cwd(), 'kustompack.json');
|
|
78
|
+
if (fs.existsSync(existingManifest)) {
|
|
79
|
+
archive.file(existingManifest, { name: 'kustompack.json' });
|
|
80
|
+
fileCount++;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.warn(chalk.yellow('Warning: No kustompack.json found. Pack may not load correctly.'));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Add files based on bundle config
|
|
87
|
+
const includePatterns = config.bundle?.include || [
|
|
88
|
+
'**/*.js',
|
|
89
|
+
'textures/**/*',
|
|
90
|
+
'gui/**/*',
|
|
91
|
+
'models/**/*',
|
|
92
|
+
'sounds/**/*'
|
|
93
|
+
];
|
|
94
|
+
const addedFiles = new Set();
|
|
95
|
+
for (const pattern of includePatterns) {
|
|
96
|
+
const files = await glob(pattern, {
|
|
97
|
+
cwd: process.cwd(),
|
|
98
|
+
nodir: true,
|
|
99
|
+
ignore: ['node_modules/**', 'dist/**', '*.ts', 'kustompack.json']
|
|
100
|
+
});
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
if (addedFiles.has(file))
|
|
103
|
+
continue;
|
|
104
|
+
const filePath = path.resolve(process.cwd(), file);
|
|
105
|
+
if (fs.existsSync(filePath)) {
|
|
106
|
+
archive.file(filePath, { name: file });
|
|
107
|
+
addedFiles.add(file);
|
|
108
|
+
fileCount++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Optionally include source files
|
|
113
|
+
if (options.includeSource) {
|
|
114
|
+
const sourcePatterns = config.include || ['scripts/**/*.ts', 'blocks/**/*.ts', 'items/**/*.ts'];
|
|
115
|
+
for (const pattern of sourcePatterns) {
|
|
116
|
+
const files = await glob(pattern, {
|
|
117
|
+
cwd: process.cwd(),
|
|
118
|
+
nodir: true,
|
|
119
|
+
ignore: config.exclude
|
|
120
|
+
});
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
if (addedFiles.has(file))
|
|
123
|
+
continue;
|
|
124
|
+
const filePath = path.resolve(process.cwd(), file);
|
|
125
|
+
if (fs.existsSync(filePath)) {
|
|
126
|
+
archive.file(filePath, { name: file });
|
|
127
|
+
addedFiles.add(file);
|
|
128
|
+
fileCount++;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await archive.finalize();
|
|
134
|
+
});
|