kustom-mc 0.1.1 → 0.1.3

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.
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Options for creating a bundle.
3
+ */
4
+ export interface BundleOptions {
5
+ /** Whether to generate and include the manifest (default: true) */
6
+ includeManifest?: boolean;
7
+ /** Whether to include TypeScript source files (default: false) */
8
+ includeSource?: boolean;
9
+ }
10
+ /**
11
+ * Result of a bundle operation.
12
+ */
13
+ export interface BundleResult {
14
+ packId: string;
15
+ version: string;
16
+ fileCount: number;
17
+ }
18
+ /**
19
+ * Result of bundling to a buffer.
20
+ */
21
+ export interface BufferBundleResult extends BundleResult {
22
+ buffer: Buffer;
23
+ }
24
+ /**
25
+ * Create a zip bundle in memory and return as a Buffer.
26
+ * Used by the push command for HTTP upload.
27
+ */
28
+ export declare function bundleToBuffer(projectDir: string, options?: BundleOptions): Promise<BufferBundleResult>;
29
+ /**
30
+ * Create a zip bundle and write it to a file on disk.
31
+ * Used by the bundle command for distribution.
32
+ */
33
+ export declare function bundleToFile(projectDir: string, outputPath: string, options?: BundleOptions): Promise<BundleResult>;
@@ -0,0 +1,195 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { glob } from 'glob';
4
+ import archiver from 'archiver';
5
+ import { loadConfig, generateManifest, validateManifest } from './config.js';
6
+ /**
7
+ * Collect all files that should be included in the bundle.
8
+ * Returns a list of { absolutePath, archiveName } entries.
9
+ */
10
+ async function collectBundleFiles(projectDir, config) {
11
+ const includePatterns = config.bundle?.include || [
12
+ '**/*.js',
13
+ 'textures/**/*',
14
+ 'gui/**/*',
15
+ 'models/**/*',
16
+ 'sounds/**/*'
17
+ ];
18
+ const outDir = config.outDir || 'dist';
19
+ const outDirResolved = path.resolve(projectDir, outDir);
20
+ const addedFiles = new Set();
21
+ const files = [];
22
+ for (const pattern of includePatterns) {
23
+ const isJsPattern = pattern.includes('*.js');
24
+ if (isJsPattern) {
25
+ // JS files live in outDir (e.g. dist/)
26
+ // Exclude types/ directory (generated .d.ts files) and zip files
27
+ const matches = await glob(pattern, {
28
+ cwd: outDirResolved,
29
+ nodir: true,
30
+ ignore: ['types/**', '*.zip', 'kustompack.json']
31
+ });
32
+ for (const file of matches) {
33
+ if (addedFiles.has(file))
34
+ continue;
35
+ const absolutePath = path.resolve(outDirResolved, file);
36
+ if (fs.existsSync(absolutePath)) {
37
+ files.push({ absolutePath, archiveName: file });
38
+ addedFiles.add(file);
39
+ }
40
+ }
41
+ }
42
+ else {
43
+ // Asset files live in projectDir (source directory)
44
+ const matches = await glob(pattern, {
45
+ cwd: projectDir,
46
+ nodir: true,
47
+ ignore: ['node_modules/**', 'dist/**', '*.ts', 'kustompack.json']
48
+ });
49
+ for (const file of matches) {
50
+ if (addedFiles.has(file))
51
+ continue;
52
+ const absolutePath = path.resolve(projectDir, file);
53
+ if (fs.existsSync(absolutePath)) {
54
+ files.push({ absolutePath, archiveName: file });
55
+ addedFiles.add(file);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ return files;
61
+ }
62
+ /**
63
+ * Collect TypeScript source files for optional inclusion.
64
+ */
65
+ async function collectSourceFiles(projectDir, config) {
66
+ const sourcePatterns = config.include || ['scripts/**/*.ts', 'blocks/**/*.ts', 'items/**/*.ts'];
67
+ const addedFiles = new Set();
68
+ const files = [];
69
+ for (const pattern of sourcePatterns) {
70
+ const matches = await glob(pattern, {
71
+ cwd: projectDir,
72
+ nodir: true,
73
+ ignore: config.exclude
74
+ });
75
+ for (const file of matches) {
76
+ if (addedFiles.has(file))
77
+ continue;
78
+ const absolutePath = path.resolve(projectDir, file);
79
+ if (fs.existsSync(absolutePath)) {
80
+ files.push({ absolutePath, archiveName: file });
81
+ addedFiles.add(file);
82
+ }
83
+ }
84
+ }
85
+ return files;
86
+ }
87
+ /**
88
+ * Populate an archiver instance with all bundle files.
89
+ * Returns the file count.
90
+ */
91
+ async function populateArchive(archive, projectDir, config, options = {}) {
92
+ let fileCount = 0;
93
+ // Add manifest
94
+ if (options.includeManifest !== false) {
95
+ const manifestContent = generateManifest(config);
96
+ archive.append(manifestContent, { name: 'kustompack.json' });
97
+ fileCount++;
98
+ }
99
+ // Add compiled JS + assets
100
+ const bundleFiles = await collectBundleFiles(projectDir, config);
101
+ for (const file of bundleFiles) {
102
+ archive.file(file.absolutePath, { name: file.archiveName });
103
+ fileCount++;
104
+ }
105
+ // Optionally add source files
106
+ if (options.includeSource) {
107
+ const sourceFiles = await collectSourceFiles(projectDir, config);
108
+ for (const file of sourceFiles) {
109
+ archive.file(file.absolutePath, { name: file.archiveName });
110
+ fileCount++;
111
+ }
112
+ }
113
+ return { fileCount };
114
+ }
115
+ /**
116
+ * Validate config and return pack metadata.
117
+ * Throws on validation errors.
118
+ */
119
+ function validateConfig(config) {
120
+ const errors = validateManifest(config);
121
+ if (errors.length > 0) {
122
+ throw new Error(`Manifest validation failed:\n ${errors.join('\n ')}`);
123
+ }
124
+ return {
125
+ packId: config.manifest?.id || 'kustompack',
126
+ version: config.manifest?.version || '1.0.0'
127
+ };
128
+ }
129
+ /**
130
+ * Create a zip bundle in memory and return as a Buffer.
131
+ * Used by the push command for HTTP upload.
132
+ */
133
+ export async function bundleToBuffer(projectDir, options = {}) {
134
+ const config = loadConfig(projectDir);
135
+ const { packId, version } = validateConfig(config);
136
+ return new Promise((resolve, reject) => {
137
+ const chunks = [];
138
+ const archive = archiver('zip', { zlib: { level: 9 } });
139
+ archive.on('data', (chunk) => chunks.push(chunk));
140
+ archive.on('error', reject);
141
+ archive.on('end', () => {
142
+ // fileCount is set by the async populate below before finalize
143
+ });
144
+ const run = async () => {
145
+ const { fileCount } = await populateArchive(archive, projectDir, config, options);
146
+ // Listen for end after we know fileCount
147
+ return new Promise((res) => {
148
+ archive.on('end', () => {
149
+ res({
150
+ buffer: Buffer.concat(chunks),
151
+ packId,
152
+ version,
153
+ fileCount
154
+ });
155
+ });
156
+ archive.finalize();
157
+ });
158
+ };
159
+ run().then(resolve).catch(reject);
160
+ });
161
+ }
162
+ /**
163
+ * Create a zip bundle and write it to a file on disk.
164
+ * Used by the bundle command for distribution.
165
+ */
166
+ export async function bundleToFile(projectDir, outputPath, options = {}) {
167
+ const config = loadConfig(projectDir);
168
+ const { packId, version } = validateConfig(config);
169
+ // Ensure output directory exists
170
+ const outputDir = path.dirname(outputPath);
171
+ if (!fs.existsSync(outputDir)) {
172
+ fs.mkdirSync(outputDir, { recursive: true });
173
+ }
174
+ return new Promise((resolve, reject) => {
175
+ const output = fs.createWriteStream(outputPath);
176
+ const archive = archiver('zip', { zlib: { level: 9 } });
177
+ archive.on('warning', (err) => {
178
+ if (err.code !== 'ENOENT') {
179
+ reject(err);
180
+ }
181
+ });
182
+ archive.on('error', reject);
183
+ archive.pipe(output);
184
+ const run = async () => {
185
+ const { fileCount } = await populateArchive(archive, projectDir, config, options);
186
+ return new Promise((res) => {
187
+ output.on('close', () => {
188
+ res({ packId, version, fileCount });
189
+ });
190
+ archive.finalize();
191
+ });
192
+ };
193
+ run().then(resolve).catch(reject);
194
+ });
195
+ }
@@ -1,2 +1,18 @@
1
1
  import { Command } from 'commander';
2
+ import { type KustomConfig } from '../config.js';
2
3
  export declare const buildCommand: Command;
4
+ /**
5
+ * Build all TypeScript files in the project.
6
+ * Exported for use by push and bundle commands.
7
+ */
8
+ export declare function buildAll(config: KustomConfig, options: {
9
+ validate?: boolean;
10
+ verbose?: boolean;
11
+ }): Promise<void>;
12
+ /**
13
+ * Build a single TypeScript file to Rhino-compatible JavaScript.
14
+ * Exported for use by push --watch for incremental rebuilds.
15
+ */
16
+ export declare function buildFile(file: string, config: KustomConfig, options: {
17
+ verbose?: boolean;
18
+ }): Promise<string>;
@@ -11,7 +11,6 @@ import { prepare } from './prepare.js';
11
11
  export const buildCommand = new Command('build')
12
12
  .description('Compile TypeScript scripts to Rhino-compatible JavaScript')
13
13
  .option('-w, --watch', 'Watch for changes and rebuild')
14
- .option('-d, --deploy', 'Copy compiled files to deploy target')
15
14
  .option('--no-validate', 'Skip validation')
16
15
  .option('-v, --verbose', 'Verbose output')
17
16
  .option('--clean', 'Clean output directory before building')
@@ -47,7 +46,11 @@ function getOutputPath(sourceFile, config) {
47
46
  function getOutDir(config) {
48
47
  return path.resolve(process.cwd(), config.outDir || 'dist');
49
48
  }
50
- async function buildAll(config, options) {
49
+ /**
50
+ * Build all TypeScript files in the project.
51
+ * Exported for use by push and bundle commands.
52
+ */
53
+ export async function buildAll(config, options) {
51
54
  // Validate manifest configuration
52
55
  const manifestErrors = validateManifest(config);
53
56
  if (manifestErrors.length > 0) {
@@ -115,9 +118,6 @@ async function buildAll(config, options) {
115
118
  }
116
119
  const elapsed = Date.now() - startTime;
117
120
  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
121
  }
122
122
  /**
123
123
  * esbuild plugin to mark relative script imports and cross-pack imports as external.
@@ -173,7 +173,11 @@ function externalScriptImportsPlugin(config) {
173
173
  }
174
174
  };
175
175
  }
176
- async function buildFile(file, config, options) {
176
+ /**
177
+ * Build a single TypeScript file to Rhino-compatible JavaScript.
178
+ * Exported for use by push --watch for incremental rebuilds.
179
+ */
180
+ export async function buildFile(file, config, options) {
177
181
  const inputPath = path.resolve(process.cwd(), file);
178
182
  const outputPath = getOutputPath(file, config);
179
183
  // Ensure output directory exists
@@ -207,25 +211,8 @@ async function buildFile(file, config, options) {
207
211
  }
208
212
  async function watchMode(config, options) {
209
213
  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
- }
214
+ // Initial build
215
+ await buildAll(config, options);
229
216
  // Watch for changes
230
217
  const patterns = config.include || ['scripts/**/*.ts'];
231
218
  const libDirs = config.lib || ['scripts/lib'];
@@ -234,7 +221,7 @@ async function watchMode(config, options) {
234
221
  ignoreInitial: true,
235
222
  ignored: config.exclude
236
223
  });
237
- // Watch sounds folder for type regeneration and deployment
224
+ // Watch sounds folder for type regeneration
238
225
  const soundsWatcher = chokidar.watch('sounds/**/*.ogg', {
239
226
  cwd: process.cwd(),
240
227
  ignoreInitial: true
@@ -243,56 +230,19 @@ async function watchMode(config, options) {
243
230
  console.log(`\nSound added: ${file}`);
244
231
  console.log('Regenerating types...');
245
232
  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
233
  });
258
234
  soundsWatcher.on('unlink', async (file) => {
259
235
  console.log(`\nSound removed: ${file}`);
260
236
  console.log('Regenerating types...');
261
237
  await prepare(process.cwd());
262
238
  });
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
239
  const rebuild = async (changedFile) => {
290
240
  const isLibFile = libDirs.some(lib => changedFile.startsWith(lib + '/') || changedFile.startsWith(lib + '\\'));
291
241
  if (isLibFile) {
292
242
  // Rebuild all files if a lib file changed
293
243
  console.log(`\nLib file changed: ${changedFile}`);
294
244
  console.log('Rebuilding all scripts...');
295
- await buildAll(config, { ...options, deploy: options.deploy });
245
+ await buildAll(config, options);
296
246
  }
297
247
  else {
298
248
  // Rebuild just the changed file
@@ -300,9 +250,6 @@ async function watchMode(config, options) {
300
250
  try {
301
251
  const outputPath = await buildFile(changedFile, config, options);
302
252
  console.log(` [OK] ${changedFile} -> ${path.relative(process.cwd(), outputPath)}`);
303
- if (options.deploy && config.deploy?.target) {
304
- await deploySingleFile(changedFile, config);
305
- }
306
253
  }
307
254
  catch (error) {
308
255
  console.error(` [ERROR] ${changedFile}: ${error instanceof Error ? error.message : error}`);
@@ -325,123 +272,6 @@ async function watchMode(config, options) {
325
272
  console.log('\nStopping watch mode...');
326
273
  watcher.close();
327
274
  soundsWatcher.close();
328
- assetsWatcher.close();
329
275
  process.exit(0);
330
276
  });
331
277
  }
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
- }
@@ -1,17 +1,17 @@
1
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
2
  import chalk from 'chalk';
7
- import { loadConfig, generateManifest, validateManifest } from '../config.js';
3
+ import { loadConfig, validateManifest } from '../config.js';
4
+ import { bundleToFile } from '../bundler.js';
5
+ import { buildAll } from './build.js';
8
6
  export const bundleCommand = new Command('bundle')
9
- .description('Create a distributable zip file with manifest')
7
+ .description('Build and create a distributable zip file')
10
8
  .option('-o, --output <file>', 'Output file name')
11
9
  .option('--include-source', 'Include TypeScript source files')
12
- .option('--no-manifest', 'Skip manifest generation (use existing kustompack.json)')
10
+ .option('--no-manifest', 'Skip manifest generation')
11
+ .option('-v, --verbose', 'Verbose output')
13
12
  .action(async (options) => {
14
- const config = loadConfig(process.cwd());
13
+ const projectDir = process.cwd();
14
+ const config = loadConfig(projectDir);
15
15
  // Validate manifest configuration
16
16
  const errors = validateManifest(config);
17
17
  if (errors.length > 0) {
@@ -23,112 +23,25 @@ export const bundleCommand = new Command('bundle')
23
23
  }
24
24
  const packId = config.manifest?.id || 'kustompack';
25
25
  const outputFile = options.output || config.bundle?.output || `dist/${packId}.zip`;
26
- const outputPath = path.resolve(process.cwd(), outputFile);
26
+ // Auto-build before bundling
27
+ console.log(chalk.blue('Building before bundle...\n'));
28
+ await buildAll(config, { verbose: options.verbose });
29
+ console.log();
30
+ // Create bundle
27
31
  console.log(chalk.blue(`Creating bundle for pack: ${chalk.bold(packId)}`));
28
32
  console.log(` Version: ${config.manifest?.version || '1.0.0'}`);
29
33
  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);
34
+ try {
35
+ const result = await bundleToFile(projectDir, outputFile, {
36
+ includeManifest: options.manifest !== false,
37
+ includeSource: options.includeSource
38
+ });
53
39
  console.log(chalk.green(`\nBundle created successfully!`));
54
40
  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++;
41
+ console.log(` Files: ${result.fileCount}`);
74
42
  }
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
- }
43
+ catch (error) {
44
+ console.error(chalk.red(`\nFailed to create bundle: ${error instanceof Error ? error.message : error}`));
45
+ process.exit(1);
132
46
  }
133
- await archive.finalize();
134
47
  });
@@ -88,7 +88,7 @@ export const initCommand = new Command('init')
88
88
  "**/*.test.ts",
89
89
  "**/*.spec.ts"
90
90
  ],
91
- outDir: ".",
91
+ outDir: "dist",
92
92
  lib: ["scripts/lib"],
93
93
  manifest: {
94
94
  id: packId,
@@ -103,10 +103,6 @@ export const initCommand = new Command('init')
103
103
  server: {
104
104
  url: "http://localhost:8765"
105
105
  },
106
- deploy: {
107
- target: "../run/plugins/kustom-plugin/packs",
108
- reloadCommand: "/kustom pack reload"
109
- },
110
106
  bundle: {
111
107
  output: `dist/${packId}.zip`,
112
108
  include: [
@@ -127,12 +123,11 @@ export const initCommand = new Command('init')
127
123
  version: "1.0.0",
128
124
  type: "module",
129
125
  scripts: {
126
+ push: "kustom push",
127
+ dev: "kustom push --watch",
130
128
  build: "kustom build",
131
- watch: "kustom build --watch",
132
- deploy: "kustom build --deploy",
133
129
  bundle: "kustom bundle",
134
- validate: "kustom validate",
135
- push: "kustom push"
130
+ validate: "kustom validate"
136
131
  },
137
132
  devDependencies: {
138
133
  "kustom-mc": "^0.1.0",
@@ -186,8 +181,6 @@ export function delay(ms: number): Promise<void> {
186
181
  const gitignore = `node_modules/
187
182
  dist/
188
183
  .kustom/
189
- *.js
190
- !scripts/lib/**/*.js
191
184
  `;
192
185
  fs.writeFileSync(path.join(targetDir, '.gitignore'), gitignore);
193
186
  console.log(chalk.green('\nProject initialized successfully!'));
@@ -210,10 +203,10 @@ dist/
210
203
  console.log(chalk.cyan(` cd ${projectName}`));
211
204
  }
212
205
  console.log(chalk.cyan(' npm install'));
213
- console.log(chalk.cyan(' npm run build'));
206
+ console.log(chalk.cyan(' npm run push'));
214
207
  console.log('\n' + chalk.bold('Available commands:'));
215
- console.log(' npm run build - Compile TypeScript');
216
- console.log(' npm run watch - Watch mode');
217
- console.log(' npm run deploy - Build and deploy to server');
208
+ console.log(' npm run push - Build and push to server');
209
+ console.log(' npm run dev - Watch mode + auto-push');
210
+ console.log(' npm run build - Compile TypeScript only');
218
211
  console.log(' npm run bundle - Create distributable zip');
219
212
  });
@@ -1,9 +1,16 @@
1
1
  import { Command } from 'commander';
2
2
  /**
3
- * Push command - bundle and upload pack to server.
3
+ * Push command build, bundle, and upload pack to server.
4
+ *
5
+ * This is the primary deployment command. It:
6
+ * 1. Builds all TypeScript files
7
+ * 2. Bundles compiled JS + assets into a zip
8
+ * 3. Uploads the zip to the server via HTTP
4
9
  *
5
10
  * Usage:
6
- * npx kustom-mc push # Push to configured server
7
- * npx kustom-mc push --server <url> # Push to specific server
11
+ * kustom push # One-shot build + push
12
+ * kustom push --watch # Watch + auto-push on changes
13
+ * kustom push --server <url> # Push to specific server
14
+ * kustom push --dry-run # Build + bundle, don't upload
8
15
  */
9
16
  export declare const pushCommand: Command;
@@ -2,113 +2,181 @@ import { Command } from 'commander';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import chalk from 'chalk';
5
- import { glob } from 'glob';
6
- import archiver from 'archiver';
7
- import { loadConfig, generateManifest, validateManifest } from '../config.js';
5
+ import chokidar from 'chokidar';
6
+ import { loadConfig } from '../config.js';
8
7
  import { getServerToken, normalizeServerUrl, getServerCredential } from '../credentials.js';
8
+ import { bundleToBuffer } from '../bundler.js';
9
+ import { uploadPack } from '../upload.js';
10
+ import { buildAll, buildFile } from './build.js';
11
+ import { prepare } from './prepare.js';
12
+ /** Debounce delay in milliseconds before auto-pushing after a change */
13
+ const PUSH_DEBOUNCE_MS = 500;
9
14
  /**
10
- * Create a zip bundle in memory and return as Buffer.
15
+ * Build, bundle, and upload the pack to the server.
16
+ * Returns true on success, false on failure.
11
17
  */
12
- async function createBundle(projectDir) {
13
- const config = loadConfig(projectDir);
14
- // Validate manifest
15
- const errors = validateManifest(config);
16
- if (errors.length > 0) {
17
- throw new Error(`Manifest validation failed:\n ${errors.join('\n ')}`);
18
+ async function doPush(projectDir, serverUrl, token, options) {
19
+ try {
20
+ const { buffer, packId, version, fileCount } = await bundleToBuffer(projectDir);
21
+ const sizeKB = (buffer.length / 1024).toFixed(2);
22
+ if (options.verbose) {
23
+ console.log(` Bundle: ${fileCount} files, ${sizeKB} KB`);
24
+ }
25
+ const result = await uploadPack(serverUrl, token, buffer, packId);
26
+ if (result.success) {
27
+ console.log(chalk.green(` Pushed ${packId} v${version} (${sizeKB} KB)`));
28
+ return true;
29
+ }
30
+ else {
31
+ console.error(chalk.red(` Push failed: ${result.error || 'Unknown error'}`));
32
+ return false;
33
+ }
34
+ }
35
+ catch (error) {
36
+ console.error(chalk.red(` Push failed: ${error instanceof Error ? error.message : error}`));
37
+ return false;
18
38
  }
19
- const packId = config.manifest?.id || 'kustompack';
20
- const version = config.manifest?.version || '1.0.0';
21
- return new Promise((resolve, reject) => {
22
- const chunks = [];
23
- const archive = archiver('zip', { zlib: { level: 9 } });
24
- archive.on('data', (chunk) => chunks.push(chunk));
25
- archive.on('end', () => resolve({
26
- buffer: Buffer.concat(chunks),
27
- packId,
28
- version
29
- }));
30
- archive.on('error', reject);
31
- // Generate and add manifest
32
- const manifestContent = generateManifest(config);
33
- archive.append(manifestContent, { name: 'kustompack.json' });
34
- // Add files based on bundle config
35
- const includePatterns = config.bundle?.include || [
36
- '**/*.js',
37
- 'textures/**/*',
38
- 'gui/**/*',
39
- 'models/**/*',
40
- 'sounds/**/*'
41
- ];
42
- const addFiles = async () => {
43
- const addedFiles = new Set();
44
- for (const pattern of includePatterns) {
45
- const files = await glob(pattern, {
46
- cwd: projectDir,
47
- nodir: true,
48
- ignore: ['node_modules/**', 'dist/**', '*.ts', 'kustompack.json']
49
- });
50
- for (const file of files) {
51
- if (addedFiles.has(file))
52
- continue;
53
- const filePath = path.resolve(projectDir, file);
54
- if (fs.existsSync(filePath)) {
55
- archive.file(filePath, { name: file });
56
- addedFiles.add(file);
57
- }
58
- }
59
- }
60
- archive.finalize();
61
- };
62
- addFiles().catch(reject);
63
- });
64
39
  }
65
40
  /**
66
- * Upload a pack to the server.
67
- *
68
- * Currently sends raw zip bytes for simplicity and performance.
69
- *
70
- * TODO: If a web UI is added later, consider supporting multipart/form-data
71
- * uploads on the server side (Option 2). This would require:
72
- * - Adding multipart parsing to PackRegistryServer.java (Apache Commons FileUpload or manual)
73
- * - Keeping raw zip support for CLI (faster)
74
- * - Adding multipart support for browser uploads
41
+ * Watch mode: rebuild and auto-push on file changes with debounce.
75
42
  */
76
- async function uploadPack(serverUrl, token, buffer, packId) {
77
- const url = `${normalizeServerUrl(serverUrl)}/packs/upload`;
78
- // Send raw zip bytes (faster than multipart, server already supports this)
79
- const response = await fetch(url, {
80
- method: 'POST',
81
- headers: {
82
- 'Authorization': `Bearer ${token}`,
83
- 'Content-Type': 'application/zip',
84
- 'X-Pack-Id': packId // Optional hint for server
85
- },
86
- body: buffer
87
- });
88
- if (response.status === 401) {
89
- throw new Error('Authentication failed. Token may be invalid or expired. Run: npx kustom-mc login');
90
- }
91
- if (response.status === 403) {
92
- throw new Error('Permission denied. You may not have upload permissions.');
93
- }
94
- const result = await response.json();
95
- if (!response.ok) {
96
- throw new Error(result.error || result.message || `Server returned ${response.status}`);
43
+ async function pushWatchMode(config, projectDir, serverUrl, token, options) {
44
+ console.log(chalk.blue('Starting push watch mode...'));
45
+ console.log(` Server: ${serverUrl}`);
46
+ console.log();
47
+ // Initial build
48
+ await buildAll(config, options);
49
+ // Initial push
50
+ console.log('\nPushing initial build...');
51
+ await doPush(projectDir, serverUrl, token, options);
52
+ // Debounce timer
53
+ let pushTimer = null;
54
+ function schedulePush() {
55
+ if (pushTimer)
56
+ clearTimeout(pushTimer);
57
+ pushTimer = setTimeout(async () => {
58
+ pushTimer = null;
59
+ console.log(chalk.gray('\nPushing changes...'));
60
+ await doPush(projectDir, serverUrl, token, options);
61
+ }, PUSH_DEBOUNCE_MS);
97
62
  }
98
- return result;
63
+ // Watch script files
64
+ const patterns = config.include || ['scripts/**/*.ts'];
65
+ const libDirs = config.lib || ['scripts/lib'];
66
+ const scriptWatcher = chokidar.watch(patterns, {
67
+ cwd: projectDir,
68
+ ignoreInitial: true,
69
+ ignored: config.exclude
70
+ });
71
+ const rebuildAndPush = async (changedFile) => {
72
+ const isLibFile = libDirs.some(lib => changedFile.startsWith(lib + '/') || changedFile.startsWith(lib + '\\'));
73
+ if (isLibFile) {
74
+ console.log(`\nLib file changed: ${changedFile}`);
75
+ console.log('Rebuilding all scripts...');
76
+ await buildAll(config, options);
77
+ }
78
+ else {
79
+ console.log(`\nFile changed: ${changedFile}`);
80
+ try {
81
+ const outputPath = await buildFile(changedFile, config, options);
82
+ console.log(` [OK] ${changedFile} -> ${path.relative(projectDir, outputPath)}`);
83
+ }
84
+ catch (error) {
85
+ console.error(` [ERROR] ${changedFile}: ${error instanceof Error ? error.message : error}`);
86
+ return; // Don't push if build failed
87
+ }
88
+ }
89
+ schedulePush();
90
+ };
91
+ scriptWatcher.on('change', rebuildAndPush);
92
+ scriptWatcher.on('add', rebuildAndPush);
93
+ scriptWatcher.on('unlink', (file) => {
94
+ console.log(`\nFile deleted: ${file}`);
95
+ // Delete the compiled output
96
+ const outDir = config.outDir || 'dist';
97
+ const outputPath = path.resolve(projectDir, outDir, file.replace(/\.ts$/, '.js'));
98
+ if (fs.existsSync(outputPath)) {
99
+ fs.unlinkSync(outputPath);
100
+ }
101
+ schedulePush();
102
+ });
103
+ // Watch sounds folder for type regeneration + push
104
+ const soundsWatcher = chokidar.watch('sounds/**/*.ogg', {
105
+ cwd: projectDir,
106
+ ignoreInitial: true
107
+ });
108
+ soundsWatcher.on('add', async (file) => {
109
+ console.log(`\nSound added: ${file}`);
110
+ console.log('Regenerating types...');
111
+ await prepare(projectDir);
112
+ schedulePush();
113
+ });
114
+ soundsWatcher.on('change', () => {
115
+ schedulePush();
116
+ });
117
+ soundsWatcher.on('unlink', async (file) => {
118
+ console.log(`\nSound removed: ${file}`);
119
+ console.log('Regenerating types...');
120
+ await prepare(projectDir);
121
+ schedulePush();
122
+ });
123
+ // Watch asset folders (textures, models, gui, etc.) — push on change
124
+ const assetPatterns = [
125
+ 'textures/**/*',
126
+ 'models/**/*',
127
+ 'gui/**/*',
128
+ 'lang/**/*',
129
+ 'definitions/**/*'
130
+ ];
131
+ const assetsWatcher = chokidar.watch(assetPatterns, {
132
+ cwd: projectDir,
133
+ ignoreInitial: true
134
+ });
135
+ assetsWatcher.on('add', (file) => {
136
+ console.log(`\nAsset added: ${file}`);
137
+ schedulePush();
138
+ });
139
+ assetsWatcher.on('change', (file) => {
140
+ console.log(`\nAsset changed: ${file}`);
141
+ schedulePush();
142
+ });
143
+ assetsWatcher.on('unlink', (file) => {
144
+ console.log(`\nAsset removed: ${file}`);
145
+ schedulePush();
146
+ });
147
+ console.log('\nWatching for changes... (Ctrl+C to stop)');
148
+ // Keep process alive
149
+ process.on('SIGINT', () => {
150
+ console.log('\nStopping push watch mode...');
151
+ if (pushTimer)
152
+ clearTimeout(pushTimer);
153
+ scriptWatcher.close();
154
+ soundsWatcher.close();
155
+ assetsWatcher.close();
156
+ process.exit(0);
157
+ });
99
158
  }
100
159
  /**
101
- * Push command - bundle and upload pack to server.
160
+ * Push command build, bundle, and upload pack to server.
161
+ *
162
+ * This is the primary deployment command. It:
163
+ * 1. Builds all TypeScript files
164
+ * 2. Bundles compiled JS + assets into a zip
165
+ * 3. Uploads the zip to the server via HTTP
102
166
  *
103
167
  * Usage:
104
- * npx kustom-mc push # Push to configured server
105
- * npx kustom-mc push --server <url> # Push to specific server
168
+ * kustom push # One-shot build + push
169
+ * kustom push --watch # Watch + auto-push on changes
170
+ * kustom push --server <url> # Push to specific server
171
+ * kustom push --dry-run # Build + bundle, don't upload
106
172
  */
107
173
  export const pushCommand = new Command('push')
108
- .description('Bundle and upload pack to the connected server')
174
+ .description('Build, bundle, and upload pack to the server')
109
175
  .option('-s, --server <url>', 'Server URL (overrides config)')
110
- .option('--dry-run', 'Create bundle but do not upload')
176
+ .option('-w, --watch', 'Watch for changes and auto-push')
177
+ .option('--dry-run', 'Build and bundle but do not upload')
111
178
  .option('-y, --yes', 'Skip confirmation prompt')
179
+ .option('-v, --verbose', 'Verbose output')
112
180
  .action(async (options) => {
113
181
  const projectDir = process.cwd();
114
182
  const config = loadConfig(projectDir);
@@ -135,22 +203,35 @@ export const pushCommand = new Command('push')
135
203
  process.exit(1);
136
204
  }
137
205
  const cred = getServerCredential(serverUrl);
138
- console.log(chalk.blue('Preparing pack for upload...'));
206
+ // Watch mode
207
+ if (options?.watch) {
208
+ if (!token) {
209
+ console.error(chalk.red('Error: --watch requires authentication'));
210
+ process.exit(1);
211
+ }
212
+ await pushWatchMode(config, projectDir, serverUrl, token, { verbose: options.verbose });
213
+ return;
214
+ }
215
+ // One-shot mode: build → bundle → upload
216
+ console.log(chalk.blue('Building and pushing pack...'));
139
217
  console.log(` Server: ${serverUrl}`);
140
218
  if (cred?.playerName) {
141
219
  console.log(` As: ${cred.playerName}`);
142
220
  }
143
221
  console.log();
144
222
  try {
145
- // Create bundle
223
+ // Build
224
+ await buildAll(config, { verbose: options?.verbose });
225
+ console.log();
226
+ // Bundle
146
227
  console.log('Creating bundle...');
147
- const { buffer, packId, version } = await createBundle(projectDir);
228
+ const { buffer, packId, version, fileCount } = await bundleToBuffer(projectDir);
148
229
  const sizeKB = (buffer.length / 1024).toFixed(2);
149
230
  console.log(chalk.green(` Pack: ${packId} v${version}`));
150
- console.log(chalk.green(` Size: ${sizeKB} KB`));
231
+ console.log(chalk.green(` Size: ${sizeKB} KB (${fileCount} files)`));
232
+ // Dry run — save to disk instead of uploading
151
233
  if (options?.dryRun) {
152
234
  console.log(chalk.yellow('\n--dry-run: Bundle created but not uploaded.'));
153
- // Optionally save the bundle locally
154
235
  const outputPath = path.join(projectDir, 'dist', `${packId}.zip`);
155
236
  const outputDir = path.dirname(outputPath);
156
237
  if (!fs.existsSync(outputDir)) {
@@ -164,7 +245,6 @@ export const pushCommand = new Command('push')
164
245
  if (!options?.yes) {
165
246
  console.log(chalk.yellow(`\nThis will upload ${packId} v${version} to ${serverUrl}`));
166
247
  console.log(chalk.yellow('If a pack with this ID exists, it will be replaced.'));
167
- // Simple confirmation using readline
168
248
  const readline = await import('readline');
169
249
  const rl = readline.createInterface({
170
250
  input: process.stdin,
@@ -183,15 +263,14 @@ export const pushCommand = new Command('push')
183
263
  console.log('\nUploading...');
184
264
  const result = await uploadPack(serverUrl, token, buffer, packId);
185
265
  if (result.success) {
186
- console.log(chalk.green('\nUpload successful!'));
266
+ console.log(chalk.green('\nPush successful!'));
187
267
  if (result.message) {
188
268
  console.log(` ${result.message}`);
189
269
  }
190
- console.log(`\nPack ${packId} v${version} is now available on the server.`);
191
- console.log(chalk.gray('Run /kustom pack reload to apply changes in-game.'));
270
+ console.log(`\nPack ${packId} v${version} is now live on the server.`);
192
271
  }
193
272
  else {
194
- console.error(chalk.red('\nUpload failed'));
273
+ console.error(chalk.red('\nPush failed'));
195
274
  if (result.error) {
196
275
  console.error(chalk.red(` ${result.error}`));
197
276
  }
package/dist/config.d.ts CHANGED
@@ -37,10 +37,6 @@ export interface KustomConfig {
37
37
  dependencies?: string[];
38
38
  /** Server connection settings */
39
39
  server?: ServerConfig;
40
- deploy?: {
41
- target?: string;
42
- reloadCommand?: string;
43
- };
44
40
  bundle?: {
45
41
  output?: string;
46
42
  include?: string[];
package/dist/config.js CHANGED
@@ -3,7 +3,7 @@ import * as path from 'path';
3
3
  const defaultConfig = {
4
4
  include: ['scripts/**/*.ts', 'blocks/**/*.ts', 'items/**/*.ts'],
5
5
  exclude: ['**/*.test.ts', '**/*.spec.ts'],
6
- outDir: '.',
6
+ outDir: 'dist',
7
7
  lib: ['scripts/lib'],
8
8
  manifest: {
9
9
  id: 'my-pack',
@@ -15,10 +15,6 @@ const defaultConfig = {
15
15
  server: {
16
16
  url: 'http://localhost:8765'
17
17
  },
18
- deploy: {
19
- target: '../run/plugins/kustom-plugin/packs',
20
- reloadCommand: '/kustom pack reload'
21
- },
22
18
  bundle: {
23
19
  output: 'dist/kustompack.zip',
24
20
  include: ['**/*.js', 'textures/**/*', 'gui/**/*', 'models/**/*', 'sounds/**/*']
@@ -41,7 +37,6 @@ export function loadConfig(cwd) {
41
37
  id: userConfig.manifest?.id || defaultConfig.manifest?.id || 'my-pack'
42
38
  },
43
39
  server: { ...defaultConfig.server, ...userConfig.server },
44
- deploy: { ...defaultConfig.deploy, ...userConfig.deploy },
45
40
  bundle: { ...defaultConfig.bundle, ...userConfig.bundle }
46
41
  };
47
42
  return merged;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Response from the /packs/upload endpoint.
3
+ */
4
+ export interface UploadResponse {
5
+ success: boolean;
6
+ packId?: string;
7
+ version?: string;
8
+ message?: string;
9
+ error?: string;
10
+ }
11
+ /**
12
+ * Upload a pack zip to the server via HTTP POST.
13
+ *
14
+ * Sends raw zip bytes with application/zip content type.
15
+ * The server saves the zip and hot-loads the pack.
16
+ *
17
+ * @param serverUrl - Server base URL (e.g. "http://localhost:8765")
18
+ * @param token - Authentication token (from `kustom login`)
19
+ * @param buffer - Raw zip bytes
20
+ * @param packId - Pack ID hint for the server
21
+ */
22
+ export declare function uploadPack(serverUrl: string, token: string, buffer: Buffer, packId: string): Promise<UploadResponse>;
package/dist/upload.js ADDED
@@ -0,0 +1,35 @@
1
+ import { normalizeServerUrl } from './credentials.js';
2
+ /**
3
+ * Upload a pack zip to the server via HTTP POST.
4
+ *
5
+ * Sends raw zip bytes with application/zip content type.
6
+ * The server saves the zip and hot-loads the pack.
7
+ *
8
+ * @param serverUrl - Server base URL (e.g. "http://localhost:8765")
9
+ * @param token - Authentication token (from `kustom login`)
10
+ * @param buffer - Raw zip bytes
11
+ * @param packId - Pack ID hint for the server
12
+ */
13
+ export async function uploadPack(serverUrl, token, buffer, packId) {
14
+ const url = `${normalizeServerUrl(serverUrl)}/packs/upload`;
15
+ const response = await fetch(url, {
16
+ method: 'POST',
17
+ headers: {
18
+ 'Authorization': `Bearer ${token}`,
19
+ 'Content-Type': 'application/zip',
20
+ 'X-Pack-Id': packId
21
+ },
22
+ body: buffer
23
+ });
24
+ if (response.status === 401) {
25
+ throw new Error('Authentication failed. Token may be invalid or expired. Run: npx kustom-mc login');
26
+ }
27
+ if (response.status === 403) {
28
+ throw new Error('Permission denied. You may not have upload permissions.');
29
+ }
30
+ const result = await response.json();
31
+ if (!response.ok) {
32
+ throw new Error(result.error || result.message || `Server returned ${response.status}`);
33
+ }
34
+ return result;
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kustom-mc",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI and type library for kustompack development",
5
5
  "type": "module",
6
6
  "main": "dist/runtime.js",