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