@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/donut.js
CHANGED
|
@@ -263,9 +263,9 @@ function spawnInstall(packageManager, workingDirectory) {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
// ../cli/dist/commands/dev.js
|
|
266
|
-
import
|
|
267
|
-
import
|
|
268
|
-
import
|
|
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/
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
|
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
|
|
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 =
|
|
535
|
-
if (!await
|
|
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
|
|
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 =
|
|
597
|
-
if (!await
|
|
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 =
|
|
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 =
|
|
628
|
-
if (
|
|
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 =
|
|
635
|
-
const fallback =
|
|
636
|
-
if (
|
|
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
|
-
|
|
672
|
-
|
|
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
|
|
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
|
|
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 =
|
|
712
|
-
if (!await
|
|
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
|
|
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
|
|
599
|
+
const entries = await fileSystemExtra4.readdir(directoryPath, { withFileTypes: true });
|
|
741
600
|
for (const entry of entries) {
|
|
742
|
-
const entryPath =
|
|
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 =
|
|
753
|
-
const assetsRoot =
|
|
754
|
-
if (!await
|
|
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
|
|
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 =
|
|
787
|
-
if (!await
|
|
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
|
|
681
|
+
if (!await fileSystemExtra5.pathExists(directoryPath)) {
|
|
827
682
|
return [];
|
|
828
683
|
}
|
|
829
684
|
const collected = [];
|
|
830
|
-
const entries = await
|
|
685
|
+
const entries = await fileSystemExtra5.readdir(directoryPath, { withFileTypes: true });
|
|
831
686
|
for (const entry of entries) {
|
|
832
|
-
const entryPath =
|
|
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
|
-
|
|
844
|
-
|
|
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
|
|
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 =
|
|
874
|
-
if (!await
|
|
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
|
|
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(
|
|
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 =
|
|
961
|
-
await
|
|
962
|
-
const gitIgnorePath =
|
|
963
|
-
if (!await
|
|
964
|
-
await
|
|
965
|
-
}
|
|
966
|
-
const entryPath =
|
|
967
|
-
await
|
|
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
|
|
854
|
+
if (!await fileSystemExtra5.pathExists(projectAssetsDirectory)) {
|
|
1000
855
|
return [];
|
|
1001
856
|
}
|
|
1002
|
-
await
|
|
857
|
+
await fileSystemExtra5.ensureDir(stagingAssetsDirectory);
|
|
1003
858
|
const entries = [];
|
|
1004
859
|
async function walk(currentSource, currentDestination, relativePrefix) {
|
|
1005
|
-
const directoryEntries = await
|
|
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 =
|
|
1011
|
-
const destinationPath =
|
|
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
|
|
869
|
+
await fileSystemExtra5.ensureDir(destinationPath);
|
|
1015
870
|
await walk(sourcePath, destinationPath, relativePath);
|
|
1016
871
|
} else if (directoryEntry.isFile()) {
|
|
1017
|
-
const extensionWithDot =
|
|
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
|
|
879
|
+
await fileSystemExtra5.writeFile(destinationPath, optimizedBuffer);
|
|
1025
880
|
wroteOptimizedOutput = true;
|
|
1026
881
|
} else if (!sharpUnavailableWarningEmitted) {
|
|
1027
|
-
log(
|
|
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
|
|
887
|
+
await fileSystemExtra5.copy(sourcePath, destinationPath);
|
|
1033
888
|
}
|
|
1034
|
-
const stat = await
|
|
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 =
|
|
907
|
+
const packagesDirectory = path5.join(monorepoRoot, "packages");
|
|
1053
908
|
for (const packageName of donutPackageNames) {
|
|
1054
|
-
const packageJsonPath =
|
|
1055
|
-
if (await
|
|
1056
|
-
const parsed = await
|
|
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 =
|
|
1073
|
-
const outputDirectory =
|
|
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 =
|
|
1094
|
-
await
|
|
1095
|
-
await
|
|
1096
|
-
await
|
|
1097
|
-
const donutRoot =
|
|
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 =
|
|
1107
|
-
if (!await
|
|
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 =
|
|
1111
|
-
const stagingAssetsDirectory =
|
|
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
|
|
970
|
+
const gameJsContents = await fileSystemExtra5.readFile(builtGameJsPath);
|
|
1116
971
|
const bundleFileName = `${donutManifest.name}-${donutManifest.version ?? "0.0.0"}.donut`;
|
|
1117
|
-
const bundlePath =
|
|
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 =
|
|
1133
|
-
const assetBuffer = await
|
|
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
|
|
1159
|
-
const bundleStat = await
|
|
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 =
|
|
1210
|
-
if (!await
|
|
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
|
|
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";
|