@moku-labs/game 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/app/page.d.mts +1 -1
- package/dist/app/system.d.mts +2 -2
- package/dist/app.d.mts +1 -1
- package/dist/app.mjs +1 -1
- package/dist/{assets-BHwTLVta.mjs → assets-DYiKxPBX.mjs} +27 -63
- package/dist/assets.mjs +1 -1
- package/dist/cli.d.mts +3 -2
- package/dist/cli.mjs +339 -50
- package/dist/{commands-C4QlqijJ.d.mts → commands-DvFr8Uwp.d.mts} +2 -2
- package/dist/control.d.mts +1 -1
- package/dist/{index-8TiJQjQA.d.mts → index-rnOYAriI.d.mts} +44 -4
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +2 -2
- package/dist/inspect.d.mts +3 -3
- package/dist/jsx-dev-runtime.d.mts +1 -1
- package/dist/jsx-runtime.d.mts +1 -1
- package/dist/lint.mjs +3 -1
- package/dist/project.d.mts +1 -1
- package/dist/project.mjs +2 -2
- package/dist/{runtime-DOBYM6g4.d.mts → runtime-B276Gg0s.d.mts} +1 -1
- package/dist/{src-BSbtHZWk.mjs → src-SmT-XxVf.mjs} +56 -6
- package/dist/testing.d.mts +1 -1
- package/dist/testing.mjs +1 -1
- package/dist/{types-CefgYzFt.d.mts → types-Bcdo5-2w.d.mts} +1 -1
- package/dist/{types-BUwDHKXm.d.mts → types-BrTpz9oL.d.mts} +1 -1
- package/dist/{types-Ca_z_4tQ.d.mts → types-DLghaGEy.d.mts} +2 -2
- package/dist/{types-BM4shh8K.d.mts → types-D_2G6Q68.d.mts} +1 -1
- package/dist/visual.d.mts +9 -6
- package/dist/visual.mjs +7 -3
- package/llms.txt +8 -7
- package/package.json +3 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as resolveConfig } from "./config-CdARrrhU.mjs";
|
|
2
|
-
import { a as runCli$1, i as compileStrings, n as importStrings, t as exportStrings } from "./assets-
|
|
2
|
+
import { a as runCli$1, i as compileStrings, n as importStrings, t as exportStrings } from "./assets-DYiKxPBX.mjs";
|
|
3
3
|
import { constants, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createBrandConsole } from "@moku-labs/common/cli";
|
|
@@ -230,13 +230,15 @@ function importLine(binding, specifier) {
|
|
|
230
230
|
*
|
|
231
231
|
* @param settings - The resolved `config.ts`.
|
|
232
232
|
* @param sources - The scenario files, the `.dev` modules and the agents.
|
|
233
|
+
* @param toRoot - The path from `main.ts` to the game folder: `"../"` for `.moku/`, `"../../"`
|
|
234
|
+
* for the page of `moku-game visual` in `.moku/visual/`.
|
|
233
235
|
* @returns The text of `.moku/main.ts`.
|
|
234
236
|
* @example
|
|
235
237
|
* ```ts
|
|
236
238
|
* devMain(resolveConfig({ page: { title: "T" } }), { scenarios: ["ready.ts"], devModules: [] }).includes('scenarios: { "ready": scenario0 }'); // true
|
|
237
239
|
* ```
|
|
238
240
|
*/
|
|
239
|
-
function devMain(settings, sources) {
|
|
241
|
+
function devMain(settings, sources, toRoot = "../") {
|
|
240
242
|
const scenarios = scenarioFiles(sources.scenarios);
|
|
241
243
|
const agents = sources.agents ?? [];
|
|
242
244
|
const devModules = agents.length === 0 ? [] : sources.devModules.toSorted();
|
|
@@ -250,9 +252,9 @@ function devMain(settings, sources) {
|
|
|
250
252
|
return [
|
|
251
253
|
WRITTEN,
|
|
252
254
|
"import \"./dev.ts\";",
|
|
253
|
-
...pageImports(settings,
|
|
254
|
-
...scenarios.map((file, index) => importLine(`scenario${index}`,
|
|
255
|
-
...devModules.map((file, index) => importLine(`* as devModule${index}`,
|
|
255
|
+
...pageImports(settings, toRoot),
|
|
256
|
+
...scenarios.map((file, index) => importLine(`scenario${index}`, `${toRoot}tests/scenarios/${file}`)),
|
|
257
|
+
...devModules.map((file, index) => importLine(`* as devModule${index}`, `${toRoot}${file}`)),
|
|
256
258
|
...agents.map((agent, index) => importLine(`agent${index}`, agent)),
|
|
257
259
|
"",
|
|
258
260
|
"await startPage(game, config, {",
|
|
@@ -353,6 +355,11 @@ const FORWARDED = [
|
|
|
353
355
|
const STOPPING = ["SIGINT", "SIGTERM"];
|
|
354
356
|
/** How often the dev child checks that its parent still runs. */
|
|
355
357
|
const PARENT_CHECK_MS = 1e3;
|
|
358
|
+
/**
|
|
359
|
+
* The dev manifest `moku-game keys` writes, relative to the game folder, next to
|
|
360
|
+
* `generated/assets.ts`. The dev server answers `/manifest.json` with it.
|
|
361
|
+
*/
|
|
362
|
+
const DEV_MANIFEST = "generated/manifest.json";
|
|
356
363
|
/** The folders a `.dev.ts` module is never looked for in. */
|
|
357
364
|
const NO_DEV_MODULES = /^(?:node_modules|dist|generated|tests|__tests__)$/;
|
|
358
365
|
/** A `.gitignore` line that keeps `.moku/` of its own folder out. */
|
|
@@ -514,47 +521,52 @@ function writeIfChanged(file, text) {
|
|
|
514
521
|
* @param root - The game folder.
|
|
515
522
|
* @param settings - The resolved config.
|
|
516
523
|
* @param agents - The agent modules, when the page has any.
|
|
524
|
+
* @param toRoot - The path from `main.ts` to the game folder.
|
|
517
525
|
* @returns The text.
|
|
518
526
|
*/
|
|
519
|
-
function mainOf(root, settings, agents) {
|
|
527
|
+
function mainOf(root, settings, agents, toRoot = "../") {
|
|
520
528
|
const scenarios = scenarioNamesOf(root);
|
|
521
529
|
return agents === void 0 || agents.length === 0 ? devMain(settings, {
|
|
522
530
|
scenarios,
|
|
523
531
|
devModules: []
|
|
524
|
-
}) : devMain(settings, {
|
|
532
|
+
}, toRoot) : devMain(settings, {
|
|
525
533
|
scenarios,
|
|
526
534
|
devModules: devModulesOf(root),
|
|
527
535
|
agents
|
|
528
|
-
});
|
|
536
|
+
}, toRoot);
|
|
529
537
|
}
|
|
530
538
|
/**
|
|
531
539
|
* Writes the dev page of a checked game into `<game>/.moku/`: `index.html`, `dev.ts`, `main.ts`
|
|
532
|
-
* and `bunfig.toml`.
|
|
533
|
-
*
|
|
540
|
+
* and `bunfig.toml`. `moku-game visual` writes its own page into `.moku/visual/`, so the page of
|
|
541
|
+
* `dev` or of the editor stays as it is; the links of the page reach the game from its depth.
|
|
542
|
+
* Every text is made before the first write, so a refused value writes nothing.
|
|
534
543
|
*
|
|
535
544
|
* @param root - The game folder, absolute.
|
|
536
545
|
* @param settings - The resolved config.
|
|
537
546
|
* @param options - The agents, the preloads and the extra plugins, absolute.
|
|
538
547
|
* @param resolve - Bun's resolver, to find the hot plugin.
|
|
548
|
+
* @param page - The folder of the page under the game, `/` separated: `.moku` by default.
|
|
539
549
|
* @returns The paths of the HTML and the bunfig.
|
|
540
550
|
* @throws {Error} When the hot plugin does not resolve, a preload or plugin is not a file, or a
|
|
541
551
|
* page value is refused.
|
|
542
552
|
*/
|
|
543
|
-
function writePage(root, settings, options, resolve) {
|
|
553
|
+
function writePage(root, settings, options, resolve, page = ".moku") {
|
|
544
554
|
const preload = options.preload ?? [];
|
|
545
555
|
const servePlugins = options.servePlugins ?? [];
|
|
546
556
|
checkFiles("--preload", preload);
|
|
547
557
|
checkFiles("--serve-plugin", servePlugins);
|
|
548
|
-
const
|
|
558
|
+
const segments = page.split("/");
|
|
559
|
+
const toRoot = "../".repeat(segments.length);
|
|
560
|
+
const folder = path.join(root, ...segments);
|
|
549
561
|
const html = path.join(folder, "index.html");
|
|
550
562
|
const bunfig = path.join(folder, "bunfig.toml");
|
|
551
563
|
const texts = [
|
|
552
564
|
[html, pageHtml(settings.page, {
|
|
553
|
-
toRoot
|
|
565
|
+
toRoot,
|
|
554
566
|
script: "./main.ts"
|
|
555
567
|
})],
|
|
556
568
|
[path.join(folder, "dev.ts"), devFlag()],
|
|
557
|
-
[path.join(folder, "main.ts"), mainOf(root, settings, options.agents)],
|
|
569
|
+
[path.join(folder, "main.ts"), mainOf(root, settings, options.agents, toRoot)],
|
|
558
570
|
[bunfig, bunfigText({
|
|
559
571
|
hotPlugin: hotPluginOf(root, resolve),
|
|
560
572
|
servePlugins,
|
|
@@ -656,13 +668,30 @@ function devChildCommand(run, page, deps) {
|
|
|
656
668
|
async function serveParent(run, deps) {
|
|
657
669
|
const settings = await loadGame(run.root);
|
|
658
670
|
const hasPack = isFile(path.join(run.root, "dist", "assets", "manifest.json"));
|
|
659
|
-
const hasManifest = isFile(path.join(run.root,
|
|
671
|
+
const hasManifest = isFile(path.join(run.root, DEV_MANIFEST));
|
|
660
672
|
if (run.packed && !hasPack) throw new Error(`[game] dev: no packed build in "${path.join(run.root, "dist", "assets")}".\n Run "moku-game pack" first.`);
|
|
661
|
-
if (!run.packed && !hasManifest) deps.ui.warn(`[game] dev: no
|
|
673
|
+
if (!run.packed && !hasManifest) deps.ui.warn(`[game] dev: no ${DEV_MANIFEST} in "${run.root}".\n Run "moku-game keys" first.`);
|
|
662
674
|
const page = writePage(run.root, settings, run, deps.resolve);
|
|
663
675
|
if (!ignoresMoku(run.root)) deps.ui.warn("[game] dev: add \".moku/\" to .gitignore. moku-game writes its dev page there.");
|
|
664
|
-
|
|
676
|
+
return runChild(devChildCommand(run, page, deps), deps, {
|
|
665
677
|
cwd: run.root,
|
|
678
|
+
whileRunning: () => watchScenarios(run.root, settings, deps)
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Runs the bin again as a marked child, `MOKU_GAME_CHILD=1`, in its own process group: Ctrl+C
|
|
683
|
+
* reaches the parent, which hands it and the other stop signals on. `dev` and `visual` run their
|
|
684
|
+
* page server this way, under the page's bunfig.
|
|
685
|
+
*
|
|
686
|
+
* @param command - The words of the command: the Bun, its flags, the bin and its arguments.
|
|
687
|
+
* @param deps - The spawn, the signals and the environment.
|
|
688
|
+
* @param child - The working directory, and what lives as long as the child.
|
|
689
|
+
* @returns The exit code of the child.
|
|
690
|
+
* @throws {Error} When the child does not start.
|
|
691
|
+
*/
|
|
692
|
+
async function runChild(command, deps, child) {
|
|
693
|
+
const started = deps.spawn(command, {
|
|
694
|
+
cwd: child.cwd,
|
|
666
695
|
env: {
|
|
667
696
|
...deps.env,
|
|
668
697
|
MOKU_GAME_CHILD: "1"
|
|
@@ -674,11 +703,11 @@ async function serveParent(run, deps) {
|
|
|
674
703
|
],
|
|
675
704
|
detached: true
|
|
676
705
|
});
|
|
677
|
-
const removers = FORWARDED.map((signal) => deps.onSignal(signal, () =>
|
|
706
|
+
const removers = FORWARDED.map((signal) => deps.onSignal(signal, () => started.kill(signal)));
|
|
678
707
|
let watcher;
|
|
679
708
|
try {
|
|
680
|
-
watcher =
|
|
681
|
-
return await
|
|
709
|
+
watcher = child.whileRunning?.();
|
|
710
|
+
return await started.exited;
|
|
682
711
|
} finally {
|
|
683
712
|
watcher?.close();
|
|
684
713
|
for (const remove of removers) remove();
|
|
@@ -731,15 +760,19 @@ function fileResponse(file) {
|
|
|
731
760
|
}
|
|
732
761
|
/**
|
|
733
762
|
* Starts the dev server on `127.0.0.1` only, never on every interface: the page on `/`, the
|
|
734
|
-
* manifest
|
|
763
|
+
* manifest on `/manifest.json`, and the files of the served folder as static files. The manifest
|
|
764
|
+
* is the dev manifest `generated/manifest.json` of the game, or `manifest.json` of `dist/assets`
|
|
765
|
+
* with `--packed`; its paths are relative to the served folder. The dev child and
|
|
766
|
+
* `moku-game visual` serve the page with it.
|
|
735
767
|
*
|
|
736
768
|
* @param run - The flags of the run.
|
|
737
|
-
* @param page - The page bundle the
|
|
769
|
+
* @param page - The page bundle the process imported.
|
|
738
770
|
* @returns The server.
|
|
739
771
|
* @throws {Error} When the port is in use.
|
|
740
772
|
*/
|
|
741
773
|
function serveOn(run, page) {
|
|
742
774
|
const base = run.packed ? path.join(run.root, "dist", "assets") : run.root;
|
|
775
|
+
const manifest = path.join(base, run.packed ? "manifest.json" : DEV_MANIFEST);
|
|
743
776
|
try {
|
|
744
777
|
return Bun.serve({
|
|
745
778
|
hostname: "127.0.0.1",
|
|
@@ -747,7 +780,7 @@ function serveOn(run, page) {
|
|
|
747
780
|
development: true,
|
|
748
781
|
routes: {
|
|
749
782
|
"/": page,
|
|
750
|
-
"/manifest.json": () => fileResponse(
|
|
783
|
+
"/manifest.json": () => fileResponse(manifest)
|
|
751
784
|
},
|
|
752
785
|
fetch: (request) => fileResponse(staticPath(base, new URL(request.url).pathname))
|
|
753
786
|
});
|
|
@@ -757,6 +790,22 @@ function serveOn(run, page) {
|
|
|
757
790
|
}
|
|
758
791
|
}
|
|
759
792
|
/**
|
|
793
|
+
* Watches the parent of a child run: a parent that died hands the child to another process, so
|
|
794
|
+
* its parent id changes. The dev child and the visual child stop their server then.
|
|
795
|
+
*
|
|
796
|
+
* @param onGone - Called once when the parent is gone.
|
|
797
|
+
* @returns Stops watching.
|
|
798
|
+
*/
|
|
799
|
+
function watchParent(onGone) {
|
|
800
|
+
const parent = process.ppid;
|
|
801
|
+
const timer = setInterval(() => {
|
|
802
|
+
if (process.ppid === parent) return;
|
|
803
|
+
clearInterval(timer);
|
|
804
|
+
onGone();
|
|
805
|
+
}, PARENT_CHECK_MS);
|
|
806
|
+
return () => clearInterval(timer);
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
760
809
|
* Waits until the dev child should stop: Ctrl+C, a stop signal, or a parent that died (its
|
|
761
810
|
* parent id changed). Then stops the server.
|
|
762
811
|
*
|
|
@@ -765,17 +814,14 @@ function serveOn(run, page) {
|
|
|
765
814
|
* @returns Resolves to the exit code `0` once the server stopped.
|
|
766
815
|
*/
|
|
767
816
|
function untilStopped(server, deps) {
|
|
768
|
-
const parent = process.ppid;
|
|
769
817
|
return new Promise((resolve) => {
|
|
770
818
|
const removers = [];
|
|
771
|
-
const timer = setInterval(() => {
|
|
772
|
-
if (process.ppid !== parent) stop();
|
|
773
|
-
}, PARENT_CHECK_MS);
|
|
774
819
|
const stop = () => {
|
|
775
|
-
|
|
820
|
+
unwatch();
|
|
776
821
|
for (const remove of removers.splice(0)) remove();
|
|
777
822
|
resolve(server.stop(true).then(() => 0));
|
|
778
823
|
};
|
|
824
|
+
const unwatch = watchParent(stop);
|
|
779
825
|
for (const signal of STOPPING) removers.push(deps.onSignal(signal, stop));
|
|
780
826
|
});
|
|
781
827
|
}
|
|
@@ -853,7 +899,7 @@ function hasEnglishStrings(root, settings) {
|
|
|
853
899
|
return [...featureFolders, ...layerFolders].some((folder) => statSync(path.join(folder, "strings", "en.json"), { throwIfNoEntry: false })?.isFile() === true);
|
|
854
900
|
}
|
|
855
901
|
/**
|
|
856
|
-
* The flags of the asset scan for `moku-game keys`: the dev manifest
|
|
902
|
+
* The flags of the asset scan for `moku-game keys`: the dev manifest in `generated/`, the
|
|
857
903
|
* pseudo-locale when the game has an English string file, and `--check` for a check run.
|
|
858
904
|
*
|
|
859
905
|
* @param root - The game folder.
|
|
@@ -862,7 +908,7 @@ function hasEnglishStrings(root, settings) {
|
|
|
862
908
|
* @returns The flags for the scanner's command line.
|
|
863
909
|
* @example
|
|
864
910
|
* ```ts
|
|
865
|
-
* keysArguments("/g", resolveConfig({ page: { title: "T" } }), true).slice(4); // ["--manifest", "/g/manifest.json", "--check"]: no strings
|
|
911
|
+
* keysArguments("/g", resolveConfig({ page: { title: "T" } }), true).slice(4); // ["--manifest", "/g/generated/manifest.json", "--check"]: no strings
|
|
866
912
|
* keysArguments("/g", resolveConfig({ page: { title: "T" } }), false).at(-1); // "--pseudo" once /g/features/home/strings/en.json exists
|
|
867
913
|
* ```
|
|
868
914
|
*/
|
|
@@ -870,7 +916,7 @@ function keysArguments(root, settings, check) {
|
|
|
870
916
|
return [
|
|
871
917
|
...scanArguments(root, settings),
|
|
872
918
|
"--manifest",
|
|
873
|
-
path.join(root,
|
|
919
|
+
path.join(root, DEV_MANIFEST),
|
|
874
920
|
...hasEnglishStrings(root, settings) ? ["--pseudo"] : [],
|
|
875
921
|
...check ? ["--check"] : []
|
|
876
922
|
];
|
|
@@ -1086,13 +1132,188 @@ async function runBuild(run, deps) {
|
|
|
1086
1132
|
return 0;
|
|
1087
1133
|
}
|
|
1088
1134
|
//#endregion
|
|
1135
|
+
//#region src/app/visual.ts
|
|
1136
|
+
/**
|
|
1137
|
+
* @file `moku-game visual`: the visual tests of a game from the bin. It imports the tests module,
|
|
1138
|
+
* `tests/visual/index.ts` by default, whose default export is `{ app, tests }`, the two arguments
|
|
1139
|
+
* of `runVisualTests`: `app` is a `VisualSetup`. The flags of the runner are read by
|
|
1140
|
+
* `parseVisualArgv`. When the pixel leg runs without `--url`, the command writes its own dev page
|
|
1141
|
+
* into `.moku/visual/`, so the page of `dev` or of the editor stays as it is, runs the bin again
|
|
1142
|
+
* under that page's bunfig as `dev` does, and the child serves the page with the server of `dev`
|
|
1143
|
+
* on a free port and stops it after. Node and Bun only: the bin bundles it.
|
|
1144
|
+
*/
|
|
1145
|
+
/** The baselines of a game, under the game folder. */
|
|
1146
|
+
const BASELINES = path.join("tests", "visual", "baselines");
|
|
1147
|
+
/** The folder of the visual page under the game: its own, beside the page of `dev`. */
|
|
1148
|
+
const VISUAL_PAGE = ".moku/visual";
|
|
1149
|
+
/**
|
|
1150
|
+
* Tells whether a value is a setup of the runner: an object with an `app` function.
|
|
1151
|
+
*
|
|
1152
|
+
* @param value - The `app` of the module.
|
|
1153
|
+
* @returns True for a `VisualSetup`.
|
|
1154
|
+
*/
|
|
1155
|
+
function isSetup(value) {
|
|
1156
|
+
return typeof value === "object" && value !== null && "app" in value && typeof value.app === "function";
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Reads the default export of a tests module: `app` is the setup of the runner, `{ app, page? }`,
|
|
1160
|
+
* and `tests` is a list. Each test is checked by the runner.
|
|
1161
|
+
*
|
|
1162
|
+
* @param value - The default export.
|
|
1163
|
+
* @returns The suite, or `undefined` for another shape.
|
|
1164
|
+
*/
|
|
1165
|
+
function suiteOf(value) {
|
|
1166
|
+
if (typeof value !== "object" || value === null || !("app" in value) || !("tests" in value)) return;
|
|
1167
|
+
const { app, tests } = value;
|
|
1168
|
+
return Array.isArray(tests) && isSetup(app) ? {
|
|
1169
|
+
setup: app,
|
|
1170
|
+
tests
|
|
1171
|
+
} : void 0;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Imports the tests module of the run.
|
|
1175
|
+
*
|
|
1176
|
+
* @param file - The module, absolute.
|
|
1177
|
+
* @param cwd - The working directory, to name the module in the message.
|
|
1178
|
+
* @returns The setup and the tests.
|
|
1179
|
+
* @throws {Error} When the module is missing or its default export is not `{ app, tests }`.
|
|
1180
|
+
*/
|
|
1181
|
+
async function loadSuite(file, cwd) {
|
|
1182
|
+
const suite = suiteOf((statSync(file, { throwIfNoEntry: false })?.isFile() === true ? await import(pathToFileURL(file).href) : void 0)?.default);
|
|
1183
|
+
if (suite === void 0) throw new Error(`[game] visual: ${path.relative(cwd, file)} must export default { app, tests }.\n app is a VisualSetup { app, page? }: export default { app: { app: () => game.screen().app }, tests }.`);
|
|
1184
|
+
return suite;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Writes the visual page of the game into `.moku/visual/`: `index.html`, `dev.ts`, `main.ts` and
|
|
1188
|
+
* `bunfig.toml`, the hot plugin, the serve plugins and the preloads in it, as `dev` writes its page.
|
|
1189
|
+
*
|
|
1190
|
+
* @param page - The game folder, the preloads and the serve plugins.
|
|
1191
|
+
* @param resolve - Bun's resolver, to find the hot plugin.
|
|
1192
|
+
* @returns The paths of the HTML and the bunfig.
|
|
1193
|
+
* @throws {Error} The errors of `moku-game dev` before its server starts, and a game without
|
|
1194
|
+
* `generated/manifest.json`.
|
|
1195
|
+
*/
|
|
1196
|
+
async function writeVisualPage(page, resolve) {
|
|
1197
|
+
const settings = await loadGame(page.root);
|
|
1198
|
+
const manifest = path.join(page.root, DEV_MANIFEST);
|
|
1199
|
+
if (statSync(manifest, { throwIfNoEntry: false })?.isFile() !== true) throw new Error(`[game] visual: no ${DEV_MANIFEST} in "${page.root}".\n Run "moku-game keys" first.`);
|
|
1200
|
+
return writePage(page.root, settings, {
|
|
1201
|
+
preload: page.preload,
|
|
1202
|
+
servePlugins: page.servePlugins
|
|
1203
|
+
}, resolve, VISUAL_PAGE);
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Writes the visual page and serves it in this process on a free port, as the dev child serves
|
|
1207
|
+
* its page. It runs in the child that runs under the page's bunfig, so the hot plugin, the serve
|
|
1208
|
+
* plugins and the preloads apply.
|
|
1209
|
+
*
|
|
1210
|
+
* @param page - The game folder, the preloads and the serve plugins.
|
|
1211
|
+
* @param deps - Bun's resolver and the page loader.
|
|
1212
|
+
* @returns The running server.
|
|
1213
|
+
* @throws {Error} The errors of `writeVisualPage`.
|
|
1214
|
+
*/
|
|
1215
|
+
async function serveVisualPage(page, deps) {
|
|
1216
|
+
const files = await writeVisualPage(page, deps.resolve);
|
|
1217
|
+
const loaded = await deps.loadPage(files.html);
|
|
1218
|
+
return serveOn({
|
|
1219
|
+
root: page.root,
|
|
1220
|
+
port: 0,
|
|
1221
|
+
packed: false,
|
|
1222
|
+
preload: page.preload,
|
|
1223
|
+
servePlugins: page.servePlugins
|
|
1224
|
+
}, loaded.default);
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Runs the bin again for a run that has to: under the visual page's bunfig when the page is
|
|
1228
|
+
* served, so it is bundled as `dev` bundles its page; with Bun's preloads otherwise.
|
|
1229
|
+
*
|
|
1230
|
+
* @param command - The run.
|
|
1231
|
+
* @param serves - True when the page is served for the run.
|
|
1232
|
+
* @param deps - The process seams.
|
|
1233
|
+
* @returns The exit code of the child.
|
|
1234
|
+
* @throws {Error} The errors of `writeVisualPage`.
|
|
1235
|
+
*/
|
|
1236
|
+
async function rerun(command, serves, deps) {
|
|
1237
|
+
const page = serves ? await writeVisualPage(command, deps.resolve) : void 0;
|
|
1238
|
+
const bun = page === void 0 ? command.preload.map((file) => `--preload=${file}`) : [`--config=${page.bunfig}`];
|
|
1239
|
+
return runChild([
|
|
1240
|
+
deps.execPath,
|
|
1241
|
+
...bun,
|
|
1242
|
+
...deps.self,
|
|
1243
|
+
...command.argv
|
|
1244
|
+
], deps, { cwd: deps.cwd });
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Plays the tests in this process: the headless leg, then the pixel leg against `--url` or the
|
|
1248
|
+
* page served here, which stops when the run ends or the parent of the run is gone.
|
|
1249
|
+
*
|
|
1250
|
+
* @param command - The run.
|
|
1251
|
+
* @param run - The runner, its flags, and whether the pixel leg runs.
|
|
1252
|
+
* @param deps - The process seams.
|
|
1253
|
+
* @returns `0` when the report is ok, `1` otherwise.
|
|
1254
|
+
* @throws {Error} When the tests module is refused, the page cannot be served, or the runner
|
|
1255
|
+
* throws.
|
|
1256
|
+
*/
|
|
1257
|
+
async function play(command, run, deps) {
|
|
1258
|
+
const suite = await loadSuite(command.tests, deps.cwd);
|
|
1259
|
+
const dir = path.resolve(deps.cwd, run.flags.dir ?? path.join(command.root, BASELINES));
|
|
1260
|
+
const server = run.serves ? await serveVisualPage(command, deps) : void 0;
|
|
1261
|
+
const unwatch = server === void 0 ? void 0 : watchParent(() => server.stop(true));
|
|
1262
|
+
const url = command.url ?? server?.url.href;
|
|
1263
|
+
try {
|
|
1264
|
+
const setup = url === void 0 ? suite.setup : {
|
|
1265
|
+
...suite.setup,
|
|
1266
|
+
page: {
|
|
1267
|
+
...suite.setup.page,
|
|
1268
|
+
url
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
return (await run.visual.runVisualTests(setup, suite.tests, {
|
|
1272
|
+
...run.flags,
|
|
1273
|
+
pixels: run.pixels,
|
|
1274
|
+
dir,
|
|
1275
|
+
argv: []
|
|
1276
|
+
})).ok ? 0 : 1;
|
|
1277
|
+
} finally {
|
|
1278
|
+
unwatch?.();
|
|
1279
|
+
await server?.stop(true);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Runs `moku-game visual`: the headless leg, then the pixel leg on a Mac (or with `--pixels`)
|
|
1284
|
+
* against `--url` or the page served for the run. A run that serves the page runs the bin again
|
|
1285
|
+
* under `.moku/visual/bunfig.toml`, as `dev` does, and a run with `--preload` with the preloads;
|
|
1286
|
+
* the marked child plays the tests. The baselines live in `<game>/tests/visual/baselines/` unless
|
|
1287
|
+
* `--dir` names another folder.
|
|
1288
|
+
*
|
|
1289
|
+
* @param command - The run.
|
|
1290
|
+
* @param deps - The process seams.
|
|
1291
|
+
* @returns `0` when the report is ok, `1` otherwise, or the exit code of the child.
|
|
1292
|
+
* @throws {Error} When the tests module is refused, a flag misses its value, the page cannot be
|
|
1293
|
+
* served, or the runner throws.
|
|
1294
|
+
*/
|
|
1295
|
+
async function runVisual(command, deps) {
|
|
1296
|
+
const visual = await deps.visual();
|
|
1297
|
+
const flags = visual.parseVisualArgv(command.flags);
|
|
1298
|
+
const pixels = flags.pixels ?? process.platform === "darwin";
|
|
1299
|
+
const serves = pixels && command.url === void 0;
|
|
1300
|
+
if (!(deps.env.MOKU_GAME_CHILD === "1") && (serves || command.preload.length > 0)) return rerun(command, serves, deps);
|
|
1301
|
+
return play(command, {
|
|
1302
|
+
visual,
|
|
1303
|
+
flags,
|
|
1304
|
+
pixels,
|
|
1305
|
+
serves
|
|
1306
|
+
}, deps);
|
|
1307
|
+
}
|
|
1308
|
+
//#endregion
|
|
1089
1309
|
//#region src/app/cli.ts
|
|
1090
1310
|
/**
|
|
1091
1311
|
* @file The command line of `moku-game`: the arguments read per command with `node:util`
|
|
1092
|
-
* `parseArgs`, then one of `dev`, `build`, `native <verb>`, `keys`, `pack` or `help`.
|
|
1093
|
-
* of the process (the console, the environment, spawning, signals, the watcher, the
|
|
1094
|
-
* scanner, native, Bun's resolver) comes in as `deps`, so a test
|
|
1095
|
-
* returns the exit code and never exits itself. A plain function,
|
|
1312
|
+
* `parseArgs`, then one of `dev`, `build`, `native <verb>`, `keys`, `pack`, `visual` or `help`.
|
|
1313
|
+
* Every seam of the process (the console, the environment, spawning, signals, the watcher, the
|
|
1314
|
+
* asset scanner, native, Bun's resolver, the visual test runner) comes in as `deps`, so a test
|
|
1315
|
+
* runs a command with stubs. It returns the exit code and never exits itself. A plain function,
|
|
1316
|
+
* on no API.
|
|
1096
1317
|
*/
|
|
1097
1318
|
/** The flags every command takes. */
|
|
1098
1319
|
const GLOBAL_OPTIONS = [
|
|
@@ -1121,15 +1342,38 @@ const ALL_OPTIONS = {
|
|
|
1121
1342
|
out: { type: "string" },
|
|
1122
1343
|
simulator: { type: "boolean" },
|
|
1123
1344
|
check: { type: "boolean" },
|
|
1124
|
-
"no-cache": { type: "boolean" }
|
|
1345
|
+
"no-cache": { type: "boolean" },
|
|
1346
|
+
tests: { type: "string" },
|
|
1347
|
+
url: { type: "string" },
|
|
1348
|
+
update: { type: "boolean" },
|
|
1349
|
+
only: {
|
|
1350
|
+
type: "string",
|
|
1351
|
+
multiple: true
|
|
1352
|
+
},
|
|
1353
|
+
"no-pixels": { type: "boolean" },
|
|
1354
|
+
pixels: { type: "boolean" },
|
|
1355
|
+
dir: { type: "string" },
|
|
1356
|
+
webgl: { type: "boolean" }
|
|
1125
1357
|
};
|
|
1358
|
+
/** The flags of `visual` that `parseVisualArgv` reads, in the runner's words. */
|
|
1359
|
+
const VISUAL_FLAGS = /^(?:update|only|no-pixels|pixels|dir|webgl)$/;
|
|
1126
1360
|
/** The flags of one command besides the global ones. */
|
|
1127
1361
|
const COMMAND_OPTIONS = {
|
|
1128
1362
|
dev: ["port", "packed"],
|
|
1129
1363
|
build: ["out"],
|
|
1130
1364
|
native: ["simulator"],
|
|
1131
1365
|
keys: ["check"],
|
|
1132
|
-
pack: ["no-cache"]
|
|
1366
|
+
pack: ["no-cache"],
|
|
1367
|
+
visual: [
|
|
1368
|
+
"tests",
|
|
1369
|
+
"url",
|
|
1370
|
+
"update",
|
|
1371
|
+
"only",
|
|
1372
|
+
"no-pixels",
|
|
1373
|
+
"pixels",
|
|
1374
|
+
"dir",
|
|
1375
|
+
"webgl"
|
|
1376
|
+
]
|
|
1133
1377
|
};
|
|
1134
1378
|
/** The native verbs, in the order the messages name them. */
|
|
1135
1379
|
const NATIVE_VERBS = [
|
|
@@ -1138,6 +1382,10 @@ const NATIVE_VERBS = [
|
|
|
1138
1382
|
"doctor",
|
|
1139
1383
|
"clean"
|
|
1140
1384
|
];
|
|
1385
|
+
/** The command words, in the order the messages name them. */
|
|
1386
|
+
const COMMAND_NAMES = "dev, build, native, keys, pack, visual, help";
|
|
1387
|
+
/** The tests module of a game, under the game folder. */
|
|
1388
|
+
const VISUAL_TESTS = path.join("tests", "visual", "index.ts");
|
|
1141
1389
|
/** The port of `moku-game dev` without `--port`. */
|
|
1142
1390
|
const DEFAULT_PORT = "3000";
|
|
1143
1391
|
/** The highest port number. */
|
|
@@ -1151,8 +1399,11 @@ const USAGE = [
|
|
|
1151
1399
|
" native build <target> [--simulator] build the native app (ios, macos, android)",
|
|
1152
1400
|
" native dev <target> run the native shell on the dev server",
|
|
1153
1401
|
" native doctor | native clean check or remove the native project",
|
|
1154
|
-
" keys [--check] write generated/assets.ts and manifest.json",
|
|
1402
|
+
" keys [--check] write generated/assets.ts and generated/manifest.json",
|
|
1155
1403
|
" pack [--no-cache] pack the assets into dist/assets",
|
|
1404
|
+
" visual [--update] [--only <name>] run tests/visual/index.ts: headless, pixels on a Mac",
|
|
1405
|
+
" [--no-pixels | --pixels] [--webgl] [--dir tests/visual/baselines]",
|
|
1406
|
+
" [--tests tests/visual/index.ts] [--url <url>]",
|
|
1156
1407
|
" help print this text",
|
|
1157
1408
|
"",
|
|
1158
1409
|
"Every command: --root <dir> (default .), --preload <path>, --serve-plugin <path>."
|
|
@@ -1161,7 +1412,7 @@ const USAGE = [
|
|
|
1161
1412
|
* Tells whether a word is a command of the line that runs something.
|
|
1162
1413
|
*
|
|
1163
1414
|
* @param word - The first positional argument.
|
|
1164
|
-
* @returns True for `dev`, `build`, `native`, `keys` and `
|
|
1415
|
+
* @returns True for `dev`, `build`, `native`, `keys`, `pack` and `visual`.
|
|
1165
1416
|
*/
|
|
1166
1417
|
function isCommand(word) {
|
|
1167
1418
|
return Object.hasOwn(COMMAND_OPTIONS, word);
|
|
@@ -1238,7 +1489,7 @@ function parseCommand(argv, cwd) {
|
|
|
1238
1489
|
const word = loose.positionals[0];
|
|
1239
1490
|
if (loose.values.help === true || word === "help") return "help";
|
|
1240
1491
|
if (word === void 0) return void 0;
|
|
1241
|
-
if (!isCommand(word)) throw new Error(`[game] moku-game: no command "${word}". Name one of
|
|
1492
|
+
if (!isCommand(word)) throw new Error(`[game] moku-game: no command "${word}". Name one of ${COMMAND_NAMES}.`);
|
|
1242
1493
|
checkTokens(word, loose.tokens);
|
|
1243
1494
|
const { values, positionals } = parseStrict(argv);
|
|
1244
1495
|
const resolveAll = (files) => (files ?? []).map((file) => path.resolve(cwd, file));
|
|
@@ -1354,6 +1605,41 @@ async function runScan(parsed, deps) {
|
|
|
1354
1605
|
return deps.assets(argv);
|
|
1355
1606
|
}
|
|
1356
1607
|
/**
|
|
1608
|
+
* The flags of `visual` that `parseVisualArgv` reads, in the order given, each value after its
|
|
1609
|
+
* flag: `--dir=shots` becomes `--dir shots`.
|
|
1610
|
+
*
|
|
1611
|
+
* @param argv - The arguments after the bin, their flags checked.
|
|
1612
|
+
* @returns The flags of the runner.
|
|
1613
|
+
*/
|
|
1614
|
+
function visualFlags(argv) {
|
|
1615
|
+
return parseLoose(argv).tokens.flatMap((token) => {
|
|
1616
|
+
if (token.kind !== "option" || !VISUAL_FLAGS.test(token.name)) return [];
|
|
1617
|
+
return token.value === void 0 ? [`--${token.name}`] : [`--${token.name}`, token.value];
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Runs `moku-game visual` with the tests module and the baselines of the game.
|
|
1622
|
+
*
|
|
1623
|
+
* @param argv - The arguments after the bin.
|
|
1624
|
+
* @param parsed - The command line.
|
|
1625
|
+
* @param deps - The process seams.
|
|
1626
|
+
* @returns `0` when every checkpoint passed, else `1`.
|
|
1627
|
+
*/
|
|
1628
|
+
function runVisualCommand(argv, parsed, deps) {
|
|
1629
|
+
refuseExtra(parsed, 0);
|
|
1630
|
+
const tests = parsed.values.tests ?? path.join(parsed.root, VISUAL_TESTS);
|
|
1631
|
+
const url = parsed.values.url;
|
|
1632
|
+
return runVisual({
|
|
1633
|
+
argv,
|
|
1634
|
+
root: parsed.root,
|
|
1635
|
+
tests: path.resolve(deps.cwd, tests),
|
|
1636
|
+
...url === void 0 ? {} : { url },
|
|
1637
|
+
flags: visualFlags(argv),
|
|
1638
|
+
preload: parsed.preload,
|
|
1639
|
+
servePlugins: parsed.servePlugins
|
|
1640
|
+
}, deps);
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1357
1643
|
* Runs the bin once more with every `--preload` as a Bun preload, so the preload runs before the
|
|
1358
1644
|
* engine loads. The re-run is marked, so it does not hop again.
|
|
1359
1645
|
*
|
|
@@ -1392,7 +1678,7 @@ async function runParsed(argv, parsed, deps) {
|
|
|
1392
1678
|
const usesPlugins = parsed.command !== "keys" && parsed.command !== "pack";
|
|
1393
1679
|
checkFiles("--preload", parsed.preload);
|
|
1394
1680
|
if (usesPlugins) checkFiles("--serve-plugin", parsed.servePlugins);
|
|
1395
|
-
if (parsed.command !== "dev" && parsed.preload.length > 0 && deps.env.MOKU_GAME_CHILD !== "1") return hop(argv, parsed, deps);
|
|
1681
|
+
if (parsed.command !== "dev" && parsed.command !== "visual" && parsed.preload.length > 0 && deps.env.MOKU_GAME_CHILD !== "1") return hop(argv, parsed, deps);
|
|
1396
1682
|
switch (parsed.command) {
|
|
1397
1683
|
case "dev": return runServe(parsed, deps);
|
|
1398
1684
|
case "build": {
|
|
@@ -1407,6 +1693,7 @@ async function runParsed(argv, parsed, deps) {
|
|
|
1407
1693
|
case "native": return runNativeVerb(parsed, deps);
|
|
1408
1694
|
case "keys":
|
|
1409
1695
|
case "pack": return runScan(parsed, deps);
|
|
1696
|
+
case "visual": return runVisualCommand(argv, parsed, deps);
|
|
1410
1697
|
}
|
|
1411
1698
|
}
|
|
1412
1699
|
/**
|
|
@@ -1419,8 +1706,8 @@ function printUsage(ui) {
|
|
|
1419
1706
|
}
|
|
1420
1707
|
/**
|
|
1421
1708
|
* Runs one `moku-game` command line: reads the command and its flags, then runs `dev`, `build`,
|
|
1422
|
-
* `native <verb>`, `keys`, `pack` or `help`. Every failure is printed through the
|
|
1423
|
-
* `[game] …` message; nothing exits the process.
|
|
1709
|
+
* `native <verb>`, `keys`, `pack`, `visual` or `help`. Every failure is printed through the
|
|
1710
|
+
* console as a `[game] …` message; nothing exits the process.
|
|
1424
1711
|
*
|
|
1425
1712
|
* @param argv - The arguments after the bin.
|
|
1426
1713
|
* @param deps - The process seams.
|
|
@@ -1435,7 +1722,7 @@ async function runCommand(argv, deps) {
|
|
|
1435
1722
|
}
|
|
1436
1723
|
if (parsed === void 0) {
|
|
1437
1724
|
printUsage(deps.ui);
|
|
1438
|
-
deps.ui.error(
|
|
1725
|
+
deps.ui.error(`[game] moku-game: name a command: ${COMMAND_NAMES}.`);
|
|
1439
1726
|
return 1;
|
|
1440
1727
|
}
|
|
1441
1728
|
return await runParsed(argv, parsed, deps);
|
|
@@ -1593,15 +1880,15 @@ async function runNative(verb, settings, where, options) {
|
|
|
1593
1880
|
//#region src/cli.ts
|
|
1594
1881
|
/**
|
|
1595
1882
|
* @file The `@moku-labs/game/cli` door, node and bun only: the `moku-game` bin and the editor's
|
|
1596
|
-
* seam. `runCli` runs one command line of `moku-game` (dev, build, native, keys, pack,
|
|
1597
|
-
* this process; `preparePage` writes a game's dev page into `<game>/.moku/` for a server the
|
|
1883
|
+
* seam. `runCli` runs one command line of `moku-game` (dev, build, native, keys, pack, visual,
|
|
1884
|
+
* help) in this process; `preparePage` writes a game's dev page into `<game>/.moku/` for a server the
|
|
1598
1885
|
* caller starts itself, as the editor does. It imports no Pixi, and the root entry never imports
|
|
1599
1886
|
* it. The code lives in `app/`; this file wires the real process into it.
|
|
1600
1887
|
*/
|
|
1601
1888
|
/**
|
|
1602
1889
|
* The real process behind the command line: the branded console, the environment the bin
|
|
1603
1890
|
* started with, Bun's spawn and resolver, the signals, `fs.watch`, the asset scanner with the
|
|
1604
|
-
* string tools, and
|
|
1891
|
+
* string tools, native, and the visual test runner, loaded only by `moku-game visual`.
|
|
1605
1892
|
*
|
|
1606
1893
|
* @returns The seams of this process.
|
|
1607
1894
|
*/
|
|
@@ -1634,13 +1921,14 @@ function processDeps() {
|
|
|
1634
1921
|
}, ui),
|
|
1635
1922
|
native: runNative,
|
|
1636
1923
|
resolve: (specifier, from) => Bun.resolveSync(specifier, from),
|
|
1637
|
-
loadPage: (file) => import(file)
|
|
1924
|
+
loadPage: (file) => import(file),
|
|
1925
|
+
visual: () => import("./visual.mjs")
|
|
1638
1926
|
};
|
|
1639
1927
|
}
|
|
1640
1928
|
/**
|
|
1641
1929
|
* Runs one `moku-game` command line in this process: `dev`, `build`, `native <verb>`, `keys`,
|
|
1642
|
-
* `pack` or `help`, with `--root`, `--preload` and `--serve-plugin` before or after the
|
|
1643
|
-
* word. Every line goes through the branded console; nothing exits the process.
|
|
1930
|
+
* `pack`, `visual` or `help`, with `--root`, `--preload` and `--serve-plugin` before or after the
|
|
1931
|
+
* command word. Every line goes through the branded console; nothing exits the process.
|
|
1644
1932
|
*
|
|
1645
1933
|
* @param argv - The arguments after the bin.
|
|
1646
1934
|
* @returns The exit code.
|
|
@@ -1648,6 +1936,7 @@ function processDeps() {
|
|
|
1648
1936
|
* ```ts
|
|
1649
1937
|
* // bin/moku-game.mjs in a game folder:
|
|
1650
1938
|
* await runCli(["dev", "--port", "0"]); // 0 after Ctrl+C; stdout had "http://localhost:<port>/"
|
|
1939
|
+
* await runCli(["visual", "--no-pixels"]); // 0 when every checkpoint of tests/visual/index.ts is the same
|
|
1651
1940
|
* await runCli(["help"]); // 0
|
|
1652
1941
|
* ```
|
|
1653
1942
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { A as Api$2, E as DebugSwitches, Ot as Bookmark, h as Api$1, jt as FlowState, ln as Api, vn as Time, w as Captured } from "./types-ZQLYXo5X.mjs";
|
|
2
|
-
import { a as AudioApi, r as UiApi, v as ScenesApi, x as AnimApi } from "./types-
|
|
3
|
-
import { A as InputApi } from "./types-
|
|
2
|
+
import { a as AudioApi, r as UiApi, v as ScenesApi, x as AnimApi } from "./types-D_2G6Q68.mjs";
|
|
3
|
+
import { A as InputApi } from "./types-BrTpz9oL.mjs";
|
|
4
4
|
import { t as HeadlessApp } from "./headless-G4nuZ-xz.mjs";
|
|
5
5
|
import { n as Command, r as ControlApp } from "./types-DAdwEJMp.mjs";
|
|
6
6
|
|
package/dist/control.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as HeadlessApp } from "./headless-G4nuZ-xz.mjs";
|
|
2
2
|
import { i as InputArguments, n as Command, o as InputSchema, s as Ran } from "./types-DAdwEJMp.mjs";
|
|
3
|
-
import { t as commands } from "./commands-
|
|
3
|
+
import { t as commands } from "./commands-DvFr8Uwp.mjs";
|
|
4
4
|
import { t as defineCommand } from "./define-CCc0ozKe.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/plugins/flow/doors/dev.d.ts
|