@typeonce/ritmo-devtools 0.6.0 → 0.7.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 +105 -21
- package/dist/Determinism.d.ts +70 -0
- package/dist/Determinism.d.ts.map +1 -0
- package/dist/Determinism.js +137 -0
- package/dist/Determinism.js.map +1 -0
- package/dist/Format.d.ts +6 -1
- package/dist/Format.d.ts.map +1 -1
- package/dist/Format.js +24 -0
- package/dist/Format.js.map +1 -1
- package/dist/Invariant.d.ts +34 -12
- package/dist/Invariant.d.ts.map +1 -1
- package/dist/Invariant.js +6 -19
- package/dist/Invariant.js.map +1 -1
- package/dist/Lint.d.ts +60 -0
- package/dist/Lint.d.ts.map +1 -0
- package/dist/Lint.js +15 -0
- package/dist/Lint.js.map +1 -0
- package/dist/Monitor.d.ts +18 -4
- package/dist/Monitor.d.ts.map +1 -1
- package/dist/Monitor.js +31 -20
- package/dist/Monitor.js.map +1 -1
- package/dist/Replay.d.ts +271 -0
- package/dist/Replay.d.ts.map +1 -0
- package/dist/Replay.js +335 -0
- package/dist/Replay.js.map +1 -0
- package/dist/ReplayNodeSink.d.ts +11 -0
- package/dist/ReplayNodeSink.d.ts.map +1 -0
- package/dist/ReplayNodeSink.js +29 -0
- package/dist/ReplayNodeSink.js.map +1 -0
- package/dist/ReplayVitePlugin.d.ts +41 -0
- package/dist/ReplayVitePlugin.d.ts.map +1 -0
- package/dist/ReplayVitePlugin.js +41 -0
- package/dist/ReplayVitePlugin.js.map +1 -0
- package/dist/ReplayViteSink.d.ts +30 -0
- package/dist/ReplayViteSink.d.ts.map +1 -0
- package/dist/ReplayViteSink.js +9 -0
- package/dist/ReplayViteSink.js.map +1 -0
- package/dist/Session.d.ts +66 -14
- package/dist/Session.d.ts.map +1 -1
- package/dist/Session.js +92 -6
- package/dist/Session.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +57 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/monitor.d.ts +4 -2
- package/dist/internal/monitor.d.ts.map +1 -1
- package/dist/internal/monitor.js +9 -3
- package/dist/internal/monitor.js.map +1 -1
- package/dist/internal/version.d.ts +7 -0
- package/dist/internal/version.d.ts.map +1 -0
- package/dist/internal/version.js +7 -0
- package/dist/internal/version.js.map +1 -0
- package/package.json +5 -2
- package/src/Determinism.ts +190 -0
- package/src/Format.ts +25 -0
- package/src/Invariant.ts +38 -12
- package/src/Lint.ts +72 -0
- package/src/Monitor.ts +48 -25
- package/src/Replay.ts +583 -0
- package/src/ReplayNodeSink.ts +38 -0
- package/src/ReplayVitePlugin.ts +74 -0
- package/src/ReplayViteSink.ts +33 -0
- package/src/Session.ts +153 -23
- package/src/cli.ts +55 -0
- package/src/index.ts +3 -0
- package/src/internal/monitor.ts +12 -5
- package/src/internal/version.ts +6 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type * as Replay from "./Replay.ts";
|
|
2
|
+
/** Where the last replay was written, and every path written so far. */
|
|
3
|
+
export interface NodeSink extends Replay.Sink {
|
|
4
|
+
readonly written: ReadonlyArray<string>;
|
|
5
|
+
}
|
|
6
|
+
/** The file name of a replay: its save time, then its tick count. */
|
|
7
|
+
export declare const fileName: (file: Replay.ReplayFile) => string;
|
|
8
|
+
export declare const make: (options: {
|
|
9
|
+
readonly directory: string;
|
|
10
|
+
}) => NodeSink;
|
|
11
|
+
//# sourceMappingURL=ReplayNodeSink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayNodeSink.d.ts","sourceRoot":"","sources":["../src/ReplayNodeSink.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAA;AAE1C,wEAAwE;AACxE,MAAM,WAAW,QAAS,SAAQ,MAAM,CAAC,IAAI;IAC3C,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACxC;AAED,qEAAqE;AACrE,eAAO,MAAM,QAAQ,SAAU,MAAM,CAAC,UAAU,KAAG,MACqE,CAAA;AAExH,eAAO,MAAM,IAAI,YAAa;IAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAG,QAW9D,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Replay.Sink` for Node: each saved replay becomes a JSON file in a
|
|
3
|
+
* directory. For headless runs (bots, scripts) that record replays.
|
|
4
|
+
*
|
|
5
|
+
* @module ReplayNodeSink
|
|
6
|
+
* @docGroup devtools
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const recorder = Replay.recorder(runtime, { ..., sink: ReplayNodeSink.make({ directory: ".replays" }) })
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
/** The file name of a replay: its save time, then its tick count. */
|
|
16
|
+
export const fileName = (file) => `${new Date(file.savedAt).toISOString().replaceAll(":", "-")}-tick-${file.ticks.at(-1)?.tick ?? file.start.tick}.json`;
|
|
17
|
+
export const make = (options) => {
|
|
18
|
+
const written = [];
|
|
19
|
+
return {
|
|
20
|
+
written,
|
|
21
|
+
save: (file) => {
|
|
22
|
+
mkdirSync(options.directory, { recursive: true });
|
|
23
|
+
const path = join(options.directory, fileName(file));
|
|
24
|
+
writeFileSync(path, `${JSON.stringify(file)}\n`);
|
|
25
|
+
written.push(path);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=ReplayNodeSink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayNodeSink.js","sourceRoot":"","sources":["../src/ReplayNodeSink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAShC,qEAAqE;AACrE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAuB,EAAU,EAAE,CAC1D,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,CAAA;AAExH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,OAAuC,EAAY,EAAE;IACxE,MAAM,OAAO,GAAkB,EAAE,CAAA;IACjC,OAAO;QACL,OAAO;QACP,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;YACb,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACjD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;YACpD,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Vite plugin (Node side) that receives the replays `ReplayViteSink`
|
|
3
|
+
* posts from the page and writes them into the game's repository as
|
|
4
|
+
* `.replays/<time>.json`, logging the path, the tick count, and the reason.
|
|
5
|
+
* Development only: it does nothing in a build.
|
|
6
|
+
*
|
|
7
|
+
* Add `.replays/` to the game's `.gitignore`.
|
|
8
|
+
*
|
|
9
|
+
* @module ReplayVitePlugin
|
|
10
|
+
* @docGroup devtools
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // vite.config.ts
|
|
15
|
+
* import * as ReplayVitePlugin from "@typeonce/ritmo-devtools/ReplayVitePlugin"
|
|
16
|
+
* export default defineConfig({ plugins: [ReplayVitePlugin.plugin()] })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
20
|
+
/** The part of Vite's dev server the plugin uses. */
|
|
21
|
+
export interface DevServer {
|
|
22
|
+
readonly middlewares: {
|
|
23
|
+
readonly use: (path: string, handler: (request: IncomingMessage, response: ServerResponse, next: () => void) => void) => void;
|
|
24
|
+
};
|
|
25
|
+
readonly config: {
|
|
26
|
+
readonly root: string;
|
|
27
|
+
readonly logger: {
|
|
28
|
+
readonly info: (message: string) => void;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** A Vite plugin object. */
|
|
33
|
+
export interface Plugin {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly apply: "serve";
|
|
36
|
+
readonly configureServer: (server: DevServer) => void;
|
|
37
|
+
}
|
|
38
|
+
export declare const plugin: (options?: {
|
|
39
|
+
readonly directory?: string;
|
|
40
|
+
}) => Plugin;
|
|
41
|
+
//# sourceMappingURL=ReplayVitePlugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayVitePlugin.d.ts","sourceRoot":"","sources":["../src/ReplayVitePlugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAMhE,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,CAAA;KAC9H,CAAA;IACD,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;SAAE,CAAA;KAAE,CAAA;CAC1G;AAED,4BAA4B;AAC5B,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACtD;AAED,eAAO,MAAM,MAAM,aAAa;IAAE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAAQ,MAkCrE,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as Replay from "./Replay.js";
|
|
2
|
+
import * as ReplayNodeSink from "./ReplayNodeSink.js";
|
|
3
|
+
import { endpoint } from "./ReplayViteSink.js";
|
|
4
|
+
export const plugin = (options = {}) => ({
|
|
5
|
+
name: "ritmo-replays",
|
|
6
|
+
apply: "serve",
|
|
7
|
+
configureServer: (server) => {
|
|
8
|
+
const sink = ReplayNodeSink.make({ directory: `${server.config.root}/${options.directory ?? ".replays"}` });
|
|
9
|
+
server.middlewares.use(endpoint, (request, response, next) => {
|
|
10
|
+
if (request.method !== "POST")
|
|
11
|
+
return next();
|
|
12
|
+
let body = "";
|
|
13
|
+
request.setEncoding("utf8");
|
|
14
|
+
request.on("data", (chunk) => {
|
|
15
|
+
body += chunk;
|
|
16
|
+
});
|
|
17
|
+
request.on("end", () => {
|
|
18
|
+
let data;
|
|
19
|
+
try {
|
|
20
|
+
data = JSON.parse(body);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
data = undefined;
|
|
24
|
+
}
|
|
25
|
+
const parsed = Replay.parse(data);
|
|
26
|
+
if (!parsed.ok) {
|
|
27
|
+
response.statusCode = 400;
|
|
28
|
+
response.end(parsed.error._tag === "InvalidReplay" ? parsed.error.message : parsed.error._tag);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
sink.save(parsed.value);
|
|
32
|
+
const path = sink.written.at(-1).slice(server.config.root.length + 1);
|
|
33
|
+
const ticks = parsed.value.ticks.length;
|
|
34
|
+
server.config.logger.info(`[ritmo] replay saved: ${path} (${ticks} ticks): ${parsed.value.reason}`);
|
|
35
|
+
response.statusCode = 201;
|
|
36
|
+
response.end(path);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=ReplayVitePlugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayVitePlugin.js","sourceRoot":"","sources":["../src/ReplayVitePlugin.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAiB9C,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAO,GAAoC,EAAE,EAAU,EAAE,CAAC,CAAC;IAChF,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,OAAO;IACd,eAAe,EAAE,CAAC,MAAM,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,SAAS,IAAI,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3G,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC3D,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;gBAAE,OAAO,IAAI,EAAE,CAAA;YAC5C,IAAI,IAAI,GAAG,EAAE,CAAA;YACb,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YAC3B,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;gBACnC,IAAI,IAAI,KAAK,CAAA;YACf,CAAC,CAAC,CAAA;YACF,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACrB,IAAI,IAAa,CAAA;gBACjB,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,GAAG,SAAS,CAAA;gBAClB,CAAC;gBACD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACjC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;oBACf,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAA;oBACzB,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;oBAC9F,OAAM;gBACR,CAAC;gBACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;gBACvB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;gBACtE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;gBACvC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,IAAI,KAAK,KAAK,YAAY,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;gBACnG,QAAQ,CAAC,UAAU,GAAG,GAAG,CAAA;gBACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YACpB,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `Replay.Sink` for a page served by Vite in development: each saved
|
|
3
|
+
* replay is posted to the dev server, where `ReplayVitePlugin` writes it
|
|
4
|
+
* into the game's repository (`.replays/`). The page's `fetch` is passed in.
|
|
5
|
+
*
|
|
6
|
+
* @module ReplayViteSink
|
|
7
|
+
* @docGroup devtools
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* if (import.meta.env.DEV) {
|
|
12
|
+
* const sink = ReplayViteSink.make({ fetch: (url, init) => fetch(url, init) })
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import type * as Replay from "./Replay.ts";
|
|
17
|
+
/** The dev server's endpoint for replays (what `ReplayVitePlugin` serves). */
|
|
18
|
+
export declare const endpoint = "/__ritmo/replays";
|
|
19
|
+
export interface Options {
|
|
20
|
+
/** The page's `fetch` (or any function with its shape for these two arguments). */
|
|
21
|
+
readonly fetch: (url: string, init: {
|
|
22
|
+
readonly method: "POST";
|
|
23
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
24
|
+
readonly body: string;
|
|
25
|
+
}) => Promise<unknown>;
|
|
26
|
+
/** Called when a post fails (the dev server is down, the plugin is not installed). */
|
|
27
|
+
readonly onError?: (error: unknown) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare const make: (options: Options) => Replay.Sink;
|
|
30
|
+
//# sourceMappingURL=ReplayViteSink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayViteSink.d.ts","sourceRoot":"","sources":["../src/ReplayViteSink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAA;AAE1C,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,qBAAqB,CAAA;AAE1C,MAAM,WAAW,OAAO;IACtB,mFAAmF;IACnF,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/J,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AAED,eAAO,MAAM,IAAI,YAAa,OAAO,KAAG,MAAM,CAAC,IAK7C,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The dev server's endpoint for replays (what `ReplayVitePlugin` serves). */
|
|
2
|
+
export const endpoint = "/__ritmo/replays";
|
|
3
|
+
export const make = (options) => ({
|
|
4
|
+
save: (file) => {
|
|
5
|
+
void options.fetch(endpoint, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(file) })
|
|
6
|
+
.catch((error) => options.onError?.(error));
|
|
7
|
+
}
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=ReplayViteSink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReplayViteSink.js","sourceRoot":"","sources":["../src/ReplayViteSink.ts"],"names":[],"mappings":"AAiBA,8EAA8E;AAC9E,MAAM,CAAC,MAAM,QAAQ,GAAG,kBAAkB,CAAA;AAS1C,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,OAAgB,EAAe,EAAE,CAAC,CAAC;IACtD,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;QACb,KAAK,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;aAC1H,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IACxD,CAAC;CACF,CAAC,CAAA"}
|
package/dist/Session.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A debug session around a runtime made with `debug:
|
|
2
|
+
* A debug session around a runtime made with `debug: { hostClock }`.
|
|
3
3
|
*
|
|
4
4
|
* The session names the schedules it drives, keeps a bounded history of
|
|
5
5
|
* trace events, and answers questions about it as text and data:
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* at the first system failure, thrown defect, or violated invariant.
|
|
9
9
|
* - `journal(filter)` lists what happened, one fact per line, filtered by
|
|
10
10
|
* frames, entity, component, resource, event, machine, system, or kind.
|
|
11
|
-
* - `why(entity, component)` lists the latest changes to one component
|
|
11
|
+
* - `why(entity, component)` lists the latest changes to one component, and
|
|
12
|
+
* the systems whose declared `spawns` could have spawned the entity.
|
|
12
13
|
* - `system(name)` shows one system's declared access and recent activity.
|
|
13
14
|
* - `describe()`, `dump(filter)`, `streams()` render the core debug handle.
|
|
14
15
|
* - `report()` summarizes per-system timing and warnings accumulated while
|
|
@@ -34,25 +35,44 @@
|
|
|
34
35
|
* console.log(session.journal({ frames: [180, 185], entity: 1 }))
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
38
|
+
import { HostClock } from "@typeonce/ritmo";
|
|
37
39
|
import type * as Debug from "@typeonce/ritmo/Debug";
|
|
40
|
+
import type * as Inspector from "@typeonce/ritmo/Inspector";
|
|
38
41
|
import type * as Machine from "@typeonce/ritmo/Machine";
|
|
39
42
|
import type * as Result from "@typeonce/ritmo/Result";
|
|
40
|
-
import type
|
|
43
|
+
import type * as Runtime from "@typeonce/ritmo/Runtime";
|
|
44
|
+
import type { AnyScheduleOrder, ExecutableScheduleDefinition } from "@typeonce/ritmo/Schedule";
|
|
41
45
|
import type { Schema } from "@typeonce/ritmo/Schema";
|
|
42
46
|
import * as Format from "./Format.ts";
|
|
43
47
|
import type { Invariant } from "./Invariant.ts";
|
|
44
48
|
import * as Rendered from "./Rendered.ts";
|
|
45
49
|
/**
|
|
46
|
-
* What a session needs from a runtime: its debug handle
|
|
47
|
-
* `tryTick`, which reports missing requirements as data
|
|
50
|
+
* What a session needs from a runtime: its debug handle, the dynamic
|
|
51
|
+
* `tryTick`, which reports missing requirements as data, `inspect` for
|
|
52
|
+
* invariants, and what it was provided with, which invariants are checked
|
|
53
|
+
* against.
|
|
48
54
|
*/
|
|
49
|
-
export interface Target<S extends Schema.Any, Root> {
|
|
55
|
+
export interface Target<S extends Schema.Any, Root, Services extends Record<string, unknown> = Record<string, unknown>, Resources extends object = object, Machines extends Record<string, unknown> = Record<string, unknown>> {
|
|
50
56
|
readonly debug: Debug.Handle<S, Root>;
|
|
51
|
-
readonly tryTick: (schedule: ExecutableScheduleDefinition<S, any, any, any, any>) => Result.Result<void, unknown>;
|
|
57
|
+
readonly tryTick: (schedule: ExecutableScheduleDefinition<S, any, any, any, any, AnyScheduleOrder>) => Result.Result<void, unknown>;
|
|
58
|
+
readonly inspect: (inspector: never) => unknown;
|
|
59
|
+
readonly services: Services;
|
|
60
|
+
readonly resourceValues: Resources;
|
|
61
|
+
readonly machineValues: Machines;
|
|
52
62
|
}
|
|
53
|
-
|
|
63
|
+
type InvariantRequirementErrors<Invariants extends ReadonlyArray<Invariant<Invariant.CheckInspector>>, Services extends Record<string, unknown>, Resources extends object, Machines extends Record<string, unknown>> = Runtime.ValidateInspector<Invariants[number]["inspector"], Services, Resources, Machines> extends {
|
|
64
|
+
readonly __fixRuntimeRequirements__: infer Errors;
|
|
65
|
+
} ? Errors : never;
|
|
66
|
+
/**
|
|
67
|
+
* Rejects invariants whose inspectors read a resource, service, or machine
|
|
68
|
+
* the runtime does not provide, naming each one.
|
|
69
|
+
*/
|
|
70
|
+
export type ValidateInvariants<Invariants extends ReadonlyArray<Invariant<Invariant.CheckInspector>>, Services extends Record<string, unknown>, Resources extends object, Machines extends Record<string, unknown>> = [InvariantRequirementErrors<Invariants, Services, Resources, Machines>] extends [never] ? unknown : {
|
|
71
|
+
readonly __fixInvariantRequirements__: InvariantRequirementErrors<Invariants, Services, Resources, Machines>;
|
|
72
|
+
};
|
|
73
|
+
export interface Options<S extends Schema.Any, Root, Names extends string, Invariants extends ReadonlyArray<Invariant<Invariant.CheckInspector>> = readonly []> {
|
|
54
74
|
/** The schedules `run` can drive, by name. Also names them in traces. */
|
|
55
|
-
readonly schedules: Readonly<Record<Names, ExecutableScheduleDefinition<S, any, Root, any, any>>>;
|
|
75
|
+
readonly schedules: Readonly<Record<Names, ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>>;
|
|
56
76
|
/**
|
|
57
77
|
* More schedules to include in `describe()`, its lints, and the access
|
|
58
78
|
* maps, without making them runnable: typically a render schedule that
|
|
@@ -60,11 +80,22 @@ export interface Options<S extends Schema.Any, Root, Names extends string> {
|
|
|
60
80
|
* and components only those schedules read are reported as never read.
|
|
61
81
|
* Their services show as not provided, which is not a lint.
|
|
62
82
|
*/
|
|
63
|
-
readonly describe?: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any>>>;
|
|
64
|
-
/**
|
|
65
|
-
readonly
|
|
83
|
+
readonly describe?: Readonly<Record<string, ExecutableScheduleDefinition<S, any, Root, any, any, AnyScheduleOrder>>>;
|
|
84
|
+
/** Events the host publishes with `runtime.send`: written, for the lints (see `Debug.Handle.declareHostEvents`). */
|
|
85
|
+
readonly hostEvents?: ReadonlyArray<Schema.EventDescriptor<S>>;
|
|
86
|
+
/**
|
|
87
|
+
* Checked after every frame of every run, in order; the first violation
|
|
88
|
+
* stops the run. Built with `Invariant.fromInspector(Game.Inspector(...))`.
|
|
89
|
+
*/
|
|
90
|
+
readonly invariants?: Invariants;
|
|
66
91
|
/** Frames of trace history kept for `journal` and `why`. Defaults to 600. */
|
|
67
92
|
readonly history?: number;
|
|
93
|
+
/**
|
|
94
|
+
* Times runs (`RunResult.ms`). `HostClock.test()` makes every timing 0,
|
|
95
|
+
* so text output is identical across runs (give the runtime's
|
|
96
|
+
* `debug: { hostClock }` a test clock too).
|
|
97
|
+
*/
|
|
98
|
+
readonly hostClock: HostClock.HostClock;
|
|
68
99
|
/** Value formatting for all text output. */
|
|
69
100
|
readonly format?: Format.ValueOptions;
|
|
70
101
|
}
|
|
@@ -89,6 +120,12 @@ export type Stop = {
|
|
|
89
120
|
readonly reason: "missingRequirements";
|
|
90
121
|
readonly frame: number;
|
|
91
122
|
readonly requirements: unknown;
|
|
123
|
+
}
|
|
124
|
+
/** A `Game.System.thisTick` read whose writer runs after it, or that has none; nothing ran. */
|
|
125
|
+
| {
|
|
126
|
+
readonly reason: "tickOrder";
|
|
127
|
+
readonly frame: number;
|
|
128
|
+
readonly issues: ReadonlyArray<string>;
|
|
92
129
|
} | {
|
|
93
130
|
readonly reason: "defect";
|
|
94
131
|
readonly frame: number;
|
|
@@ -98,6 +135,20 @@ export type Stop = {
|
|
|
98
135
|
readonly frame: number;
|
|
99
136
|
readonly invariant: string;
|
|
100
137
|
readonly message: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A failed tick result nobody read (for example a setup tick the game ran
|
|
141
|
+
* before this run): `frame` is the failed tick's, `skipped` the systems
|
|
142
|
+
* that did not run then.
|
|
143
|
+
*/
|
|
144
|
+
| {
|
|
145
|
+
readonly reason: "ignored-failure";
|
|
146
|
+
readonly frame: number;
|
|
147
|
+
readonly schedule: string;
|
|
148
|
+
readonly system: string | undefined;
|
|
149
|
+
readonly error: unknown;
|
|
150
|
+
readonly skipped: ReadonlyArray<string>;
|
|
151
|
+
readonly when: "next-tick" | "session-end";
|
|
101
152
|
};
|
|
102
153
|
export interface RunResult {
|
|
103
154
|
readonly schedule: string;
|
|
@@ -163,7 +214,7 @@ export interface SystemStats {
|
|
|
163
214
|
readonly maxMs: number;
|
|
164
215
|
}
|
|
165
216
|
export interface Warning {
|
|
166
|
-
readonly code: "missed-read" | "pending-commands" | "pending-next-state" | "transition-failed" | "discarded-messages" | "sent-event-unread" | "system-failed" | "non-finite-value" | "population-growing";
|
|
217
|
+
readonly code: "missed-read" | "pending-commands" | "pending-next-state" | "transition-failed" | "discarded-messages" | "sent-event-unread" | "system-failed" | "non-finite-value" | "population-growing" | "ignored-failure";
|
|
167
218
|
readonly message: string;
|
|
168
219
|
readonly count: number;
|
|
169
220
|
readonly firstFrame: number;
|
|
@@ -208,5 +259,6 @@ export interface Session<S extends Schema.Any, Names extends string> {
|
|
|
208
259
|
/** Stops tracing. Queries keep working on the history recorded so far. */
|
|
209
260
|
readonly close: () => void;
|
|
210
261
|
}
|
|
211
|
-
export declare const make: <S extends Schema.Any, Root, const Names extends string>(runtime: Target<S, Root>, options: Options<S, Root, Names>) => Session<S, Names>;
|
|
262
|
+
export declare const make: <S extends Schema.Any, Root, Services extends Record<string, unknown>, Resources extends object, Machines extends Record<string, unknown>, const Names extends string, const Invariants extends ReadonlyArray<Invariant<Inspector.InspectorDefinition<any, string | undefined, Root, any, any>>> = readonly []>(runtime: Target<S, Root, Services, Resources, Machines>, options: Options<S, Root, Names, Invariants> & ValidateInvariants<Invariants, Services, Resources, Machines>) => Session<S, Names>;
|
|
263
|
+
export {};
|
|
212
264
|
//# sourceMappingURL=Session.d.ts.map
|
package/dist/Session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Session.d.ts","sourceRoot":"","sources":["../src/Session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Session.d.ts","sourceRoot":"","sources":["../src/Session.ts"],"names":[],"mappings":"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"}
|
package/dist/Session.js
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A debug session around a runtime made with `debug: { hostClock }`.
|
|
3
|
+
*
|
|
4
|
+
* The session names the schedules it drives, keeps a bounded history of
|
|
5
|
+
* trace events, and answers questions about it as text and data:
|
|
6
|
+
*
|
|
7
|
+
* - `run(name, { frames, until })` ticks a schedule frame by frame and stops
|
|
8
|
+
* at the first system failure, thrown defect, or violated invariant.
|
|
9
|
+
* - `journal(filter)` lists what happened, one fact per line, filtered by
|
|
10
|
+
* frames, entity, component, resource, event, machine, system, or kind.
|
|
11
|
+
* - `why(entity, component)` lists the latest changes to one component, and
|
|
12
|
+
* the systems whose declared `spawns` could have spawned the entity.
|
|
13
|
+
* - `system(name)` shows one system's declared access and recent activity.
|
|
14
|
+
* - `describe()`, `dump(filter)`, `streams()` render the core debug handle.
|
|
15
|
+
* - `report()` summarizes per-system timing and warnings accumulated while
|
|
16
|
+
* tracing: missed reads, commands still pending at the end of a frame,
|
|
17
|
+
* failed transitions, skipped systems that discarded messages, NaN or
|
|
18
|
+
* infinite numbers written anywhere, and entity counts that keep growing
|
|
19
|
+
* through a run.
|
|
20
|
+
*
|
|
21
|
+
* Everything returns a `Rendered` value that prints as text.
|
|
22
|
+
*
|
|
23
|
+
* @module Session
|
|
24
|
+
* @docGroup devtools
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const session = Session.make(runtime, {
|
|
29
|
+
* schedules: { setup: setupSchedule, update: updateSchedule },
|
|
30
|
+
* invariants: [PlayerInsideWorld]
|
|
31
|
+
* })
|
|
32
|
+
* session.run("setup")
|
|
33
|
+
* console.log(session.run("update", { frames: 300 }))
|
|
34
|
+
* console.log(session.why(1, Position))
|
|
35
|
+
* console.log(session.journal({ frames: [180, 185], entity: 1 }))
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
import { HostClock } from "@typeonce/ritmo";
|
|
1
39
|
import * as Format from "./Format.js";
|
|
2
40
|
import * as Rendered from "./Rendered.js";
|
|
3
41
|
import { nonFinitePath } from "./internal/values.js";
|
|
@@ -25,6 +63,7 @@ const growsSteadily = (samples) => {
|
|
|
25
63
|
};
|
|
26
64
|
const describeError = (error) => typeof error === "object" && error !== null ? error : {};
|
|
27
65
|
export const make = (runtime, options) => {
|
|
66
|
+
const inspect = runtime.inspect;
|
|
28
67
|
const debug = runtime.debug;
|
|
29
68
|
const capacity = options.history ?? 600;
|
|
30
69
|
const format = options.format ?? {};
|
|
@@ -35,6 +74,7 @@ export const make = (runtime, options) => {
|
|
|
35
74
|
throw new Error(`Session: "${name}" is both a runnable and a described-only schedule`);
|
|
36
75
|
}
|
|
37
76
|
debug.nameSchedules({ ...described, ...schedules });
|
|
77
|
+
debug.declareHostEvents(options.hostEvents ?? []);
|
|
38
78
|
const history = [];
|
|
39
79
|
let current = { frame: debug.frame(), events: [] };
|
|
40
80
|
history.push(current);
|
|
@@ -158,6 +198,10 @@ export const make = (runtime, options) => {
|
|
|
158
198
|
}
|
|
159
199
|
}
|
|
160
200
|
break;
|
|
201
|
+
case "failure.ignored":
|
|
202
|
+
ignored = event;
|
|
203
|
+
warn("ignored-failure", `ignored:${event.frame}`, Format.ignoredFailure(event, format), event.frame);
|
|
204
|
+
break;
|
|
161
205
|
case "transition":
|
|
162
206
|
if (event.outcome !== "failed")
|
|
163
207
|
pendingStates.delete(event.machine);
|
|
@@ -167,6 +211,13 @@ export const make = (runtime, options) => {
|
|
|
167
211
|
break;
|
|
168
212
|
}
|
|
169
213
|
};
|
|
214
|
+
/** The last `failure.ignored` event seen, for `run` to stop on. */
|
|
215
|
+
let ignored;
|
|
216
|
+
const takeIgnored = () => {
|
|
217
|
+
const found = ignored;
|
|
218
|
+
ignored = undefined;
|
|
219
|
+
return found;
|
|
220
|
+
};
|
|
170
221
|
const stopObserving = debug.observe(record);
|
|
171
222
|
const historyRange = () => {
|
|
172
223
|
const first = history.find((entry) => entry.events.length > 0);
|
|
@@ -175,7 +226,7 @@ export const make = (runtime, options) => {
|
|
|
175
226
|
const run = (name, runOptions = {}) => {
|
|
176
227
|
const schedule = schedules[name];
|
|
177
228
|
const frames = runOptions.frames ?? 1;
|
|
178
|
-
const started =
|
|
229
|
+
const started = options.hostClock.now();
|
|
179
230
|
const warningsBefore = new Map([...warnings].map(([key, warning]) => [key, warning.count]));
|
|
180
231
|
const initial = debug.population();
|
|
181
232
|
const entityCounts = [];
|
|
@@ -189,6 +240,7 @@ export const make = (runtime, options) => {
|
|
|
189
240
|
let first;
|
|
190
241
|
let last;
|
|
191
242
|
let stop = { reason: "completed" };
|
|
243
|
+
takeIgnored();
|
|
192
244
|
for (let index = 0; index < frames; index++) {
|
|
193
245
|
let result;
|
|
194
246
|
try {
|
|
@@ -205,17 +257,25 @@ export const make = (runtime, options) => {
|
|
|
205
257
|
first ??= frame;
|
|
206
258
|
last = frame;
|
|
207
259
|
sample();
|
|
260
|
+
const unread = takeIgnored();
|
|
261
|
+
if (unread !== undefined) {
|
|
262
|
+
const { type: _type, ...found } = unread;
|
|
263
|
+
stop = { reason: "ignored-failure", ...found };
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
208
266
|
if (!result.ok) {
|
|
209
267
|
const error = describeError(result.error);
|
|
210
268
|
stop = error.kind === "MissingRuntimeRequirements"
|
|
211
269
|
? { reason: "missingRequirements", frame, requirements: error.requirements }
|
|
212
|
-
:
|
|
270
|
+
: error.kind === "TickOrderViolation"
|
|
271
|
+
? { reason: "tickOrder", frame, issues: (error.issues ?? []).map((issue) => issue.message) }
|
|
272
|
+
: { reason: "failure", frame, system: typeof error.system === "string" ? error.system : "(unknown)", error: error.error };
|
|
213
273
|
break;
|
|
214
274
|
}
|
|
215
275
|
const violation = (options.invariants ?? []).reduce((found, invariant) => {
|
|
216
276
|
if (found)
|
|
217
277
|
return found;
|
|
218
|
-
const message = invariant.
|
|
278
|
+
const message = inspect(invariant.inspector);
|
|
219
279
|
return message === undefined ? undefined : { invariant: invariant.name, message };
|
|
220
280
|
}, undefined);
|
|
221
281
|
if (violation) {
|
|
@@ -254,7 +314,7 @@ export const make = (runtime, options) => {
|
|
|
254
314
|
frames: first === undefined || last === undefined ? 0 : last - first + 1,
|
|
255
315
|
range: first === undefined || last === undefined ? undefined : [first, last],
|
|
256
316
|
stop,
|
|
257
|
-
ms:
|
|
317
|
+
ms: HostClock.elapsed(started, options.hostClock.now()),
|
|
258
318
|
lintWarnings: lintWarnings.length,
|
|
259
319
|
warnings: raised,
|
|
260
320
|
entities: range(initial.entities, entityCounts),
|
|
@@ -319,11 +379,28 @@ export const make = (runtime, options) => {
|
|
|
319
379
|
: component.name in currentValue.components
|
|
320
380
|
? `e${entity} ${component.name} is now ${Format.value(currentValue.components[component.name], format)}`
|
|
321
381
|
: `e${entity} has no ${component.name} now`;
|
|
322
|
-
const
|
|
382
|
+
const changes = lines.length === 0
|
|
323
383
|
? `${now}; no change to it in ${rangeText()}`
|
|
324
384
|
: `${now}; last ${lines.length} changes (oldest first):`;
|
|
385
|
+
const spawners = spawnersOf(currentValue === undefined ? [component.name] : Object.keys(currentValue.components), component.name);
|
|
386
|
+
const header = `${changes}\n${spawners.length === 0
|
|
387
|
+
? `no described system declares spawns of ${component.name}`
|
|
388
|
+
: `could have spawned it: ${spawners.join(", ")}`}`;
|
|
325
389
|
return Rendered.make(lines, renderLines(header, lines));
|
|
326
390
|
};
|
|
391
|
+
/**
|
|
392
|
+
* The described systems whose declared `spawns` include `component` and
|
|
393
|
+
* cover every component the entity has that no described system may
|
|
394
|
+
* insert (so it can only have come from the spawn).
|
|
395
|
+
*/
|
|
396
|
+
const spawnersOf = (has, component) => {
|
|
397
|
+
const systems = debug.describe().systems;
|
|
398
|
+
const insertable = new Set(systems.flatMap((system) => system.commands.inserts));
|
|
399
|
+
const fromSpawn = has.filter((name) => !insertable.has(name));
|
|
400
|
+
return systems
|
|
401
|
+
.filter((system) => system.commands.spawns.includes(component) && fromSpawn.every((name) => system.commands.spawns.includes(name)))
|
|
402
|
+
.map((system) => system.name);
|
|
403
|
+
};
|
|
327
404
|
const whyResource = (resource, whyOptions = {}) => {
|
|
328
405
|
const lines = collectLines({ resource, kinds: ["resource"], limit: whyOptions.limit ?? 5 });
|
|
329
406
|
const resources = debug.dump({ limit: 0 }).resources;
|
|
@@ -359,6 +436,8 @@ export const make = (runtime, options) => {
|
|
|
359
436
|
return Rendered.make(data, lines.join("\n"));
|
|
360
437
|
};
|
|
361
438
|
const report = () => {
|
|
439
|
+
// The report is where a session ends: a failure still unread shows here.
|
|
440
|
+
debug.checkIgnoredFailures();
|
|
362
441
|
const systems = [...stats.keys()].map((name) => statsFor(name)).sort((left, right) => right.totalMs - left.totalMs);
|
|
363
442
|
const data = {
|
|
364
443
|
frames: debug.frame(),
|
|
@@ -406,7 +485,10 @@ export const make = (runtime, options) => {
|
|
|
406
485
|
},
|
|
407
486
|
report,
|
|
408
487
|
frame: () => debug.frame(),
|
|
409
|
-
close:
|
|
488
|
+
close: () => {
|
|
489
|
+
debug.checkIgnoredFailures();
|
|
490
|
+
stopObserving();
|
|
491
|
+
}
|
|
410
492
|
};
|
|
411
493
|
};
|
|
412
494
|
const renderRun = (result, format) => {
|
|
@@ -426,6 +508,10 @@ const renderRun = (result, format) => {
|
|
|
426
508
|
return `${head}, DEFECT at f${stop.frame}: ${stop.error instanceof Error ? stop.error.stack ?? stop.error.message : Format.value(stop.error, format)}`;
|
|
427
509
|
case "invariant":
|
|
428
510
|
return `${head}, INVARIANT "${stop.invariant}" violated at f${stop.frame}: ${stop.message}`;
|
|
511
|
+
case "tickOrder":
|
|
512
|
+
return `${head}, NOT RUN after f${stop.frame}: ${stop.issues.join("; ")}`;
|
|
513
|
+
case "ignored-failure":
|
|
514
|
+
return `${head}, STOPPED: ${Format.ignoredFailure({ type: "failure.ignored", ...stop }, format)}, at f${stop.frame}`;
|
|
429
515
|
}
|
|
430
516
|
};
|
|
431
517
|
//# sourceMappingURL=Session.js.map
|