@moku-labs/game 0.11.0 → 0.12.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 +1 -0
- 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 +324 -43
- 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/{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 +6 -5
- 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, {",
|
|
@@ -514,47 +516,52 @@ function writeIfChanged(file, text) {
|
|
|
514
516
|
* @param root - The game folder.
|
|
515
517
|
* @param settings - The resolved config.
|
|
516
518
|
* @param agents - The agent modules, when the page has any.
|
|
519
|
+
* @param toRoot - The path from `main.ts` to the game folder.
|
|
517
520
|
* @returns The text.
|
|
518
521
|
*/
|
|
519
|
-
function mainOf(root, settings, agents) {
|
|
522
|
+
function mainOf(root, settings, agents, toRoot = "../") {
|
|
520
523
|
const scenarios = scenarioNamesOf(root);
|
|
521
524
|
return agents === void 0 || agents.length === 0 ? devMain(settings, {
|
|
522
525
|
scenarios,
|
|
523
526
|
devModules: []
|
|
524
|
-
}) : devMain(settings, {
|
|
527
|
+
}, toRoot) : devMain(settings, {
|
|
525
528
|
scenarios,
|
|
526
529
|
devModules: devModulesOf(root),
|
|
527
530
|
agents
|
|
528
|
-
});
|
|
531
|
+
}, toRoot);
|
|
529
532
|
}
|
|
530
533
|
/**
|
|
531
534
|
* Writes the dev page of a checked game into `<game>/.moku/`: `index.html`, `dev.ts`, `main.ts`
|
|
532
|
-
* and `bunfig.toml`.
|
|
533
|
-
*
|
|
535
|
+
* and `bunfig.toml`. `moku-game visual` writes its own page into `.moku/visual/`, so the page of
|
|
536
|
+
* `dev` or of the editor stays as it is; the links of the page reach the game from its depth.
|
|
537
|
+
* Every text is made before the first write, so a refused value writes nothing.
|
|
534
538
|
*
|
|
535
539
|
* @param root - The game folder, absolute.
|
|
536
540
|
* @param settings - The resolved config.
|
|
537
541
|
* @param options - The agents, the preloads and the extra plugins, absolute.
|
|
538
542
|
* @param resolve - Bun's resolver, to find the hot plugin.
|
|
543
|
+
* @param page - The folder of the page under the game, `/` separated: `.moku` by default.
|
|
539
544
|
* @returns The paths of the HTML and the bunfig.
|
|
540
545
|
* @throws {Error} When the hot plugin does not resolve, a preload or plugin is not a file, or a
|
|
541
546
|
* page value is refused.
|
|
542
547
|
*/
|
|
543
|
-
function writePage(root, settings, options, resolve) {
|
|
548
|
+
function writePage(root, settings, options, resolve, page = ".moku") {
|
|
544
549
|
const preload = options.preload ?? [];
|
|
545
550
|
const servePlugins = options.servePlugins ?? [];
|
|
546
551
|
checkFiles("--preload", preload);
|
|
547
552
|
checkFiles("--serve-plugin", servePlugins);
|
|
548
|
-
const
|
|
553
|
+
const segments = page.split("/");
|
|
554
|
+
const toRoot = "../".repeat(segments.length);
|
|
555
|
+
const folder = path.join(root, ...segments);
|
|
549
556
|
const html = path.join(folder, "index.html");
|
|
550
557
|
const bunfig = path.join(folder, "bunfig.toml");
|
|
551
558
|
const texts = [
|
|
552
559
|
[html, pageHtml(settings.page, {
|
|
553
|
-
toRoot
|
|
560
|
+
toRoot,
|
|
554
561
|
script: "./main.ts"
|
|
555
562
|
})],
|
|
556
563
|
[path.join(folder, "dev.ts"), devFlag()],
|
|
557
|
-
[path.join(folder, "main.ts"), mainOf(root, settings, options.agents)],
|
|
564
|
+
[path.join(folder, "main.ts"), mainOf(root, settings, options.agents, toRoot)],
|
|
558
565
|
[bunfig, bunfigText({
|
|
559
566
|
hotPlugin: hotPluginOf(root, resolve),
|
|
560
567
|
servePlugins,
|
|
@@ -661,8 +668,25 @@ async function serveParent(run, deps) {
|
|
|
661
668
|
if (!run.packed && !hasManifest) deps.ui.warn(`[game] dev: no manifest.json in "${run.root}".\n Run "moku-game keys" first.`);
|
|
662
669
|
const page = writePage(run.root, settings, run, deps.resolve);
|
|
663
670
|
if (!ignoresMoku(run.root)) deps.ui.warn("[game] dev: add \".moku/\" to .gitignore. moku-game writes its dev page there.");
|
|
664
|
-
|
|
671
|
+
return runChild(devChildCommand(run, page, deps), deps, {
|
|
665
672
|
cwd: run.root,
|
|
673
|
+
whileRunning: () => watchScenarios(run.root, settings, deps)
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Runs the bin again as a marked child, `MOKU_GAME_CHILD=1`, in its own process group: Ctrl+C
|
|
678
|
+
* reaches the parent, which hands it and the other stop signals on. `dev` and `visual` run their
|
|
679
|
+
* page server this way, under the page's bunfig.
|
|
680
|
+
*
|
|
681
|
+
* @param command - The words of the command: the Bun, its flags, the bin and its arguments.
|
|
682
|
+
* @param deps - The spawn, the signals and the environment.
|
|
683
|
+
* @param child - The working directory, and what lives as long as the child.
|
|
684
|
+
* @returns The exit code of the child.
|
|
685
|
+
* @throws {Error} When the child does not start.
|
|
686
|
+
*/
|
|
687
|
+
async function runChild(command, deps, child) {
|
|
688
|
+
const started = deps.spawn(command, {
|
|
689
|
+
cwd: child.cwd,
|
|
666
690
|
env: {
|
|
667
691
|
...deps.env,
|
|
668
692
|
MOKU_GAME_CHILD: "1"
|
|
@@ -674,11 +698,11 @@ async function serveParent(run, deps) {
|
|
|
674
698
|
],
|
|
675
699
|
detached: true
|
|
676
700
|
});
|
|
677
|
-
const removers = FORWARDED.map((signal) => deps.onSignal(signal, () =>
|
|
701
|
+
const removers = FORWARDED.map((signal) => deps.onSignal(signal, () => started.kill(signal)));
|
|
678
702
|
let watcher;
|
|
679
703
|
try {
|
|
680
|
-
watcher =
|
|
681
|
-
return await
|
|
704
|
+
watcher = child.whileRunning?.();
|
|
705
|
+
return await started.exited;
|
|
682
706
|
} finally {
|
|
683
707
|
watcher?.close();
|
|
684
708
|
for (const remove of removers) remove();
|
|
@@ -731,10 +755,11 @@ function fileResponse(file) {
|
|
|
731
755
|
}
|
|
732
756
|
/**
|
|
733
757
|
* Starts the dev server on `127.0.0.1` only, never on every interface: the page on `/`, the
|
|
734
|
-
* manifest of the served folder on `/manifest.json`, and its files as static files.
|
|
758
|
+
* manifest of the served folder on `/manifest.json`, and its files as static files. The dev child
|
|
759
|
+
* and `moku-game visual` serve the page with it.
|
|
735
760
|
*
|
|
736
761
|
* @param run - The flags of the run.
|
|
737
|
-
* @param page - The page bundle the
|
|
762
|
+
* @param page - The page bundle the process imported.
|
|
738
763
|
* @returns The server.
|
|
739
764
|
* @throws {Error} When the port is in use.
|
|
740
765
|
*/
|
|
@@ -757,6 +782,22 @@ function serveOn(run, page) {
|
|
|
757
782
|
}
|
|
758
783
|
}
|
|
759
784
|
/**
|
|
785
|
+
* Watches the parent of a child run: a parent that died hands the child to another process, so
|
|
786
|
+
* its parent id changes. The dev child and the visual child stop their server then.
|
|
787
|
+
*
|
|
788
|
+
* @param onGone - Called once when the parent is gone.
|
|
789
|
+
* @returns Stops watching.
|
|
790
|
+
*/
|
|
791
|
+
function watchParent(onGone) {
|
|
792
|
+
const parent = process.ppid;
|
|
793
|
+
const timer = setInterval(() => {
|
|
794
|
+
if (process.ppid === parent) return;
|
|
795
|
+
clearInterval(timer);
|
|
796
|
+
onGone();
|
|
797
|
+
}, PARENT_CHECK_MS);
|
|
798
|
+
return () => clearInterval(timer);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
760
801
|
* Waits until the dev child should stop: Ctrl+C, a stop signal, or a parent that died (its
|
|
761
802
|
* parent id changed). Then stops the server.
|
|
762
803
|
*
|
|
@@ -765,17 +806,14 @@ function serveOn(run, page) {
|
|
|
765
806
|
* @returns Resolves to the exit code `0` once the server stopped.
|
|
766
807
|
*/
|
|
767
808
|
function untilStopped(server, deps) {
|
|
768
|
-
const parent = process.ppid;
|
|
769
809
|
return new Promise((resolve) => {
|
|
770
810
|
const removers = [];
|
|
771
|
-
const timer = setInterval(() => {
|
|
772
|
-
if (process.ppid !== parent) stop();
|
|
773
|
-
}, PARENT_CHECK_MS);
|
|
774
811
|
const stop = () => {
|
|
775
|
-
|
|
812
|
+
unwatch();
|
|
776
813
|
for (const remove of removers.splice(0)) remove();
|
|
777
814
|
resolve(server.stop(true).then(() => 0));
|
|
778
815
|
};
|
|
816
|
+
const unwatch = watchParent(stop);
|
|
779
817
|
for (const signal of STOPPING) removers.push(deps.onSignal(signal, stop));
|
|
780
818
|
});
|
|
781
819
|
}
|
|
@@ -1086,13 +1124,188 @@ async function runBuild(run, deps) {
|
|
|
1086
1124
|
return 0;
|
|
1087
1125
|
}
|
|
1088
1126
|
//#endregion
|
|
1127
|
+
//#region src/app/visual.ts
|
|
1128
|
+
/**
|
|
1129
|
+
* @file `moku-game visual`: the visual tests of a game from the bin. It imports the tests module,
|
|
1130
|
+
* `tests/visual/index.ts` by default, whose default export is `{ app, tests }`, the two arguments
|
|
1131
|
+
* of `runVisualTests`: `app` is a `VisualSetup`. The flags of the runner are read by
|
|
1132
|
+
* `parseVisualArgv`. When the pixel leg runs without `--url`, the command writes its own dev page
|
|
1133
|
+
* into `.moku/visual/`, so the page of `dev` or of the editor stays as it is, runs the bin again
|
|
1134
|
+
* under that page's bunfig as `dev` does, and the child serves the page with the server of `dev`
|
|
1135
|
+
* on a free port and stops it after. Node and Bun only: the bin bundles it.
|
|
1136
|
+
*/
|
|
1137
|
+
/** The baselines of a game, under the game folder. */
|
|
1138
|
+
const BASELINES = path.join("tests", "visual", "baselines");
|
|
1139
|
+
/** The folder of the visual page under the game: its own, beside the page of `dev`. */
|
|
1140
|
+
const VISUAL_PAGE = ".moku/visual";
|
|
1141
|
+
/**
|
|
1142
|
+
* Tells whether a value is a setup of the runner: an object with an `app` function.
|
|
1143
|
+
*
|
|
1144
|
+
* @param value - The `app` of the module.
|
|
1145
|
+
* @returns True for a `VisualSetup`.
|
|
1146
|
+
*/
|
|
1147
|
+
function isSetup(value) {
|
|
1148
|
+
return typeof value === "object" && value !== null && "app" in value && typeof value.app === "function";
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Reads the default export of a tests module: `app` is the setup of the runner, `{ app, page? }`,
|
|
1152
|
+
* and `tests` is a list. Each test is checked by the runner.
|
|
1153
|
+
*
|
|
1154
|
+
* @param value - The default export.
|
|
1155
|
+
* @returns The suite, or `undefined` for another shape.
|
|
1156
|
+
*/
|
|
1157
|
+
function suiteOf(value) {
|
|
1158
|
+
if (typeof value !== "object" || value === null || !("app" in value) || !("tests" in value)) return;
|
|
1159
|
+
const { app, tests } = value;
|
|
1160
|
+
return Array.isArray(tests) && isSetup(app) ? {
|
|
1161
|
+
setup: app,
|
|
1162
|
+
tests
|
|
1163
|
+
} : void 0;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Imports the tests module of the run.
|
|
1167
|
+
*
|
|
1168
|
+
* @param file - The module, absolute.
|
|
1169
|
+
* @param cwd - The working directory, to name the module in the message.
|
|
1170
|
+
* @returns The setup and the tests.
|
|
1171
|
+
* @throws {Error} When the module is missing or its default export is not `{ app, tests }`.
|
|
1172
|
+
*/
|
|
1173
|
+
async function loadSuite(file, cwd) {
|
|
1174
|
+
const suite = suiteOf((statSync(file, { throwIfNoEntry: false })?.isFile() === true ? await import(pathToFileURL(file).href) : void 0)?.default);
|
|
1175
|
+
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 }.`);
|
|
1176
|
+
return suite;
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Writes the visual page of the game into `.moku/visual/`: `index.html`, `dev.ts`, `main.ts` and
|
|
1180
|
+
* `bunfig.toml`, the hot plugin, the serve plugins and the preloads in it, as `dev` writes its page.
|
|
1181
|
+
*
|
|
1182
|
+
* @param page - The game folder, the preloads and the serve plugins.
|
|
1183
|
+
* @param resolve - Bun's resolver, to find the hot plugin.
|
|
1184
|
+
* @returns The paths of the HTML and the bunfig.
|
|
1185
|
+
* @throws {Error} The errors of `moku-game dev` before its server starts, and a game without
|
|
1186
|
+
* `manifest.json`.
|
|
1187
|
+
*/
|
|
1188
|
+
async function writeVisualPage(page, resolve) {
|
|
1189
|
+
const settings = await loadGame(page.root);
|
|
1190
|
+
const manifest = path.join(page.root, "manifest.json");
|
|
1191
|
+
if (statSync(manifest, { throwIfNoEntry: false })?.isFile() !== true) throw new Error(`[game] visual: no manifest.json in "${page.root}".\n Run "moku-game keys" first.`);
|
|
1192
|
+
return writePage(page.root, settings, {
|
|
1193
|
+
preload: page.preload,
|
|
1194
|
+
servePlugins: page.servePlugins
|
|
1195
|
+
}, resolve, VISUAL_PAGE);
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Writes the visual page and serves it in this process on a free port, as the dev child serves
|
|
1199
|
+
* its page. It runs in the child that runs under the page's bunfig, so the hot plugin, the serve
|
|
1200
|
+
* plugins and the preloads apply.
|
|
1201
|
+
*
|
|
1202
|
+
* @param page - The game folder, the preloads and the serve plugins.
|
|
1203
|
+
* @param deps - Bun's resolver and the page loader.
|
|
1204
|
+
* @returns The running server.
|
|
1205
|
+
* @throws {Error} The errors of `writeVisualPage`.
|
|
1206
|
+
*/
|
|
1207
|
+
async function serveVisualPage(page, deps) {
|
|
1208
|
+
const files = await writeVisualPage(page, deps.resolve);
|
|
1209
|
+
const loaded = await deps.loadPage(files.html);
|
|
1210
|
+
return serveOn({
|
|
1211
|
+
root: page.root,
|
|
1212
|
+
port: 0,
|
|
1213
|
+
packed: false,
|
|
1214
|
+
preload: page.preload,
|
|
1215
|
+
servePlugins: page.servePlugins
|
|
1216
|
+
}, loaded.default);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Runs the bin again for a run that has to: under the visual page's bunfig when the page is
|
|
1220
|
+
* served, so it is bundled as `dev` bundles its page; with Bun's preloads otherwise.
|
|
1221
|
+
*
|
|
1222
|
+
* @param command - The run.
|
|
1223
|
+
* @param serves - True when the page is served for the run.
|
|
1224
|
+
* @param deps - The process seams.
|
|
1225
|
+
* @returns The exit code of the child.
|
|
1226
|
+
* @throws {Error} The errors of `writeVisualPage`.
|
|
1227
|
+
*/
|
|
1228
|
+
async function rerun(command, serves, deps) {
|
|
1229
|
+
const page = serves ? await writeVisualPage(command, deps.resolve) : void 0;
|
|
1230
|
+
const bun = page === void 0 ? command.preload.map((file) => `--preload=${file}`) : [`--config=${page.bunfig}`];
|
|
1231
|
+
return runChild([
|
|
1232
|
+
deps.execPath,
|
|
1233
|
+
...bun,
|
|
1234
|
+
...deps.self,
|
|
1235
|
+
...command.argv
|
|
1236
|
+
], deps, { cwd: deps.cwd });
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Plays the tests in this process: the headless leg, then the pixel leg against `--url` or the
|
|
1240
|
+
* page served here, which stops when the run ends or the parent of the run is gone.
|
|
1241
|
+
*
|
|
1242
|
+
* @param command - The run.
|
|
1243
|
+
* @param run - The runner, its flags, and whether the pixel leg runs.
|
|
1244
|
+
* @param deps - The process seams.
|
|
1245
|
+
* @returns `0` when the report is ok, `1` otherwise.
|
|
1246
|
+
* @throws {Error} When the tests module is refused, the page cannot be served, or the runner
|
|
1247
|
+
* throws.
|
|
1248
|
+
*/
|
|
1249
|
+
async function play(command, run, deps) {
|
|
1250
|
+
const suite = await loadSuite(command.tests, deps.cwd);
|
|
1251
|
+
const dir = path.resolve(deps.cwd, run.flags.dir ?? path.join(command.root, BASELINES));
|
|
1252
|
+
const server = run.serves ? await serveVisualPage(command, deps) : void 0;
|
|
1253
|
+
const unwatch = server === void 0 ? void 0 : watchParent(() => server.stop(true));
|
|
1254
|
+
const url = command.url ?? server?.url.href;
|
|
1255
|
+
try {
|
|
1256
|
+
const setup = url === void 0 ? suite.setup : {
|
|
1257
|
+
...suite.setup,
|
|
1258
|
+
page: {
|
|
1259
|
+
...suite.setup.page,
|
|
1260
|
+
url
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
return (await run.visual.runVisualTests(setup, suite.tests, {
|
|
1264
|
+
...run.flags,
|
|
1265
|
+
pixels: run.pixels,
|
|
1266
|
+
dir,
|
|
1267
|
+
argv: []
|
|
1268
|
+
})).ok ? 0 : 1;
|
|
1269
|
+
} finally {
|
|
1270
|
+
unwatch?.();
|
|
1271
|
+
await server?.stop(true);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Runs `moku-game visual`: the headless leg, then the pixel leg on a Mac (or with `--pixels`)
|
|
1276
|
+
* against `--url` or the page served for the run. A run that serves the page runs the bin again
|
|
1277
|
+
* under `.moku/visual/bunfig.toml`, as `dev` does, and a run with `--preload` with the preloads;
|
|
1278
|
+
* the marked child plays the tests. The baselines live in `<game>/tests/visual/baselines/` unless
|
|
1279
|
+
* `--dir` names another folder.
|
|
1280
|
+
*
|
|
1281
|
+
* @param command - The run.
|
|
1282
|
+
* @param deps - The process seams.
|
|
1283
|
+
* @returns `0` when the report is ok, `1` otherwise, or the exit code of the child.
|
|
1284
|
+
* @throws {Error} When the tests module is refused, a flag misses its value, the page cannot be
|
|
1285
|
+
* served, or the runner throws.
|
|
1286
|
+
*/
|
|
1287
|
+
async function runVisual(command, deps) {
|
|
1288
|
+
const visual = await deps.visual();
|
|
1289
|
+
const flags = visual.parseVisualArgv(command.flags);
|
|
1290
|
+
const pixels = flags.pixels ?? process.platform === "darwin";
|
|
1291
|
+
const serves = pixels && command.url === void 0;
|
|
1292
|
+
if (!(deps.env.MOKU_GAME_CHILD === "1") && (serves || command.preload.length > 0)) return rerun(command, serves, deps);
|
|
1293
|
+
return play(command, {
|
|
1294
|
+
visual,
|
|
1295
|
+
flags,
|
|
1296
|
+
pixels,
|
|
1297
|
+
serves
|
|
1298
|
+
}, deps);
|
|
1299
|
+
}
|
|
1300
|
+
//#endregion
|
|
1089
1301
|
//#region src/app/cli.ts
|
|
1090
1302
|
/**
|
|
1091
1303
|
* @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,
|
|
1304
|
+
* `parseArgs`, then one of `dev`, `build`, `native <verb>`, `keys`, `pack`, `visual` or `help`.
|
|
1305
|
+
* Every seam of the process (the console, the environment, spawning, signals, the watcher, the
|
|
1306
|
+
* asset scanner, native, Bun's resolver, the visual test runner) comes in as `deps`, so a test
|
|
1307
|
+
* runs a command with stubs. It returns the exit code and never exits itself. A plain function,
|
|
1308
|
+
* on no API.
|
|
1096
1309
|
*/
|
|
1097
1310
|
/** The flags every command takes. */
|
|
1098
1311
|
const GLOBAL_OPTIONS = [
|
|
@@ -1121,15 +1334,38 @@ const ALL_OPTIONS = {
|
|
|
1121
1334
|
out: { type: "string" },
|
|
1122
1335
|
simulator: { type: "boolean" },
|
|
1123
1336
|
check: { type: "boolean" },
|
|
1124
|
-
"no-cache": { type: "boolean" }
|
|
1337
|
+
"no-cache": { type: "boolean" },
|
|
1338
|
+
tests: { type: "string" },
|
|
1339
|
+
url: { type: "string" },
|
|
1340
|
+
update: { type: "boolean" },
|
|
1341
|
+
only: {
|
|
1342
|
+
type: "string",
|
|
1343
|
+
multiple: true
|
|
1344
|
+
},
|
|
1345
|
+
"no-pixels": { type: "boolean" },
|
|
1346
|
+
pixels: { type: "boolean" },
|
|
1347
|
+
dir: { type: "string" },
|
|
1348
|
+
webgl: { type: "boolean" }
|
|
1125
1349
|
};
|
|
1350
|
+
/** The flags of `visual` that `parseVisualArgv` reads, in the runner's words. */
|
|
1351
|
+
const VISUAL_FLAGS = /^(?:update|only|no-pixels|pixels|dir|webgl)$/;
|
|
1126
1352
|
/** The flags of one command besides the global ones. */
|
|
1127
1353
|
const COMMAND_OPTIONS = {
|
|
1128
1354
|
dev: ["port", "packed"],
|
|
1129
1355
|
build: ["out"],
|
|
1130
1356
|
native: ["simulator"],
|
|
1131
1357
|
keys: ["check"],
|
|
1132
|
-
pack: ["no-cache"]
|
|
1358
|
+
pack: ["no-cache"],
|
|
1359
|
+
visual: [
|
|
1360
|
+
"tests",
|
|
1361
|
+
"url",
|
|
1362
|
+
"update",
|
|
1363
|
+
"only",
|
|
1364
|
+
"no-pixels",
|
|
1365
|
+
"pixels",
|
|
1366
|
+
"dir",
|
|
1367
|
+
"webgl"
|
|
1368
|
+
]
|
|
1133
1369
|
};
|
|
1134
1370
|
/** The native verbs, in the order the messages name them. */
|
|
1135
1371
|
const NATIVE_VERBS = [
|
|
@@ -1138,6 +1374,10 @@ const NATIVE_VERBS = [
|
|
|
1138
1374
|
"doctor",
|
|
1139
1375
|
"clean"
|
|
1140
1376
|
];
|
|
1377
|
+
/** The command words, in the order the messages name them. */
|
|
1378
|
+
const COMMAND_NAMES = "dev, build, native, keys, pack, visual, help";
|
|
1379
|
+
/** The tests module of a game, under the game folder. */
|
|
1380
|
+
const VISUAL_TESTS = path.join("tests", "visual", "index.ts");
|
|
1141
1381
|
/** The port of `moku-game dev` without `--port`. */
|
|
1142
1382
|
const DEFAULT_PORT = "3000";
|
|
1143
1383
|
/** The highest port number. */
|
|
@@ -1153,6 +1393,9 @@ const USAGE = [
|
|
|
1153
1393
|
" native doctor | native clean check or remove the native project",
|
|
1154
1394
|
" keys [--check] write generated/assets.ts and manifest.json",
|
|
1155
1395
|
" pack [--no-cache] pack the assets into dist/assets",
|
|
1396
|
+
" visual [--update] [--only <name>] run tests/visual/index.ts: headless, pixels on a Mac",
|
|
1397
|
+
" [--no-pixels | --pixels] [--webgl] [--dir tests/visual/baselines]",
|
|
1398
|
+
" [--tests tests/visual/index.ts] [--url <url>]",
|
|
1156
1399
|
" help print this text",
|
|
1157
1400
|
"",
|
|
1158
1401
|
"Every command: --root <dir> (default .), --preload <path>, --serve-plugin <path>."
|
|
@@ -1161,7 +1404,7 @@ const USAGE = [
|
|
|
1161
1404
|
* Tells whether a word is a command of the line that runs something.
|
|
1162
1405
|
*
|
|
1163
1406
|
* @param word - The first positional argument.
|
|
1164
|
-
* @returns True for `dev`, `build`, `native`, `keys` and `
|
|
1407
|
+
* @returns True for `dev`, `build`, `native`, `keys`, `pack` and `visual`.
|
|
1165
1408
|
*/
|
|
1166
1409
|
function isCommand(word) {
|
|
1167
1410
|
return Object.hasOwn(COMMAND_OPTIONS, word);
|
|
@@ -1238,7 +1481,7 @@ function parseCommand(argv, cwd) {
|
|
|
1238
1481
|
const word = loose.positionals[0];
|
|
1239
1482
|
if (loose.values.help === true || word === "help") return "help";
|
|
1240
1483
|
if (word === void 0) return void 0;
|
|
1241
|
-
if (!isCommand(word)) throw new Error(`[game] moku-game: no command "${word}". Name one of
|
|
1484
|
+
if (!isCommand(word)) throw new Error(`[game] moku-game: no command "${word}". Name one of ${COMMAND_NAMES}.`);
|
|
1242
1485
|
checkTokens(word, loose.tokens);
|
|
1243
1486
|
const { values, positionals } = parseStrict(argv);
|
|
1244
1487
|
const resolveAll = (files) => (files ?? []).map((file) => path.resolve(cwd, file));
|
|
@@ -1354,6 +1597,41 @@ async function runScan(parsed, deps) {
|
|
|
1354
1597
|
return deps.assets(argv);
|
|
1355
1598
|
}
|
|
1356
1599
|
/**
|
|
1600
|
+
* The flags of `visual` that `parseVisualArgv` reads, in the order given, each value after its
|
|
1601
|
+
* flag: `--dir=shots` becomes `--dir shots`.
|
|
1602
|
+
*
|
|
1603
|
+
* @param argv - The arguments after the bin, their flags checked.
|
|
1604
|
+
* @returns The flags of the runner.
|
|
1605
|
+
*/
|
|
1606
|
+
function visualFlags(argv) {
|
|
1607
|
+
return parseLoose(argv).tokens.flatMap((token) => {
|
|
1608
|
+
if (token.kind !== "option" || !VISUAL_FLAGS.test(token.name)) return [];
|
|
1609
|
+
return token.value === void 0 ? [`--${token.name}`] : [`--${token.name}`, token.value];
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Runs `moku-game visual` with the tests module and the baselines of the game.
|
|
1614
|
+
*
|
|
1615
|
+
* @param argv - The arguments after the bin.
|
|
1616
|
+
* @param parsed - The command line.
|
|
1617
|
+
* @param deps - The process seams.
|
|
1618
|
+
* @returns `0` when every checkpoint passed, else `1`.
|
|
1619
|
+
*/
|
|
1620
|
+
function runVisualCommand(argv, parsed, deps) {
|
|
1621
|
+
refuseExtra(parsed, 0);
|
|
1622
|
+
const tests = parsed.values.tests ?? path.join(parsed.root, VISUAL_TESTS);
|
|
1623
|
+
const url = parsed.values.url;
|
|
1624
|
+
return runVisual({
|
|
1625
|
+
argv,
|
|
1626
|
+
root: parsed.root,
|
|
1627
|
+
tests: path.resolve(deps.cwd, tests),
|
|
1628
|
+
...url === void 0 ? {} : { url },
|
|
1629
|
+
flags: visualFlags(argv),
|
|
1630
|
+
preload: parsed.preload,
|
|
1631
|
+
servePlugins: parsed.servePlugins
|
|
1632
|
+
}, deps);
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1357
1635
|
* Runs the bin once more with every `--preload` as a Bun preload, so the preload runs before the
|
|
1358
1636
|
* engine loads. The re-run is marked, so it does not hop again.
|
|
1359
1637
|
*
|
|
@@ -1392,7 +1670,7 @@ async function runParsed(argv, parsed, deps) {
|
|
|
1392
1670
|
const usesPlugins = parsed.command !== "keys" && parsed.command !== "pack";
|
|
1393
1671
|
checkFiles("--preload", parsed.preload);
|
|
1394
1672
|
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);
|
|
1673
|
+
if (parsed.command !== "dev" && parsed.command !== "visual" && parsed.preload.length > 0 && deps.env.MOKU_GAME_CHILD !== "1") return hop(argv, parsed, deps);
|
|
1396
1674
|
switch (parsed.command) {
|
|
1397
1675
|
case "dev": return runServe(parsed, deps);
|
|
1398
1676
|
case "build": {
|
|
@@ -1407,6 +1685,7 @@ async function runParsed(argv, parsed, deps) {
|
|
|
1407
1685
|
case "native": return runNativeVerb(parsed, deps);
|
|
1408
1686
|
case "keys":
|
|
1409
1687
|
case "pack": return runScan(parsed, deps);
|
|
1688
|
+
case "visual": return runVisualCommand(argv, parsed, deps);
|
|
1410
1689
|
}
|
|
1411
1690
|
}
|
|
1412
1691
|
/**
|
|
@@ -1419,8 +1698,8 @@ function printUsage(ui) {
|
|
|
1419
1698
|
}
|
|
1420
1699
|
/**
|
|
1421
1700
|
* 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.
|
|
1701
|
+
* `native <verb>`, `keys`, `pack`, `visual` or `help`. Every failure is printed through the
|
|
1702
|
+
* console as a `[game] …` message; nothing exits the process.
|
|
1424
1703
|
*
|
|
1425
1704
|
* @param argv - The arguments after the bin.
|
|
1426
1705
|
* @param deps - The process seams.
|
|
@@ -1435,7 +1714,7 @@ async function runCommand(argv, deps) {
|
|
|
1435
1714
|
}
|
|
1436
1715
|
if (parsed === void 0) {
|
|
1437
1716
|
printUsage(deps.ui);
|
|
1438
|
-
deps.ui.error(
|
|
1717
|
+
deps.ui.error(`[game] moku-game: name a command: ${COMMAND_NAMES}.`);
|
|
1439
1718
|
return 1;
|
|
1440
1719
|
}
|
|
1441
1720
|
return await runParsed(argv, parsed, deps);
|
|
@@ -1593,15 +1872,15 @@ async function runNative(verb, settings, where, options) {
|
|
|
1593
1872
|
//#region src/cli.ts
|
|
1594
1873
|
/**
|
|
1595
1874
|
* @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
|
|
1875
|
+
* seam. `runCli` runs one command line of `moku-game` (dev, build, native, keys, pack, visual,
|
|
1876
|
+
* help) in this process; `preparePage` writes a game's dev page into `<game>/.moku/` for a server the
|
|
1598
1877
|
* caller starts itself, as the editor does. It imports no Pixi, and the root entry never imports
|
|
1599
1878
|
* it. The code lives in `app/`; this file wires the real process into it.
|
|
1600
1879
|
*/
|
|
1601
1880
|
/**
|
|
1602
1881
|
* The real process behind the command line: the branded console, the environment the bin
|
|
1603
1882
|
* started with, Bun's spawn and resolver, the signals, `fs.watch`, the asset scanner with the
|
|
1604
|
-
* string tools, and
|
|
1883
|
+
* string tools, native, and the visual test runner, loaded only by `moku-game visual`.
|
|
1605
1884
|
*
|
|
1606
1885
|
* @returns The seams of this process.
|
|
1607
1886
|
*/
|
|
@@ -1634,13 +1913,14 @@ function processDeps() {
|
|
|
1634
1913
|
}, ui),
|
|
1635
1914
|
native: runNative,
|
|
1636
1915
|
resolve: (specifier, from) => Bun.resolveSync(specifier, from),
|
|
1637
|
-
loadPage: (file) => import(file)
|
|
1916
|
+
loadPage: (file) => import(file),
|
|
1917
|
+
visual: () => import("./visual.mjs")
|
|
1638
1918
|
};
|
|
1639
1919
|
}
|
|
1640
1920
|
/**
|
|
1641
1921
|
* 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.
|
|
1922
|
+
* `pack`, `visual` or `help`, with `--root`, `--preload` and `--serve-plugin` before or after the
|
|
1923
|
+
* command word. Every line goes through the branded console; nothing exits the process.
|
|
1644
1924
|
*
|
|
1645
1925
|
* @param argv - The arguments after the bin.
|
|
1646
1926
|
* @returns The exit code.
|
|
@@ -1648,6 +1928,7 @@ function processDeps() {
|
|
|
1648
1928
|
* ```ts
|
|
1649
1929
|
* // bin/moku-game.mjs in a game folder:
|
|
1650
1930
|
* await runCli(["dev", "--port", "0"]); // 0 after Ctrl+C; stdout had "http://localhost:<port>/"
|
|
1931
|
+
* await runCli(["visual", "--no-pixels"]); // 0 when every checkpoint of tests/visual/index.ts is the same
|
|
1651
1932
|
* await runCli(["help"]); // 0
|
|
1652
1933
|
* ```
|
|
1653
1934
|
*/
|
|
@@ -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
|