@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 +238 -234
- package/dist/donut.js.map +1 -1
- package/dist/index.js +238 -234
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
265
|
-
import
|
|
266
|
-
import
|
|
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/
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
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
|
|
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 =
|
|
533
|
-
if (!await
|
|
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
|
|
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 =
|
|
595
|
-
if (!await
|
|
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 =
|
|
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 =
|
|
626
|
-
if (
|
|
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 =
|
|
633
|
-
const fallback =
|
|
634
|
-
if (
|
|
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
|
-
|
|
670
|
-
|
|
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
|
|
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
|
|
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 =
|
|
710
|
-
if (!await
|
|
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
|
|
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
|
|
597
|
+
const entries = await fileSystemExtra4.readdir(directoryPath, { withFileTypes: true });
|
|
739
598
|
for (const entry of entries) {
|
|
740
|
-
const entryPath =
|
|
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 =
|
|
751
|
-
const assetsRoot =
|
|
752
|
-
if (!await
|
|
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
|
|
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 =
|
|
785
|
-
if (!await
|
|
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
|
|
679
|
+
if (!await fileSystemExtra5.pathExists(directoryPath)) {
|
|
825
680
|
return [];
|
|
826
681
|
}
|
|
827
682
|
const collected = [];
|
|
828
|
-
const entries = await
|
|
683
|
+
const entries = await fileSystemExtra5.readdir(directoryPath, { withFileTypes: true });
|
|
829
684
|
for (const entry of entries) {
|
|
830
|
-
const entryPath =
|
|
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
|
-
|
|
842
|
-
|
|
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
|
|
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 =
|
|
872
|
-
if (!await
|
|
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
|
|
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(
|
|
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 =
|
|
959
|
-
await
|
|
960
|
-
const gitIgnorePath =
|
|
961
|
-
if (!await
|
|
962
|
-
await
|
|
963
|
-
}
|
|
964
|
-
const entryPath =
|
|
965
|
-
await
|
|
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
|
|
852
|
+
if (!await fileSystemExtra5.pathExists(projectAssetsDirectory)) {
|
|
998
853
|
return [];
|
|
999
854
|
}
|
|
1000
|
-
await
|
|
855
|
+
await fileSystemExtra5.ensureDir(stagingAssetsDirectory);
|
|
1001
856
|
const entries = [];
|
|
1002
857
|
async function walk(currentSource, currentDestination, relativePrefix) {
|
|
1003
|
-
const directoryEntries = await
|
|
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 =
|
|
1009
|
-
const destinationPath =
|
|
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
|
|
867
|
+
await fileSystemExtra5.ensureDir(destinationPath);
|
|
1013
868
|
await walk(sourcePath, destinationPath, relativePath);
|
|
1014
869
|
} else if (directoryEntry.isFile()) {
|
|
1015
|
-
const extensionWithDot =
|
|
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
|
|
877
|
+
await fileSystemExtra5.writeFile(destinationPath, optimizedBuffer);
|
|
1023
878
|
wroteOptimizedOutput = true;
|
|
1024
879
|
} else if (!sharpUnavailableWarningEmitted) {
|
|
1025
|
-
log(
|
|
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
|
|
885
|
+
await fileSystemExtra5.copy(sourcePath, destinationPath);
|
|
1031
886
|
}
|
|
1032
|
-
const stat = await
|
|
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 =
|
|
905
|
+
const packagesDirectory = path5.join(monorepoRoot, "packages");
|
|
1051
906
|
for (const packageName of donutPackageNames) {
|
|
1052
|
-
const packageJsonPath =
|
|
1053
|
-
if (await
|
|
1054
|
-
const parsed = await
|
|
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 =
|
|
1071
|
-
const outputDirectory =
|
|
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 =
|
|
1092
|
-
await
|
|
1093
|
-
await
|
|
1094
|
-
await
|
|
1095
|
-
const donutRoot =
|
|
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 =
|
|
1105
|
-
if (!await
|
|
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 =
|
|
1109
|
-
const stagingAssetsDirectory =
|
|
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
|
|
968
|
+
const gameJsContents = await fileSystemExtra5.readFile(builtGameJsPath);
|
|
1114
969
|
const bundleFileName = `${donutManifest.name}-${donutManifest.version ?? "0.0.0"}.donut`;
|
|
1115
|
-
const bundlePath =
|
|
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 =
|
|
1131
|
-
const assetBuffer = await
|
|
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
|
|
1157
|
-
const bundleStat = await
|
|
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 =
|
|
1208
|
-
if (!await
|
|
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
|
|
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";
|