@evitcastudio/kit 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +224 -123
  3. package/lib/bundle/cli/cli.js +491 -59
  4. package/lib/bundle/cli/kit-game-templates/multi/_gitignore +180 -180
  5. package/lib/bundle/cli/kit-game-templates/multi/package.json +3 -3
  6. package/lib/bundle/cli/kit-game-templates/multi/resource.json +1 -0
  7. package/lib/bundle/cli/kit-game-templates/multi/src/client/packets/s-packets.ts +4 -4
  8. package/lib/bundle/cli/kit-game-templates/multi/src/server/packets/c-packets.ts +4 -4
  9. package/lib/bundle/cli/kit-game-templates/single/_gitignore +180 -180
  10. package/lib/bundle/cli/kit-game-templates/single/package.json +3 -3
  11. package/lib/bundle/cli/kit-game-templates/single/resource.json +1 -0
  12. package/lib/cli/app-bundler.d.ts +29 -0
  13. package/lib/cli/app-bundler.d.ts.map +1 -0
  14. package/lib/cli/app-bundler.js +230 -0
  15. package/lib/cli/create.d.ts +11 -0
  16. package/lib/cli/create.d.ts.map +1 -0
  17. package/lib/cli/create.js +88 -0
  18. package/lib/cli/doctor.d.ts +9 -0
  19. package/lib/cli/doctor.d.ts.map +1 -0
  20. package/lib/cli/doctor.js +179 -0
  21. package/lib/cli/host.d.ts +49 -0
  22. package/lib/cli/host.d.ts.map +1 -0
  23. package/lib/cli/host.js +127 -0
  24. package/lib/cli/init.d.ts +2 -0
  25. package/lib/cli/init.d.ts.map +1 -1
  26. package/lib/cli/init.js +42 -15
  27. package/lib/cli/main.d.ts +22 -1
  28. package/lib/cli/main.d.ts.map +1 -1
  29. package/lib/cli/main.js +28 -1
  30. package/lib/cli/resource-builder.d.ts +1 -1
  31. package/lib/cli/resource-builder.d.ts.map +1 -1
  32. package/lib/cli/resource-builder.js +264 -19
  33. package/lib/cli/types.d.ts +12 -6
  34. package/lib/cli/types.d.ts.map +1 -1
  35. package/lib/types/vylo.d.ts +3848 -3848
  36. package/lib/vendor/vyi/index.js +26 -26
  37. package/package.json +73 -73
  38. package/lib/bundle/cli/kit-game-templates/multi/bun-build.ts +0 -109
  39. package/lib/bundle/cli/kit-game-templates/single/bun-build.ts +0 -33
  40. package/lib/bundle/cli/kit-game-templates/single/bun-serve.ts +0 -28
@@ -2,5 +2,5 @@ import type { ProcessOptions } from './types';
2
2
  /**
3
3
  * Main entry point for building resources.
4
4
  */
5
- export declare function processResources({ inDirectory, outDirectory, verbose, ignoreSound }: ProcessOptions): Promise<void>;
5
+ export declare function processResources({ inDirectory, outDirectory, manifestPath, watch, verbose, ignoreSound, app, minify, obfuscate, sourcemap, prod }: ProcessOptions): Promise<void>;
6
6
  //# sourceMappingURL=resource-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resource-builder.d.ts","sourceRoot":"","sources":["../../src/cli/resource-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAsP9C;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzH"}
1
+ {"version":3,"file":"resource-builder.d.ts","sourceRoot":"","sources":["../../src/cli/resource-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAggB9C;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCvL"}
@@ -1,8 +1,9 @@
1
- import { promises as fs } from 'fs';
2
- import { join, extname, basename } from 'path';
1
+ import { promises as fs, watch as fsWatch, existsSync } from 'fs';
2
+ import { join, extname, basename, resolve } from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { v4 as uuidv4 } from 'uuid';
5
5
  import { VYI } from '../vendor/vyi';
6
+ import { bundleApp } from './app-bundler';
6
7
  // Logging helpers
7
8
  const log = console.log;
8
9
  const info = chalk.hex('#ffa552');
@@ -17,7 +18,9 @@ let isVerbose = false;
17
18
  let ignoringSound = false;
18
19
  let resourceInDirectory = '';
19
20
  let resourceOutDirectory = '';
21
+ let customManifestPath = undefined;
20
22
  const resourcesToProcess = [];
23
+ const subdirectoriesToMirror = [];
21
24
  /**
22
25
  * Initializes the resource JSON structure.
23
26
  */
@@ -70,8 +73,16 @@ async function processDirectory(pDirectoryPath) {
70
73
  const contents = await fs.readdir(pDirectoryPath);
71
74
  for (const item of contents) {
72
75
  const itemPath = join(pDirectoryPath, item);
76
+ // Skip the output directory if it is inside the input directory
77
+ if (resolve(itemPath) === resourceOutDirectory) {
78
+ continue;
79
+ }
73
80
  const stats = await fs.stat(itemPath);
74
81
  if (stats.isDirectory()) {
82
+ // If top-level subdirectory inside inDirectory, track for mirroring
83
+ if (pDirectoryPath === resourceInDirectory) {
84
+ subdirectoriesToMirror.push(item);
85
+ }
75
86
  await processDirectory(itemPath);
76
87
  }
77
88
  else if (isValidExtension(extname(itemPath).slice(1))) {
@@ -89,6 +100,28 @@ async function processDirectory(pDirectoryPath) {
89
100
  function isValidExtension(pExtension) {
90
101
  return VALID_EXTENSIONS.includes(pExtension);
91
102
  }
103
+ /**
104
+ * Recursively copies a directory to destination, preserving subdirectories and files.
105
+ * Ignores engine resource files that are already handled by Vylocity obfuscation.
106
+ */
107
+ async function mirrorDirectory(pSourceDir, pDestDir) {
108
+ const entries = await fs.readdir(pSourceDir, { withFileTypes: true });
109
+ await fs.mkdir(pDestDir, { recursive: true });
110
+ for (const entry of entries) {
111
+ const srcPath = join(pSourceDir, entry.name);
112
+ const destPath = join(pDestDir, entry.name);
113
+ if (entry.isDirectory()) {
114
+ await mirrorDirectory(srcPath, destPath);
115
+ }
116
+ else {
117
+ const ext = extname(entry.name).slice(1);
118
+ // Only copy non-engine files (images, custom JSON, fonts, etc.)
119
+ if (!isValidExtension(ext)) {
120
+ await fs.copyFile(srcPath, destPath);
121
+ }
122
+ }
123
+ }
124
+ }
92
125
  /**
93
126
  * Executes all file copy operations in parallel after preparation.
94
127
  */
@@ -108,10 +141,20 @@ async function processAllFiles() {
108
141
  });
109
142
  // Execute all copy operations concurrently
110
143
  await Promise.all(copyOperations);
144
+ // Mirror subdirectories (images, fonts, emitters, particles, etc.)
145
+ for (const subDir of subdirectoriesToMirror) {
146
+ const srcPath = join(resourceInDirectory, subDir);
147
+ const destPath = join(resourceOutDirectory, 'resources', subDir);
148
+ await mirrorDirectory(srcPath, destPath);
149
+ }
111
150
  logVerbose(`[Kit CLI] All resources have been processed.`);
112
151
  await saveResourceJSON();
113
152
  const boundsData = await buildBoundsJSON();
114
153
  await saveBoundsJSON(boundsData);
154
+ const pointsData = await buildIconPointsJSON();
155
+ await saveIconPointsJSON(pointsData);
156
+ const sizesData = await buildSizesJSON();
157
+ await saveSizesJSON(sizesData);
115
158
  }
116
159
  catch (pError) {
117
160
  const errorMessage = pError instanceof Error ? pError.message : String(pError);
@@ -172,12 +215,131 @@ async function buildBoundsJSON() {
172
215
  async function saveBoundsJSON(pBoundsData) {
173
216
  const filePath = 'bounds.json';
174
217
  try {
175
- await fs.writeFile(filePath, JSON.stringify(pBoundsData, null, 4));
218
+ await fs.writeFile(filePath, JSON.stringify(pBoundsData));
176
219
  }
177
220
  catch (pError) {
178
221
  logError(`[Error] Saving bounds JSON: ${pError}`);
179
222
  }
180
223
  }
224
+ /**
225
+ * Builds the icon points map from all processed vyi files.
226
+ */
227
+ async function buildIconPointsJSON() {
228
+ const pointsData = {};
229
+ for (const { filePath, type } of resourcesToProcess) {
230
+ if (type !== 'icon')
231
+ continue;
232
+ try {
233
+ const fileBuffer = await fs.readFile(filePath);
234
+ const vyi = new VYI().parse(fileBuffer);
235
+ const atlasName = basename(filePath, '.vyi');
236
+ const atlasEntry = {};
237
+ for (const icon of vyi.getIcons()) {
238
+ const iconName = icon.getName();
239
+ const iconPoints = icon.getIconPointsExport();
240
+ const hasIconPoints = iconPoints && iconPoints.length > 0;
241
+ const iconEntry = {};
242
+ if (hasIconPoints) {
243
+ const pointsMap = {};
244
+ for (const pt of iconPoints) {
245
+ pointsMap[pt.id] = {
246
+ width: pt.width,
247
+ height: pt.height,
248
+ x: pt.x,
249
+ y: pt.y
250
+ };
251
+ }
252
+ iconEntry.points = pointsMap;
253
+ }
254
+ const statesEntry = {};
255
+ for (const state of icon.getStates()) {
256
+ const stateName = state.getName();
257
+ const statePoints = state.getIconPointsExport();
258
+ if (statePoints && statePoints.length > 0) {
259
+ const statePointsMap = {};
260
+ for (const pt of statePoints) {
261
+ statePointsMap[pt.id] = {
262
+ width: pt.width,
263
+ height: pt.height,
264
+ x: pt.x,
265
+ y: pt.y
266
+ };
267
+ }
268
+ statesEntry[stateName] = {
269
+ points: statePointsMap
270
+ };
271
+ }
272
+ }
273
+ if (Object.keys(statesEntry).length > 0) {
274
+ iconEntry.states = statesEntry;
275
+ }
276
+ if (Object.keys(iconEntry).length > 0) {
277
+ atlasEntry[iconName] = iconEntry;
278
+ }
279
+ }
280
+ if (Object.keys(atlasEntry).length > 0) {
281
+ pointsData[atlasName] = atlasEntry;
282
+ }
283
+ }
284
+ catch (pError) {
285
+ logError(`[Error] Failed to parse icon points from ${filePath}: ${pError}`);
286
+ }
287
+ }
288
+ return pointsData;
289
+ }
290
+ /**
291
+ * Saves the icon points JSON to a file.
292
+ */
293
+ async function saveIconPointsJSON(pPointsData) {
294
+ const filePath = 'icon-points.json';
295
+ try {
296
+ await fs.writeFile(filePath, JSON.stringify(pPointsData));
297
+ }
298
+ catch (pError) {
299
+ logError(`[Error] Saving icon points JSON: ${pError}`);
300
+ }
301
+ }
302
+ /**
303
+ * Builds the sizes map from all processed vyi files.
304
+ */
305
+ async function buildSizesJSON() {
306
+ const sizesData = {};
307
+ for (const { filePath, type } of resourcesToProcess) {
308
+ if (type !== 'icon')
309
+ continue;
310
+ try {
311
+ const fileBuffer = await fs.readFile(filePath);
312
+ const vyi = new VYI().parse(fileBuffer);
313
+ const atlasName = basename(filePath, '.vyi');
314
+ const atlasEntry = {};
315
+ for (const icon of vyi.getIcons()) {
316
+ atlasEntry[icon.getName()] = {
317
+ width: icon.getWidth(),
318
+ height: icon.getHeight()
319
+ };
320
+ }
321
+ if (Object.keys(atlasEntry).length > 0) {
322
+ sizesData[atlasName] = atlasEntry;
323
+ }
324
+ }
325
+ catch (pError) {
326
+ logError(`[Error] Failed to parse sizes from ${filePath}: ${pError}`);
327
+ }
328
+ }
329
+ return sizesData;
330
+ }
331
+ /**
332
+ * Saves the sizes JSON to a file.
333
+ */
334
+ async function saveSizesJSON(pSizesData) {
335
+ const filePath = 'sizes.json';
336
+ try {
337
+ await fs.writeFile(filePath, JSON.stringify(pSizesData));
338
+ }
339
+ catch (pError) {
340
+ logError(`[Error] Saving sizes JSON: ${pError}`);
341
+ }
342
+ }
181
343
  /**
182
344
  * Clears specified directories within a base directory.
183
345
  * @param pBaseDirectory - The path to the base directory.
@@ -209,39 +371,122 @@ async function copyFile(pSource, pDestinationDir, pNewName) {
209
371
  * Saves the resource JSON to a file.
210
372
  */
211
373
  async function saveResourceJSON() {
212
- const filePath = 'resource.json';
374
+ const jsonContent = JSON.stringify(resourceJSON, null, 4);
375
+ const targetPath = customManifestPath || 'resource.json';
213
376
  try {
214
- await fs.writeFile(filePath, JSON.stringify(resourceJSON, null, 4));
377
+ await fs.writeFile(targetPath, jsonContent, 'utf8');
215
378
  }
216
379
  catch (pError) {
217
- logError(`[Error] Saving resource JSON: ${pError}`);
380
+ logError(`[Error] Saving resource JSON to ${targetPath}: ${pError}`);
381
+ }
382
+ }
383
+ let shouldBundleApp = false;
384
+ let projectRootDirectory = '';
385
+ let appBundleOptions = {};
386
+ /**
387
+ * Executes a single build pass over the input assets.
388
+ */
389
+ async function runBuild() {
390
+ resourceJSON = initializeResourceJSON();
391
+ resourcesToProcess.length = 0;
392
+ subdirectoriesToMirror.length = 0;
393
+ if (existsSync(resourceInDirectory)) {
394
+ await processDirectory(resourceInDirectory);
395
+ }
396
+ if (resourcesToProcess.length > 0 || subdirectoriesToMirror.length > 0) {
397
+ await processAllFiles();
398
+ }
399
+ else {
400
+ logAlert('No resources found!');
401
+ await saveResourceJSON();
402
+ await saveBoundsJSON({});
403
+ await saveIconPointsJSON({});
404
+ await saveSizesJSON({});
405
+ }
406
+ // If app bundling is requested or enabled by default, compile app and copy web assets
407
+ if (shouldBundleApp && projectRootDirectory) {
408
+ await bundleApp(projectRootDirectory, resourceOutDirectory, {
409
+ ...appBundleOptions,
410
+ verbose: Boolean(isVerbose)
411
+ });
218
412
  }
413
+ logVerbose(`[Kit CLI] Resources built successfully.`);
414
+ }
415
+ /**
416
+ * Watches the input directory for changes and triggers incremental rebuilds.
417
+ */
418
+ async function runWatch() {
419
+ await runBuild();
420
+ console.log(chalk.cyan(`\nšŸ‘€ Watching for changes in: ${chalk.bold(resourceInDirectory)}`));
421
+ let debounceTimer = null;
422
+ const triggerRebuild = (pFilename) => {
423
+ if (debounceTimer)
424
+ clearTimeout(debounceTimer);
425
+ debounceTimer = setTimeout(async () => {
426
+ console.log(chalk.dim(`\nFile changed: ${pFilename}, rebuilding...`));
427
+ await runBuild();
428
+ }, 150);
429
+ };
430
+ const watchers = [];
431
+ if (existsSync(resourceInDirectory)) {
432
+ const resWatcher = fsWatch(resourceInDirectory, { recursive: true }, (_eventType, pFilename) => {
433
+ if (pFilename)
434
+ triggerRebuild(pFilename);
435
+ });
436
+ watchers.push(resWatcher);
437
+ }
438
+ const srcDir = join(projectRootDirectory, 'src');
439
+ if (shouldBundleApp && existsSync(srcDir) && srcDir !== resourceInDirectory) {
440
+ const srcWatcher = fsWatch(srcDir, { recursive: true }, (_eventType, pFilename) => {
441
+ if (!pFilename)
442
+ return;
443
+ // Ignore resources folder if inside src to prevent double triggers
444
+ if (pFilename.startsWith('resources'))
445
+ return;
446
+ triggerRebuild(pFilename);
447
+ });
448
+ watchers.push(srcWatcher);
449
+ }
450
+ process.on('SIGINT', () => {
451
+ for (const w of watchers)
452
+ w.close();
453
+ process.exit(0);
454
+ });
219
455
  }
220
456
  /**
221
457
  * Main entry point for building resources.
222
458
  */
223
- export async function processResources({ inDirectory, outDirectory, verbose, ignoreSound }) {
224
- // Initialize state
225
- resourceInDirectory = inDirectory;
226
- resourceOutDirectory = outDirectory;
459
+ export async function processResources({ inDirectory, outDirectory, manifestPath, watch, verbose, ignoreSound, app, minify, obfuscate, sourcemap, prod }) {
460
+ projectRootDirectory = process.cwd();
461
+ // Smart defaults: ./src/resources -> ./dist
462
+ const resolvedIn = inDirectory || (existsSync(join(projectRootDirectory, 'src', 'resources')) ? 'src/resources' : '');
463
+ const resolvedOut = outDirectory || 'dist';
464
+ resourceInDirectory = resolvedIn ? resolve(resolvedIn) : '';
465
+ resourceOutDirectory = resolvedOut ? resolve(resolvedOut) : '';
466
+ customManifestPath = manifestPath;
227
467
  isVerbose = verbose;
228
468
  ignoringSound = ignoreSound;
229
- resourceJSON = initializeResourceJSON();
230
- resourcesToProcess.length = 0;
469
+ appBundleOptions = {
470
+ minify,
471
+ obfuscate,
472
+ sourcemap,
473
+ prod
474
+ };
475
+ // Enable app bundling if explicitly requested or if app entrypoints exist and app is not false
476
+ const hasAppEntry = existsSync(join(projectRootDirectory, 'src', 'index.ts')) ||
477
+ existsSync(join(projectRootDirectory, 'src', 'client', 'index.ts')) ||
478
+ existsSync(join(projectRootDirectory, 'src', 'server', 'index.ts'));
479
+ shouldBundleApp = app !== undefined ? app : hasAppEntry;
231
480
  // Validate inputs
232
481
  if (!resourceInDirectory || !resourceOutDirectory) {
233
482
  logError('[Error] Input and output directories must be specified');
234
483
  return;
235
484
  }
236
- // Process resources
237
- await processDirectory(resourceInDirectory);
238
- if (resourcesToProcess.length > 0) {
239
- await processAllFiles();
485
+ if (watch) {
486
+ await runWatch();
240
487
  }
241
488
  else {
242
- logAlert('No resources found!');
243
- await saveResourceJSON();
244
- await saveBoundsJSON({});
489
+ await runBuild();
245
490
  }
246
491
  }
247
492
  function logVerbose(pMessage) {
@@ -1,11 +1,17 @@
1
1
  /**
2
- * ProcessOptions for resource builder.
2
+ * Options for the resource builder and app build process.
3
3
  */
4
- type ProcessOptions = {
5
- inDirectory: string;
6
- outDirectory: string;
4
+ export interface ProcessOptions {
5
+ inDirectory?: string;
6
+ outDirectory?: string;
7
+ manifestPath?: string;
7
8
  verbose?: boolean;
8
9
  ignoreSound?: boolean;
9
- };
10
- export type { ProcessOptions };
10
+ watch?: boolean;
11
+ app?: boolean;
12
+ minify?: boolean;
13
+ obfuscate?: boolean;
14
+ sourcemap?: 'none' | 'linked' | 'inline' | 'external';
15
+ prod?: boolean;
16
+ }
11
17
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,KAAK,cAAc,GAAG;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,YAAY,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB"}