kustom-mc 0.1.2 → 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.
- package/dist/bundler.d.ts +33 -0
- package/dist/bundler.js +195 -0
- package/dist/commands/build.d.ts +16 -0
- package/dist/commands/build.js +14 -184
- package/dist/commands/bundle.js +22 -133
- package/dist/commands/init.js +7 -12
- package/dist/commands/push.d.ts +10 -3
- package/dist/commands/push.js +180 -101
- package/dist/config.d.ts +0 -4
- package/dist/config.js +0 -5
- package/dist/upload.d.ts +22 -0
- package/dist/upload.js +35 -0
- package/package.json +1 -1
|
@@ -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>;
|
package/dist/bundler.js
ADDED
|
@@ -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
|
+
}
|
package/dist/commands/build.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/build.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
211
|
-
await buildAll(config,
|
|
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
|
|
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,
|
|
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
|
-
}
|
package/dist/commands/bundle.js
CHANGED
|
@@ -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,
|
|
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('
|
|
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
|
|
10
|
+
.option('--no-manifest', 'Skip manifest generation')
|
|
11
|
+
.option('-v, --verbose', 'Verbose output')
|
|
13
12
|
.action(async (options) => {
|
|
14
|
-
const
|
|
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,136 +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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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(`
|
|
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
|
-
}
|
|
41
|
+
console.log(` Files: ${result.fileCount}`);
|
|
85
42
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
'textures/**/*',
|
|
90
|
-
'gui/**/*',
|
|
91
|
-
'models/**/*',
|
|
92
|
-
'sounds/**/*'
|
|
93
|
-
];
|
|
94
|
-
const outDir = config.outDir || 'dist';
|
|
95
|
-
const outDirResolved = path.resolve(process.cwd(), outDir);
|
|
96
|
-
const addedFiles = new Set();
|
|
97
|
-
for (const pattern of includePatterns) {
|
|
98
|
-
const isJsPattern = pattern.includes('*.js');
|
|
99
|
-
if (isJsPattern) {
|
|
100
|
-
// JS files live in outDir (e.g. dist/)
|
|
101
|
-
const files = await glob(pattern, {
|
|
102
|
-
cwd: outDirResolved,
|
|
103
|
-
nodir: true,
|
|
104
|
-
ignore: ['node_modules/**', '*.ts', 'kustompack.json']
|
|
105
|
-
});
|
|
106
|
-
for (const file of files) {
|
|
107
|
-
if (addedFiles.has(file))
|
|
108
|
-
continue;
|
|
109
|
-
const filePath = path.resolve(outDirResolved, file);
|
|
110
|
-
if (fs.existsSync(filePath)) {
|
|
111
|
-
archive.file(filePath, { name: file });
|
|
112
|
-
addedFiles.add(file);
|
|
113
|
-
fileCount++;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
// Asset files live in cwd (source directory)
|
|
119
|
-
const files = await glob(pattern, {
|
|
120
|
-
cwd: process.cwd(),
|
|
121
|
-
nodir: true,
|
|
122
|
-
ignore: ['node_modules/**', 'dist/**', '*.ts', 'kustompack.json']
|
|
123
|
-
});
|
|
124
|
-
for (const file of files) {
|
|
125
|
-
if (addedFiles.has(file))
|
|
126
|
-
continue;
|
|
127
|
-
const filePath = path.resolve(process.cwd(), file);
|
|
128
|
-
if (fs.existsSync(filePath)) {
|
|
129
|
-
archive.file(filePath, { name: file });
|
|
130
|
-
addedFiles.add(file);
|
|
131
|
-
fileCount++;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Optionally include source files
|
|
137
|
-
if (options.includeSource) {
|
|
138
|
-
const sourcePatterns = config.include || ['scripts/**/*.ts', 'blocks/**/*.ts', 'items/**/*.ts'];
|
|
139
|
-
for (const pattern of sourcePatterns) {
|
|
140
|
-
const files = await glob(pattern, {
|
|
141
|
-
cwd: process.cwd(),
|
|
142
|
-
nodir: true,
|
|
143
|
-
ignore: config.exclude
|
|
144
|
-
});
|
|
145
|
-
for (const file of files) {
|
|
146
|
-
if (addedFiles.has(file))
|
|
147
|
-
continue;
|
|
148
|
-
const filePath = path.resolve(process.cwd(), file);
|
|
149
|
-
if (fs.existsSync(filePath)) {
|
|
150
|
-
archive.file(filePath, { name: file });
|
|
151
|
-
addedFiles.add(file);
|
|
152
|
-
fileCount++;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(chalk.red(`\nFailed to create bundle: ${error instanceof Error ? error.message : error}`));
|
|
45
|
+
process.exit(1);
|
|
156
46
|
}
|
|
157
|
-
await archive.finalize();
|
|
158
47
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -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",
|
|
@@ -208,10 +203,10 @@ dist/
|
|
|
208
203
|
console.log(chalk.cyan(` cd ${projectName}`));
|
|
209
204
|
}
|
|
210
205
|
console.log(chalk.cyan(' npm install'));
|
|
211
|
-
console.log(chalk.cyan(' npm run
|
|
206
|
+
console.log(chalk.cyan(' npm run push'));
|
|
212
207
|
console.log('\n' + chalk.bold('Available commands:'));
|
|
213
|
-
console.log(' npm run
|
|
214
|
-
console.log(' npm run
|
|
215
|
-
console.log(' npm run
|
|
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');
|
|
216
211
|
console.log(' npm run bundle - Create distributable zip');
|
|
217
212
|
});
|
package/dist/commands/push.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
/**
|
|
3
|
-
* Push command
|
|
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
|
-
*
|
|
7
|
-
*
|
|
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;
|
package/dist/commands/push.js
CHANGED
|
@@ -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
|
|
6
|
-
import
|
|
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
|
-
*
|
|
15
|
+
* Build, bundle, and upload the pack to the server.
|
|
16
|
+
* Returns true on success, false on failure.
|
|
11
17
|
*/
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
*
|
|
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
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
105
|
-
*
|
|
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('
|
|
174
|
+
.description('Build, bundle, and upload pack to the server')
|
|
109
175
|
.option('-s, --server <url>', 'Server URL (overrides config)')
|
|
110
|
-
.option('--
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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('\
|
|
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
|
|
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('\
|
|
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
package/dist/config.js
CHANGED
|
@@ -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;
|
package/dist/upload.d.ts
ADDED
|
@@ -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
|
+
}
|