@donut-games/cli 0.1.3 → 0.1.4

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/donut.js CHANGED
@@ -263,9 +263,9 @@ function spawnInstall(packageManager, workingDirectory) {
263
263
  }
264
264
 
265
265
  // ../cli/dist/commands/dev.js
266
- import path4 from "path";
267
- import fileSystemExtra4 from "fs-extra";
268
- import chalk2 from "chalk";
266
+ import path6 from "path";
267
+ import fileSystemExtra6 from "fs-extra";
268
+ import chalk3 from "chalk";
269
269
 
270
270
  // ../cli/dist/shared/monorepo-aliases.js
271
271
  import path3 from "path";
@@ -348,159 +348,18 @@ async function assertEveryPublishedSubpathHasAnAlias(monorepoRoot, resolvedAlias
348
348
  }
349
349
  }
350
350
 
351
- // ../cli/dist/commands/dev.js
352
- async function readProjectManifest(projectDirectory) {
353
- const manifestPath = path4.join(projectDirectory, "donut.json");
354
- if (!await fileSystemExtra4.pathExists(manifestPath)) {
355
- throw new Error(`No donut.json found in ${projectDirectory}. Run 'donut init' first.`);
356
- }
357
- const parsed = await fileSystemExtra4.readJson(manifestPath);
358
- if (typeof parsed.name !== "string" || parsed.name.length === 0) {
359
- throw new Error(`donut.json is missing required field 'name'.`);
360
- }
361
- if (parsed.rendererTarget !== "pixi-2d" && parsed.rendererTarget !== "three-3d") {
362
- throw new Error(`donut.json has invalid 'rendererTarget' \u2014 expected 'pixi-2d' or 'three-3d'.`);
363
- }
364
- return {
365
- name: parsed.name,
366
- rendererTarget: parsed.rendererTarget,
367
- donutEngineVersion: parsed.donutEngineVersion
368
- };
369
- }
370
- async function detectMonorepoRoot(projectDirectory) {
371
- let currentDirectory = path4.resolve(projectDirectory);
372
- for (let depth = 0; depth < 8; depth++) {
373
- const workspaceFile = path4.join(currentDirectory, "pnpm-workspace.yaml");
374
- if (await fileSystemExtra4.pathExists(workspaceFile)) {
375
- return currentDirectory;
376
- }
377
- const parentDirectory = path4.dirname(currentDirectory);
378
- if (parentDirectory === currentDirectory) {
379
- return void 0;
380
- }
381
- currentDirectory = parentDirectory;
382
- }
383
- return void 0;
384
- }
385
- async function writeDevEntryFiles(projectDirectory, manifest) {
386
- const donutDirectory = path4.join(projectDirectory, ".donut");
387
- await fileSystemExtra4.ensureDir(donutDirectory);
388
- const gitIgnorePath = path4.join(donutDirectory, ".gitignore");
389
- await fileSystemExtra4.writeFile(gitIgnorePath, "*\n", "utf8");
390
- const indexHtmlPath = path4.join(donutDirectory, "index.html");
391
- await fileSystemExtra4.writeFile(indexHtmlPath, buildIndexHtml(manifest), "utf8");
392
- const devEntryPath = path4.join(donutDirectory, "dev-entry.ts");
393
- await fileSystemExtra4.writeFile(devEntryPath, buildDevEntrySource(manifest), "utf8");
394
- return donutDirectory;
395
- }
396
- function buildIndexHtml(manifest) {
397
- return `<!doctype html>
398
- <html lang="en">
399
- <head>
400
- <meta charset="utf-8" />
401
- <meta name="viewport" content="width=device-width, initial-scale=1" />
402
- <title>${manifest.name} \u2014 Donut dev</title>
403
- <style>
404
- html, body { margin: 0; padding: 0; height: 100%; background: #0a0a12; overflow: hidden; }
405
- canvas#game-canvas { display: block; width: 100vw; height: 100vh; }
406
- </style>
407
- </head>
408
- <body>
409
- <canvas id="game-canvas"></canvas>
410
- <script type="module" src="./dev-entry.ts"></script>
411
- </body>
412
- </html>
413
- `;
414
- }
415
- function buildDevEntrySource(manifest) {
416
- return `// AUTO-GENERATED by \`donut dev\`. Do not edit \u2014 regenerated every launch.
417
- import { startGame } from '@donut/player';
418
- import { createDefaultScene } from '../src/scenes/default-scene';
419
-
420
- const discoveredComponentModules = import.meta.glob('../src/components/**/*.ts', { eager: true });
421
- const discoveredSystemModules = import.meta.glob('../src/systems/**/*.ts', { eager: true });
422
-
423
- for (const modulePath of Object.keys(discoveredComponentModules)) {
424
- console.log('[Donut dev] discovered component module:', modulePath);
425
- }
426
- for (const modulePath of Object.keys(discoveredSystemModules)) {
427
- console.log('[Donut dev] discovered system module:', modulePath);
428
- }
429
-
430
- async function bootstrapDevelopmentRuntime(): Promise<void> {
431
- const canvasElement = document.getElementById('game-canvas');
432
- if (!(canvasElement instanceof HTMLCanvasElement)) {
433
- throw new Error('[Donut dev] Missing <canvas id="game-canvas">');
434
- }
435
-
436
- await startGame({
437
- canvas: canvasElement,
438
- rendererTarget: '${manifest.rendererTarget}',
439
- sceneFactory: createDefaultScene,
440
- enableDevelopmentBridge: true,
441
- enableDebugOverlay: true,
442
- });
443
-
444
- console.log('[Donut dev] booted renderer=${manifest.rendererTarget}');
445
- }
446
-
447
- bootstrapDevelopmentRuntime().catch((error) => {
448
- console.error('[Donut dev] bootstrap failed', error);
449
- });
450
- `;
451
- }
452
- async function buildViteConfig(parameters) {
453
- const { projectDirectory, donutRoot, port, open } = parameters;
454
- const monorepoRoot = await detectMonorepoRoot(projectDirectory);
455
- const resolveAlias = monorepoRoot !== void 0 ? await resolveMonorepoDevAliases(monorepoRoot) : [];
456
- return {
457
- root: donutRoot,
458
- configFile: false,
459
- server: {
460
- port,
461
- open,
462
- strictPort: false,
463
- fs: {
464
- allow: [
465
- projectDirectory,
466
- ...monorepoRoot !== void 0 ? [monorepoRoot] : []
467
- ]
468
- }
469
- },
470
- resolve: {
471
- alias: resolveAlias
472
- },
473
- optimizeDeps: {
474
- // Let Vite figure out deps from the generated entry.
475
- }
476
- };
477
- }
478
- async function runDevServer(options) {
479
- const { projectDirectory, port = 5173, open = true, log = (message) => console.log(message), startViteServer } = options;
480
- const manifest = await readProjectManifest(projectDirectory);
481
- log(chalk2.cyan(`Donut dev \u2014 project '${manifest.name}' (${manifest.rendererTarget})`));
482
- const donutRoot = await writeDevEntryFiles(projectDirectory, manifest);
483
- const viteConfig = await buildViteConfig({ projectDirectory, donutRoot, port, open });
484
- const startServer = startViteServer ?? (async (configuration) => {
485
- const viteModule = await import("vite");
486
- return viteModule.createServer(configuration);
487
- });
488
- const server = await startServer(viteConfig);
489
- await server.listen();
490
- const resolvedLocalUrl = server.resolvedUrls?.local?.[0];
491
- if (resolvedLocalUrl !== void 0) {
492
- log(chalk2.green(`Dev server ready at ${resolvedLocalUrl}`));
493
- } else {
494
- log(chalk2.green(`Dev server listening on port ${port}`));
495
- }
496
- }
351
+ // ../cli/dist/commands/build.js
352
+ import path5 from "path";
353
+ import fileSystemExtra5 from "fs-extra";
354
+ import chalk2 from "chalk";
355
+ import JSZip from "jszip";
497
356
 
498
357
  // ../cli/dist/commands/validate.js
499
- import path5 from "path";
358
+ import path4 from "path";
500
359
  import { spawn as spawn2 } from "child_process";
501
360
  import { createRequire as createRequire2 } from "module";
502
361
  import { fileURLToPath as fileURLToPath2 } from "url";
503
- import fileSystemExtra5 from "fs-extra";
362
+ import fileSystemExtra4 from "fs-extra";
504
363
  var REQUIRED_MANIFEST_FIELDS = ["name", "version", "rendererTarget", "donutEngineVersion"];
505
364
  var ALLOWED_RENDERER_TARGETS = /* @__PURE__ */ new Set(["pixi-2d", "three-3d"]);
506
365
  var GAME_LOGIC_FORBIDDEN_RENDERER_IMPORT_PREFIXES = [
@@ -531,8 +390,8 @@ async function runValidation(options) {
531
390
  return { errors, warnings };
532
391
  }
533
392
  async function validateManifest(projectDirectory, errors) {
534
- const manifestPath = path5.join(projectDirectory, "donut.json");
535
- if (!await fileSystemExtra5.pathExists(manifestPath)) {
393
+ const manifestPath = path4.join(projectDirectory, "donut.json");
394
+ if (!await fileSystemExtra4.pathExists(manifestPath)) {
536
395
  errors.push({
537
396
  filePath: manifestPath,
538
397
  message: "donut.json is missing at the project root",
@@ -542,7 +401,7 @@ async function validateManifest(projectDirectory, errors) {
542
401
  }
543
402
  let raw;
544
403
  try {
545
- raw = await fileSystemExtra5.readFile(manifestPath, "utf8");
404
+ raw = await fileSystemExtra4.readFile(manifestPath, "utf8");
546
405
  } catch (readError) {
547
406
  errors.push({
548
407
  filePath: manifestPath,
@@ -593,8 +452,8 @@ async function validateManifest(projectDirectory, errors) {
593
452
  };
594
453
  }
595
454
  async function validateTypes(projectDirectory, errors) {
596
- const tsconfigPath = path5.join(projectDirectory, "tsconfig.json");
597
- if (!await fileSystemExtra5.pathExists(tsconfigPath)) {
455
+ const tsconfigPath = path4.join(projectDirectory, "tsconfig.json");
456
+ if (!await fileSystemExtra4.pathExists(tsconfigPath)) {
598
457
  return;
599
458
  }
600
459
  const typeScriptCompilerPath = resolveTypeScriptCompiler(projectDirectory);
@@ -613,7 +472,7 @@ ${stderr}`;
613
472
  let match;
614
473
  while ((match = diagnosticLineRegex.exec(combined)) !== null) {
615
474
  const [, rawFilePath, lineText, columnText, typeScriptCode, messageText] = match;
616
- const absoluteFilePath = path5.isAbsolute(rawFilePath) ? rawFilePath : path5.join(projectDirectory, rawFilePath);
475
+ const absoluteFilePath = path4.isAbsolute(rawFilePath) ? rawFilePath : path4.join(projectDirectory, rawFilePath);
617
476
  errors.push({
618
477
  filePath: absoluteFilePath,
619
478
  line: Number.parseInt(lineText, 10),
@@ -624,16 +483,16 @@ ${stderr}`;
624
483
  }
625
484
  }
626
485
  function resolveTypeScriptCompiler(projectDirectory) {
627
- const projectLocal = path5.join(projectDirectory, "node_modules", "typescript", "bin", "tsc");
628
- if (fileSystemExtra5.existsSync(projectLocal)) {
486
+ const projectLocal = path4.join(projectDirectory, "node_modules", "typescript", "bin", "tsc");
487
+ if (fileSystemExtra4.existsSync(projectLocal)) {
629
488
  return projectLocal;
630
489
  }
631
490
  try {
632
491
  const requireFromHere = createRequire2(import.meta.url);
633
492
  const typeScriptPackageJsonPath = requireFromHere.resolve("typescript/package.json");
634
- const typeScriptPackageDirectory = path5.dirname(typeScriptPackageJsonPath);
635
- const fallback = path5.join(typeScriptPackageDirectory, "bin", "tsc");
636
- if (fileSystemExtra5.existsSync(fallback)) {
493
+ const typeScriptPackageDirectory = path4.dirname(typeScriptPackageJsonPath);
494
+ const fallback = path4.join(typeScriptPackageDirectory, "bin", "tsc");
495
+ if (fileSystemExtra4.existsSync(fallback)) {
637
496
  return fallback;
638
497
  }
639
498
  } catch {
@@ -668,17 +527,17 @@ async function validateImportGraph(projectDirectory, rendererTarget, errors) {
668
527
  }
669
528
  async function validateGameLogicImports(projectDirectory, errors) {
670
529
  const gameLogicDirectories = [
671
- path5.join(projectDirectory, "src", "components"),
672
- path5.join(projectDirectory, "src", "systems")
530
+ path4.join(projectDirectory, "src", "components"),
531
+ path4.join(projectDirectory, "src", "systems")
673
532
  ];
674
533
  const importLineRegex = /^\s*import\s+[^;]*?from\s+['"]([^'"]+)['"]/;
675
534
  for (const directoryPath of gameLogicDirectories) {
676
- if (!await fileSystemExtra5.pathExists(directoryPath)) {
535
+ if (!await fileSystemExtra4.pathExists(directoryPath)) {
677
536
  continue;
678
537
  }
679
538
  const typeScriptFiles = await collectTypeScriptFiles(directoryPath);
680
539
  for (const filePath of typeScriptFiles) {
681
- const contents = await fileSystemExtra5.readFile(filePath, "utf8");
540
+ const contents = await fileSystemExtra4.readFile(filePath, "utf8");
682
541
  const lines = contents.split(/\r?\n/);
683
542
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
684
543
  const lineText = lines[lineIndex];
@@ -708,14 +567,14 @@ async function validateWrongRendererImports(projectDirectory, rendererTarget, er
708
567
  if (wrongRendererImportSpecifierPrefix === void 0 || allowedRendererImportSpecifierPrefix === void 0) {
709
568
  return;
710
569
  }
711
- const sourceRoot = path5.join(projectDirectory, "src");
712
- if (!await fileSystemExtra5.pathExists(sourceRoot)) {
570
+ const sourceRoot = path4.join(projectDirectory, "src");
571
+ if (!await fileSystemExtra4.pathExists(sourceRoot)) {
713
572
  return;
714
573
  }
715
574
  const importLineRegex = /^\s*import\s+[^;]*?from\s+['"]([^'"]+)['"]/;
716
575
  const typeScriptFiles = await collectTypeScriptFiles(sourceRoot);
717
576
  for (const filePath of typeScriptFiles) {
718
- const contents = await fileSystemExtra5.readFile(filePath, "utf8");
577
+ const contents = await fileSystemExtra4.readFile(filePath, "utf8");
719
578
  const lines = contents.split(/\r?\n/);
720
579
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
721
580
  const lineText = lines[lineIndex];
@@ -737,9 +596,9 @@ async function validateWrongRendererImports(projectDirectory, rendererTarget, er
737
596
  }
738
597
  async function collectTypeScriptFiles(directoryPath) {
739
598
  const collected = [];
740
- const entries = await fileSystemExtra5.readdir(directoryPath, { withFileTypes: true });
599
+ const entries = await fileSystemExtra4.readdir(directoryPath, { withFileTypes: true });
741
600
  for (const entry of entries) {
742
- const entryPath = path5.join(directoryPath, entry.name);
601
+ const entryPath = path4.join(directoryPath, entry.name);
743
602
  if (entry.isDirectory()) {
744
603
  collected.push(...await collectTypeScriptFiles(entryPath));
745
604
  } else if (entry.isFile() && entry.name.endsWith(".ts")) {
@@ -749,16 +608,16 @@ async function collectTypeScriptFiles(directoryPath) {
749
608
  return collected;
750
609
  }
751
610
  async function validateAssetReferences(projectDirectory, errors) {
752
- const sourceRoot = path5.join(projectDirectory, "src");
753
- const assetsRoot = path5.join(projectDirectory, "assets");
754
- if (!await fileSystemExtra5.pathExists(sourceRoot)) {
611
+ const sourceRoot = path4.join(projectDirectory, "src");
612
+ const assetsRoot = path4.join(projectDirectory, "assets");
613
+ if (!await fileSystemExtra4.pathExists(sourceRoot)) {
755
614
  return;
756
615
  }
757
616
  const typeScriptFiles = await collectTypeScriptFiles(sourceRoot);
758
617
  const texturePathRegex = /texturePath\s*(?::[^='"]*)?=?\s*['"]([^'"]*)['"]/g;
759
618
  const assetsLiteralRegex = /['"]assets\/([^'"]+)['"]/g;
760
619
  for (const filePath of typeScriptFiles) {
761
- const contents = await fileSystemExtra5.readFile(filePath, "utf8");
620
+ const contents = await fileSystemExtra4.readFile(filePath, "utf8");
762
621
  const lines = contents.split(/\r?\n/);
763
622
  const referencedAssets = [];
764
623
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
@@ -783,8 +642,8 @@ async function validateAssetReferences(projectDirectory, errors) {
783
642
  }
784
643
  for (const { relativePath, line } of referencedAssets) {
785
644
  const normalized = relativePath.startsWith("assets/") ? relativePath.slice("assets/".length) : relativePath;
786
- const absoluteAssetPath = path5.join(assetsRoot, normalized);
787
- if (!await fileSystemExtra5.pathExists(absoluteAssetPath)) {
645
+ const absoluteAssetPath = path4.join(assetsRoot, normalized);
646
+ if (!await fileSystemExtra4.pathExists(absoluteAssetPath)) {
788
647
  errors.push({
789
648
  filePath,
790
649
  line,
@@ -797,10 +656,6 @@ async function validateAssetReferences(projectDirectory, errors) {
797
656
  }
798
657
 
799
658
  // ../cli/dist/commands/build.js
800
- import path6 from "path";
801
- import fileSystemExtra6 from "fs-extra";
802
- import chalk3 from "chalk";
803
- import JSZip from "jszip";
804
659
  var BuildViolationError = class extends Error {
805
660
  violations;
806
661
  constructor(message, violations) {
@@ -823,13 +678,13 @@ var FORBIDDEN_PATTERNS = [
823
678
  { pattern: /\bdocument\b/g, severity: "warning" }
824
679
  ];
825
680
  async function collectTypeScriptFilesRecursively(directoryPath) {
826
- if (!await fileSystemExtra6.pathExists(directoryPath)) {
681
+ if (!await fileSystemExtra5.pathExists(directoryPath)) {
827
682
  return [];
828
683
  }
829
684
  const collected = [];
830
- const entries = await fileSystemExtra6.readdir(directoryPath, { withFileTypes: true });
685
+ const entries = await fileSystemExtra5.readdir(directoryPath, { withFileTypes: true });
831
686
  for (const entry of entries) {
832
- const entryPath = path6.join(directoryPath, entry.name);
687
+ const entryPath = path5.join(directoryPath, entry.name);
833
688
  if (entry.isDirectory()) {
834
689
  collected.push(...await collectTypeScriptFilesRecursively(entryPath));
835
690
  } else if (entry.isFile() && entry.name.endsWith(".ts")) {
@@ -840,14 +695,14 @@ async function collectTypeScriptFilesRecursively(directoryPath) {
840
695
  }
841
696
  async function scanUserSourcesForForbiddenPatterns(projectDirectory) {
842
697
  const directoriesToScan = [
843
- path6.join(projectDirectory, "src", "components"),
844
- path6.join(projectDirectory, "src", "systems")
698
+ path5.join(projectDirectory, "src", "components"),
699
+ path5.join(projectDirectory, "src", "systems")
845
700
  ];
846
701
  const violations = [];
847
702
  for (const directoryPath of directoriesToScan) {
848
703
  const files = await collectTypeScriptFilesRecursively(directoryPath);
849
704
  for (const filePath of files) {
850
- const contents = await fileSystemExtra6.readFile(filePath, "utf8");
705
+ const contents = await fileSystemExtra5.readFile(filePath, "utf8");
851
706
  const lines = contents.split(/\r?\n/);
852
707
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
853
708
  const lineText = lines[lineIndex];
@@ -870,11 +725,11 @@ async function scanUserSourcesForForbiddenPatterns(projectDirectory) {
870
725
  return violations;
871
726
  }
872
727
  async function detectProjectSourceKind(projectDirectory, log) {
873
- const projectPackageJsonPath = path6.join(projectDirectory, "package.json");
874
- if (!await fileSystemExtra6.pathExists(projectPackageJsonPath)) {
728
+ const projectPackageJsonPath = path5.join(projectDirectory, "package.json");
729
+ if (!await fileSystemExtra5.pathExists(projectPackageJsonPath)) {
875
730
  throw new Error(`Cannot determine project source kind \u2014 no package.json found at ${projectPackageJsonPath}.`);
876
731
  }
877
- const parsedManifest = await fileSystemExtra6.readJson(projectPackageJsonPath);
732
+ const parsedManifest = await fileSystemExtra5.readJson(projectPackageJsonPath);
878
733
  const mergedDependencies = {
879
734
  ...parsedManifest.dependencies ?? {},
880
735
  ...parsedManifest.devDependencies ?? {}
@@ -882,7 +737,7 @@ async function detectProjectSourceKind(projectDirectory, log) {
882
737
  const hasPublishedEngineDependency = "@donut-games/engine" in mergedDependencies;
883
738
  const hasAnyInternalWorkspaceDependency = Object.keys(mergedDependencies).some((dependencyName) => dependencyName.startsWith("@donut/"));
884
739
  if (hasPublishedEngineDependency && hasAnyInternalWorkspaceDependency) {
885
- log(chalk3.yellow("\u26A0 Both @donut-games/engine and internal @donut/* workspace dependencies are declared. Preferring published imports for the build entry."));
740
+ log(chalk2.yellow("\u26A0 Both @donut-games/engine and internal @donut/* workspace dependencies are declared. Preferring published imports for the build entry."));
886
741
  return "published";
887
742
  }
888
743
  if (hasPublishedEngineDependency) {
@@ -957,14 +812,14 @@ if (typeof document !== 'undefined') {
957
812
  `;
958
813
  }
959
814
  async function writeBuildEntryFile(projectDirectory, manifest, projectSourceKind) {
960
- const donutDirectory = path6.join(projectDirectory, ".donut");
961
- await fileSystemExtra6.ensureDir(donutDirectory);
962
- const gitIgnorePath = path6.join(donutDirectory, ".gitignore");
963
- if (!await fileSystemExtra6.pathExists(gitIgnorePath)) {
964
- await fileSystemExtra6.writeFile(gitIgnorePath, "*\n", "utf8");
965
- }
966
- const entryPath = path6.join(donutDirectory, "build-entry.ts");
967
- await fileSystemExtra6.writeFile(entryPath, buildBuildEntrySource(manifest, projectSourceKind), "utf8");
815
+ const donutDirectory = path5.join(projectDirectory, ".donut");
816
+ await fileSystemExtra5.ensureDir(donutDirectory);
817
+ const gitIgnorePath = path5.join(donutDirectory, ".gitignore");
818
+ if (!await fileSystemExtra5.pathExists(gitIgnorePath)) {
819
+ await fileSystemExtra5.writeFile(gitIgnorePath, "*\n", "utf8");
820
+ }
821
+ const entryPath = path5.join(donutDirectory, "build-entry.ts");
822
+ await fileSystemExtra5.writeFile(entryPath, buildBuildEntrySource(manifest, projectSourceKind), "utf8");
968
823
  return entryPath;
969
824
  }
970
825
  async function buildViteBuildConfig(parameters) {
@@ -996,42 +851,42 @@ async function buildViteBuildConfig(parameters) {
996
851
  }
997
852
  async function copyAssetsAndCollectEntries(projectAssetsDirectory, stagingAssetsDirectory, imageAssetOptimizer, log) {
998
853
  let sharpUnavailableWarningEmitted = false;
999
- if (!await fileSystemExtra6.pathExists(projectAssetsDirectory)) {
854
+ if (!await fileSystemExtra5.pathExists(projectAssetsDirectory)) {
1000
855
  return [];
1001
856
  }
1002
- await fileSystemExtra6.ensureDir(stagingAssetsDirectory);
857
+ await fileSystemExtra5.ensureDir(stagingAssetsDirectory);
1003
858
  const entries = [];
1004
859
  async function walk(currentSource, currentDestination, relativePrefix) {
1005
- const directoryEntries = await fileSystemExtra6.readdir(currentSource, { withFileTypes: true });
860
+ const directoryEntries = await fileSystemExtra5.readdir(currentSource, { withFileTypes: true });
1006
861
  for (const directoryEntry of directoryEntries) {
1007
862
  if (directoryEntry.name === ".gitkeep") {
1008
863
  continue;
1009
864
  }
1010
- const sourcePath = path6.join(currentSource, directoryEntry.name);
1011
- const destinationPath = path6.join(currentDestination, directoryEntry.name);
865
+ const sourcePath = path5.join(currentSource, directoryEntry.name);
866
+ const destinationPath = path5.join(currentDestination, directoryEntry.name);
1012
867
  const relativePath = relativePrefix === "" ? directoryEntry.name : `${relativePrefix}/${directoryEntry.name}`;
1013
868
  if (directoryEntry.isDirectory()) {
1014
- await fileSystemExtra6.ensureDir(destinationPath);
869
+ await fileSystemExtra5.ensureDir(destinationPath);
1015
870
  await walk(sourcePath, destinationPath, relativePath);
1016
871
  } else if (directoryEntry.isFile()) {
1017
- const extensionWithDot = path6.extname(directoryEntry.name).toLowerCase();
872
+ const extensionWithDot = path5.extname(directoryEntry.name).toLowerCase();
1018
873
  const extension = extensionWithDot.startsWith(".") ? extensionWithDot.slice(1) : extensionWithDot;
1019
874
  const isOptimizableImage = ["png", "jpg", "jpeg", "webp"].includes(extension);
1020
875
  let wroteOptimizedOutput = false;
1021
876
  if (isOptimizableImage) {
1022
877
  const optimizedBuffer = await imageAssetOptimizer(sourcePath, extension);
1023
878
  if (optimizedBuffer !== null) {
1024
- await fileSystemExtra6.writeFile(destinationPath, optimizedBuffer);
879
+ await fileSystemExtra5.writeFile(destinationPath, optimizedBuffer);
1025
880
  wroteOptimizedOutput = true;
1026
881
  } else if (!sharpUnavailableWarningEmitted) {
1027
- log(chalk3.yellow("\u2139 sharp not installed \u2014 assets copied uncompressed"));
882
+ log(chalk2.yellow("\u2139 sharp not installed \u2014 assets copied uncompressed"));
1028
883
  sharpUnavailableWarningEmitted = true;
1029
884
  }
1030
885
  }
1031
886
  if (!wroteOptimizedOutput) {
1032
- await fileSystemExtra6.copy(sourcePath, destinationPath);
887
+ await fileSystemExtra5.copy(sourcePath, destinationPath);
1033
888
  }
1034
- const stat = await fileSystemExtra6.stat(destinationPath);
889
+ const stat = await fileSystemExtra5.stat(destinationPath);
1035
890
  entries.push({
1036
891
  name: directoryEntry.name,
1037
892
  path: `assets/${relativePath}`,
@@ -1049,11 +904,11 @@ async function collectRuntimeVersions(projectDirectory) {
1049
904
  const monorepoRoot = await detectMonorepoRoot(projectDirectory);
1050
905
  const donutPackageNames = ["core", "math", "pixi", "three", "ctrllr", "player"];
1051
906
  if (monorepoRoot !== void 0) {
1052
- const packagesDirectory = path6.join(monorepoRoot, "packages");
907
+ const packagesDirectory = path5.join(monorepoRoot, "packages");
1053
908
  for (const packageName of donutPackageNames) {
1054
- const packageJsonPath = path6.join(packagesDirectory, packageName, "package.json");
1055
- if (await fileSystemExtra6.pathExists(packageJsonPath)) {
1056
- const parsed = await fileSystemExtra6.readJson(packageJsonPath);
909
+ const packageJsonPath = path5.join(packagesDirectory, packageName, "package.json");
910
+ if (await fileSystemExtra5.pathExists(packageJsonPath)) {
911
+ const parsed = await fileSystemExtra5.readJson(packageJsonPath);
1057
912
  if (typeof parsed.name === "string" && typeof parsed.version === "string") {
1058
913
  runtimeVersions[parsed.name] = parsed.version;
1059
914
  }
@@ -1069,8 +924,8 @@ async function runViteBuildProgrammatically(viteConfig) {
1069
924
  async function runBuild(options) {
1070
925
  const log = options.log ?? ((message) => console.log(message));
1071
926
  const startTimeMilliseconds = Date.now();
1072
- const projectDirectory = path6.resolve(options.projectDirectory);
1073
- const outputDirectory = path6.resolve(options.outputDirectory ?? path6.join(projectDirectory, "dist"));
927
+ const projectDirectory = path5.resolve(options.projectDirectory);
928
+ const outputDirectory = path5.resolve(options.outputDirectory ?? path5.join(projectDirectory, "dist"));
1074
929
  const validationReport = await runValidation({
1075
930
  projectDirectory,
1076
931
  skipTypeCheck: true
@@ -1090,11 +945,11 @@ ${formattedErrors}`, []);
1090
945
  log(`Donut build \u2014 project '${donutManifest.name}' (${donutManifest.rendererTarget})`);
1091
946
  const projectSourceKind = await detectProjectSourceKind(projectDirectory, log);
1092
947
  const buildEntryPath = await writeBuildEntryFile(projectDirectory, donutManifest, projectSourceKind);
1093
- const stagingDirectory = path6.join(outputDirectory, "bundle-staging");
1094
- await fileSystemExtra6.ensureDir(outputDirectory);
1095
- await fileSystemExtra6.remove(stagingDirectory);
1096
- await fileSystemExtra6.ensureDir(stagingDirectory);
1097
- const donutRoot = path6.join(projectDirectory, ".donut");
948
+ const stagingDirectory = path5.join(outputDirectory, "bundle-staging");
949
+ await fileSystemExtra5.ensureDir(outputDirectory);
950
+ await fileSystemExtra5.remove(stagingDirectory);
951
+ await fileSystemExtra5.ensureDir(stagingDirectory);
952
+ const donutRoot = path5.join(projectDirectory, ".donut");
1098
953
  const viteConfig = await buildViteBuildConfig({
1099
954
  projectDirectory,
1100
955
  donutRoot,
@@ -1103,18 +958,18 @@ ${formattedErrors}`, []);
1103
958
  });
1104
959
  const runVite = options.runViteBuild ?? runViteBuildProgrammatically;
1105
960
  await runVite(viteConfig);
1106
- const builtGameJsPath = path6.join(stagingDirectory, "game.js");
1107
- if (!await fileSystemExtra6.pathExists(builtGameJsPath)) {
961
+ const builtGameJsPath = path5.join(stagingDirectory, "game.js");
962
+ if (!await fileSystemExtra5.pathExists(builtGameJsPath)) {
1108
963
  throw new Error(`Expected Vite build to emit game.js at ${builtGameJsPath} \u2014 file not found.`);
1109
964
  }
1110
- const projectAssetsDirectory = path6.join(projectDirectory, "assets");
1111
- const stagingAssetsDirectory = path6.join(stagingDirectory, "assets");
965
+ const projectAssetsDirectory = path5.join(projectDirectory, "assets");
966
+ const stagingAssetsDirectory = path5.join(stagingDirectory, "assets");
1112
967
  const imageAssetOptimizer = options.optimizeImageAsset ?? optimizeImageAssetIfPossible;
1113
968
  const assetEntries = await copyAssetsAndCollectEntries(projectAssetsDirectory, stagingAssetsDirectory, imageAssetOptimizer, log);
1114
969
  const runtimeVersions = await collectRuntimeVersions(projectDirectory);
1115
- const gameJsContents = await fileSystemExtra6.readFile(builtGameJsPath);
970
+ const gameJsContents = await fileSystemExtra5.readFile(builtGameJsPath);
1116
971
  const bundleFileName = `${donutManifest.name}-${donutManifest.version ?? "0.0.0"}.donut`;
1117
- const bundlePath = path6.join(outputDirectory, bundleFileName);
972
+ const bundlePath = path5.join(outputDirectory, bundleFileName);
1118
973
  const projectManifestVersion = await readProjectManifestVersion(projectDirectory);
1119
974
  const partialManifest = {
1120
975
  name: donutManifest.name,
@@ -1129,8 +984,8 @@ ${formattedErrors}`, []);
1129
984
  const zipArchive = new JSZip();
1130
985
  zipArchive.file("game.js", gameJsContents);
1131
986
  for (const assetEntry of assetEntries) {
1132
- const absoluteAssetPath = path6.join(stagingDirectory, assetEntry.path);
1133
- const assetBuffer = await fileSystemExtra6.readFile(absoluteAssetPath);
987
+ const absoluteAssetPath = path5.join(stagingDirectory, assetEntry.path);
988
+ const assetBuffer = await fileSystemExtra5.readFile(absoluteAssetPath);
1134
989
  zipArchive.file(assetEntry.path, assetBuffer);
1135
990
  }
1136
991
  let candidateBundleSize = 0;
@@ -1155,8 +1010,8 @@ ${formattedErrors}`, []);
1155
1010
  }
1156
1011
  candidateBundleSize = finalBuffer.byteLength;
1157
1012
  }
1158
- await fileSystemExtra6.writeFile(bundlePath, finalBuffer);
1159
- const bundleStat = await fileSystemExtra6.stat(bundlePath);
1013
+ await fileSystemExtra5.writeFile(bundlePath, finalBuffer);
1014
+ const bundleStat = await fileSystemExtra5.stat(bundlePath);
1160
1015
  if (bundleStat.size !== finalManifest.bundleSizeBytes) {
1161
1016
  throw new Error(`Internal build error: bundleSizeBytes (${finalManifest.bundleSizeBytes}) does not match on-disk archive size (${bundleStat.size}).`);
1162
1017
  }
@@ -1206,14 +1061,163 @@ async function optimizeImageAssetIfPossible(sourcePath, extension) {
1206
1061
  }
1207
1062
  }
1208
1063
  async function readProjectManifestVersion(projectDirectory) {
1209
- const manifestPath = path6.join(projectDirectory, "donut.json");
1210
- if (!await fileSystemExtra6.pathExists(manifestPath)) {
1064
+ const manifestPath = path5.join(projectDirectory, "donut.json");
1065
+ if (!await fileSystemExtra5.pathExists(manifestPath)) {
1211
1066
  return "0.0.0";
1212
1067
  }
1213
- const raw = await fileSystemExtra6.readJson(manifestPath);
1068
+ const raw = await fileSystemExtra5.readJson(manifestPath);
1214
1069
  return typeof raw.version === "string" ? raw.version : "0.0.0";
1215
1070
  }
1216
1071
 
1072
+ // ../cli/dist/commands/dev.js
1073
+ async function readProjectManifest(projectDirectory) {
1074
+ const manifestPath = path6.join(projectDirectory, "donut.json");
1075
+ if (!await fileSystemExtra6.pathExists(manifestPath)) {
1076
+ throw new Error(`No donut.json found in ${projectDirectory}. Run 'donut init' first.`);
1077
+ }
1078
+ const parsed = await fileSystemExtra6.readJson(manifestPath);
1079
+ if (typeof parsed.name !== "string" || parsed.name.length === 0) {
1080
+ throw new Error(`donut.json is missing required field 'name'.`);
1081
+ }
1082
+ if (parsed.rendererTarget !== "pixi-2d" && parsed.rendererTarget !== "three-3d") {
1083
+ throw new Error(`donut.json has invalid 'rendererTarget' \u2014 expected 'pixi-2d' or 'three-3d'.`);
1084
+ }
1085
+ return {
1086
+ name: parsed.name,
1087
+ rendererTarget: parsed.rendererTarget,
1088
+ donutEngineVersion: parsed.donutEngineVersion
1089
+ };
1090
+ }
1091
+ async function detectMonorepoRoot(projectDirectory) {
1092
+ let currentDirectory = path6.resolve(projectDirectory);
1093
+ for (let depth = 0; depth < 8; depth++) {
1094
+ const workspaceFile = path6.join(currentDirectory, "pnpm-workspace.yaml");
1095
+ if (await fileSystemExtra6.pathExists(workspaceFile)) {
1096
+ return currentDirectory;
1097
+ }
1098
+ const parentDirectory = path6.dirname(currentDirectory);
1099
+ if (parentDirectory === currentDirectory) {
1100
+ return void 0;
1101
+ }
1102
+ currentDirectory = parentDirectory;
1103
+ }
1104
+ return void 0;
1105
+ }
1106
+ async function writeDevEntryFiles(projectDirectory, manifest, projectSourceKind) {
1107
+ const donutDirectory = path6.join(projectDirectory, ".donut");
1108
+ await fileSystemExtra6.ensureDir(donutDirectory);
1109
+ const gitIgnorePath = path6.join(donutDirectory, ".gitignore");
1110
+ await fileSystemExtra6.writeFile(gitIgnorePath, "*\n", "utf8");
1111
+ const indexHtmlPath = path6.join(donutDirectory, "index.html");
1112
+ await fileSystemExtra6.writeFile(indexHtmlPath, buildIndexHtml(manifest), "utf8");
1113
+ const devEntryPath = path6.join(donutDirectory, "dev-entry.ts");
1114
+ await fileSystemExtra6.writeFile(devEntryPath, buildDevEntrySource(manifest, projectSourceKind), "utf8");
1115
+ return donutDirectory;
1116
+ }
1117
+ function buildIndexHtml(manifest) {
1118
+ return `<!doctype html>
1119
+ <html lang="en">
1120
+ <head>
1121
+ <meta charset="utf-8" />
1122
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1123
+ <title>${manifest.name} \u2014 Donut dev</title>
1124
+ <style>
1125
+ html, body { margin: 0; padding: 0; height: 100%; background: #0a0a12; overflow: hidden; }
1126
+ canvas#game-canvas { display: block; width: 100vw; height: 100vh; }
1127
+ </style>
1128
+ </head>
1129
+ <body>
1130
+ <canvas id="game-canvas"></canvas>
1131
+ <script type="module" src="./dev-entry.ts"></script>
1132
+ </body>
1133
+ </html>
1134
+ `;
1135
+ }
1136
+ function buildDevEntrySource(manifest, projectSourceKind) {
1137
+ const playerImportSpecifier = projectSourceKind === "published" ? "@donut-games/engine/player" : "@donut/player";
1138
+ return `// AUTO-GENERATED by \`donut dev\`. Do not edit \u2014 regenerated every launch.
1139
+ import { startGame } from '${playerImportSpecifier}';
1140
+ import { createDefaultScene } from '../src/scenes/default-scene';
1141
+
1142
+ const discoveredComponentModules = import.meta.glob('../src/components/**/*.ts', { eager: true });
1143
+ const discoveredSystemModules = import.meta.glob('../src/systems/**/*.ts', { eager: true });
1144
+
1145
+ for (const modulePath of Object.keys(discoveredComponentModules)) {
1146
+ console.log('[Donut dev] discovered component module:', modulePath);
1147
+ }
1148
+ for (const modulePath of Object.keys(discoveredSystemModules)) {
1149
+ console.log('[Donut dev] discovered system module:', modulePath);
1150
+ }
1151
+
1152
+ async function bootstrapDevelopmentRuntime(): Promise<void> {
1153
+ const canvasElement = document.getElementById('game-canvas');
1154
+ if (!(canvasElement instanceof HTMLCanvasElement)) {
1155
+ throw new Error('[Donut dev] Missing <canvas id="game-canvas">');
1156
+ }
1157
+
1158
+ await startGame({
1159
+ canvas: canvasElement,
1160
+ rendererTarget: '${manifest.rendererTarget}',
1161
+ sceneFactory: createDefaultScene,
1162
+ enableDevelopmentBridge: true,
1163
+ enableDebugOverlay: true,
1164
+ });
1165
+
1166
+ console.log('[Donut dev] booted renderer=${manifest.rendererTarget}');
1167
+ }
1168
+
1169
+ bootstrapDevelopmentRuntime().catch((error) => {
1170
+ console.error('[Donut dev] bootstrap failed', error);
1171
+ });
1172
+ `;
1173
+ }
1174
+ async function buildViteConfig(parameters) {
1175
+ const { projectDirectory, donutRoot, port, open } = parameters;
1176
+ const monorepoRoot = await detectMonorepoRoot(projectDirectory);
1177
+ const resolveAlias = monorepoRoot !== void 0 ? await resolveMonorepoDevAliases(monorepoRoot) : [];
1178
+ return {
1179
+ root: donutRoot,
1180
+ configFile: false,
1181
+ server: {
1182
+ port,
1183
+ open,
1184
+ strictPort: false,
1185
+ fs: {
1186
+ allow: [
1187
+ projectDirectory,
1188
+ ...monorepoRoot !== void 0 ? [monorepoRoot] : []
1189
+ ]
1190
+ }
1191
+ },
1192
+ resolve: {
1193
+ alias: resolveAlias
1194
+ },
1195
+ optimizeDeps: {
1196
+ // Let Vite figure out deps from the generated entry.
1197
+ }
1198
+ };
1199
+ }
1200
+ async function runDevServer(options) {
1201
+ const { projectDirectory, port = 5173, open = true, log = (message) => console.log(message), startViteServer } = options;
1202
+ const manifest = await readProjectManifest(projectDirectory);
1203
+ log(chalk3.cyan(`Donut dev \u2014 project '${manifest.name}' (${manifest.rendererTarget})`));
1204
+ const projectSourceKind = await detectProjectSourceKind(projectDirectory, log);
1205
+ const donutRoot = await writeDevEntryFiles(projectDirectory, manifest, projectSourceKind);
1206
+ const viteConfig = await buildViteConfig({ projectDirectory, donutRoot, port, open });
1207
+ const startServer = startViteServer ?? (async (configuration) => {
1208
+ const viteModule = await import("vite");
1209
+ return viteModule.createServer(configuration);
1210
+ });
1211
+ const server = await startServer(viteConfig);
1212
+ await server.listen();
1213
+ const resolvedLocalUrl = server.resolvedUrls?.local?.[0];
1214
+ if (resolvedLocalUrl !== void 0) {
1215
+ log(chalk3.green(`Dev server ready at ${resolvedLocalUrl}`));
1216
+ } else {
1217
+ log(chalk3.green(`Dev server listening on port ${port}`));
1218
+ }
1219
+ }
1220
+
1217
1221
  // ../cli/dist/commands/publish.js
1218
1222
  import path7 from "path";
1219
1223
  import fileSystemExtra7 from "fs-extra";