@mantaq/inspect 0.3.0 → 0.5.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 +25 -12
- package/dist/index.d.mts +38 -5
- package/dist/index.mjs +98 -3
- package/dist/{vite-BBW0CmYr.mjs → inspect-B7ZQCNM2.mjs} +155 -289
- package/dist/{inspect-DvQtAcwv.d.mts → inspect-vSdt2qtL.d.mts} +3 -3
- package/dist/live-entry.mjs +84 -2
- package/dist/load-CWubcSZU.mjs +131 -0
- package/dist/record.d.mts +14 -0
- package/dist/record.mjs +154 -0
- package/dist/vite.d.mts +1 -1
- package/dist/vite.mjs +233 -1
- package/package.json +19 -12
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -152
- package/dist/live-embedded.d.mts +0 -1
- package/dist/live-embedded.mjs +0 -16
- package/dist/live-page-DEDl_Nio.mjs +0 -85
- package/dist/page-CK7-IsLJ.mjs +0 -41
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Either } from "@mantaq/utils";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { loadConfigFromFile, loadEnv, runnerImport } from "vite";
|
|
6
|
+
// src/load.ts
|
|
7
|
+
const ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
8
|
+
const ENTRY = "virtual:mantaq-inspect";
|
|
9
|
+
const RESOLVED = `\0${ENTRY}`;
|
|
10
|
+
/**
|
|
11
|
+
* A module that imports the machine file and the inspector side by side. Run
|
|
12
|
+
* through one module graph, both see the same `@mantaq/core` instance: core
|
|
13
|
+
* recognizes its state refs by a module-level registry, so a machine loaded
|
|
14
|
+
* through a bundler and inspected from plain Node would look stateless.
|
|
15
|
+
*/
|
|
16
|
+
function entrySource(file) {
|
|
17
|
+
const inspect = fileURLToPath(import.meta.resolve("@mantaq/inspect"));
|
|
18
|
+
return [
|
|
19
|
+
`import * as machines from ${JSON.stringify(file)};`,
|
|
20
|
+
`import { inspectModule } from ${JSON.stringify(inspect)};`,
|
|
21
|
+
`export function run(options) {`,
|
|
22
|
+
` return inspectModule(${JSON.stringify(file)}, { ...options, load: async () => machines });`,
|
|
23
|
+
`}`
|
|
24
|
+
].join("\n");
|
|
25
|
+
}
|
|
26
|
+
/** Vite plugin serving the entry for each `virtual:mantaq-inspect?file=<path>` id. */
|
|
27
|
+
function inspectEntry() {
|
|
28
|
+
return {
|
|
29
|
+
name: "mantaq-inspect-entry",
|
|
30
|
+
resolveId: (id) => id.startsWith(ENTRY) ? `\0${id}` : null,
|
|
31
|
+
load: (id) => {
|
|
32
|
+
if (!id.startsWith(RESOLVED)) return null;
|
|
33
|
+
const file = new URLSearchParams(id.slice(id.indexOf("?") + 1)).get("file");
|
|
34
|
+
return file === null ? null : entrySource(file);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function entryId(file) {
|
|
39
|
+
return `${ENTRY}?${new URLSearchParams({ file }).toString()}`;
|
|
40
|
+
}
|
|
41
|
+
/** The `run` export of an entry module, checked: module objects are untyped. */
|
|
42
|
+
function runOf(module) {
|
|
43
|
+
const run = typeof module === "object" && module !== null ? Reflect.get(module, "run") : void 0;
|
|
44
|
+
if (typeof run !== "function") return null;
|
|
45
|
+
return async (options) => Reflect.apply(run, void 0, [options]);
|
|
46
|
+
}
|
|
47
|
+
const ENV_ACCESS = [
|
|
48
|
+
"import",
|
|
49
|
+
"meta",
|
|
50
|
+
"env"
|
|
51
|
+
].join(".");
|
|
52
|
+
/**
|
|
53
|
+
* Replaces the env object with the project's env: `runnerImport` never reads
|
|
54
|
+
* env files, so `VITE_*` variables would otherwise be missing. A textual
|
|
55
|
+
* replace: it also rewrites matches in strings and comments, and a frozen
|
|
56
|
+
* snapshot replaces Vite's own `import.meta.env`, so assigning to it fails.
|
|
57
|
+
*/
|
|
58
|
+
function projectEnv(env, mode) {
|
|
59
|
+
const value = JSON.stringify({
|
|
60
|
+
...env,
|
|
61
|
+
BASE_URL: "/",
|
|
62
|
+
MODE: mode,
|
|
63
|
+
DEV: true,
|
|
64
|
+
PROD: false,
|
|
65
|
+
SSR: true
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
name: "mantaq-inspect-env",
|
|
69
|
+
enforce: "pre",
|
|
70
|
+
transform: (code, id) => id.includes("/node_modules/") || !code.includes(ENV_ACCESS) ? null : {
|
|
71
|
+
code: code.replaceAll(ENV_ACCESS, `(${value})`),
|
|
72
|
+
map: null
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The inline config for a load: empty without `--config`; otherwise the
|
|
78
|
+
* project's Vite config (plugins, aliases, define) plus its env files.
|
|
79
|
+
*/
|
|
80
|
+
async function inlineConfig({ config, mode }) {
|
|
81
|
+
const base = {
|
|
82
|
+
configFile: false,
|
|
83
|
+
logLevel: "silent"
|
|
84
|
+
};
|
|
85
|
+
if (config === void 0) return {
|
|
86
|
+
...base,
|
|
87
|
+
...mode === void 0 ? {} : { mode }
|
|
88
|
+
};
|
|
89
|
+
const chosen = mode ?? "development";
|
|
90
|
+
const user = (await loadConfigFromFile({
|
|
91
|
+
command: "serve",
|
|
92
|
+
mode: chosen
|
|
93
|
+
}, config))?.config ?? {};
|
|
94
|
+
const envDir = resolve(dirname(config), user.envDir === false ? "." : user.envDir ?? ".");
|
|
95
|
+
const env = user.envDir === false ? {} : loadEnv(chosen, envDir, user.envPrefix ?? "VITE_");
|
|
96
|
+
return {
|
|
97
|
+
...user,
|
|
98
|
+
...base,
|
|
99
|
+
mode: chosen,
|
|
100
|
+
plugins: [...user.plugins ?? [], projectEnv(env, chosen)]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Load `file` with Vite (TS, tsconfig paths, extensionless imports) and inspect its machines. */
|
|
104
|
+
async function inspectFile(file, options = {}, load = {}) {
|
|
105
|
+
if (load.config !== void 0 && !existsSync(load.config)) return {
|
|
106
|
+
machines: [],
|
|
107
|
+
warnings: [`${file}: Vite config not found: ${load.config}`],
|
|
108
|
+
failed: true
|
|
109
|
+
};
|
|
110
|
+
const loaded = await inlineConfig(load).then((inline) => runnerImport(entryId(file), {
|
|
111
|
+
...inline,
|
|
112
|
+
plugins: [...inline.plugins ?? [], inspectEntry()]
|
|
113
|
+
})).then((value) => Either.right(value), (error) => Either.left((error instanceof Error ? error.message : String(error)).replace(ANSI, "")));
|
|
114
|
+
if (Either.isLeft(loaded)) return {
|
|
115
|
+
machines: [],
|
|
116
|
+
warnings: [`${file}: ${loaded[0]}`],
|
|
117
|
+
failed: true
|
|
118
|
+
};
|
|
119
|
+
const { module } = loaded[1];
|
|
120
|
+
const run = runOf(module);
|
|
121
|
+
if (run === null) return {
|
|
122
|
+
machines: [],
|
|
123
|
+
warnings: [`${file}: could not load the inspector`],
|
|
124
|
+
failed: true
|
|
125
|
+
};
|
|
126
|
+
return {
|
|
127
|
+
...await run(options),
|
|
128
|
+
failed: false
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export { runOf as i, inspectEntry as n, inspectFile as r, entryId as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AnyActor } from "@mantaq/core";
|
|
2
|
+
type Made = AnyActor<unknown> | {
|
|
3
|
+
actor: AnyActor<unknown>;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Wrap a machine factory in a vitest test file so every passing test that
|
|
7
|
+
* builds an actor with it is recorded to `__runs__/<test file>.json` next to
|
|
8
|
+
* the test: the events it handled and how far its clock moved. Run the whole
|
|
9
|
+
* file to refresh it; with `CI` set a stale file fails the test instead.
|
|
10
|
+
* The factory may be called in the test or in `beforeEach`. A retried test is
|
|
11
|
+
* recorded once, from the attempt that passed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function recordRuns<F extends (...args: never[]) => Made>(factory: F): F;
|
|
14
|
+
export {};
|
package/dist/record.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { r as machineId } from "./inspect-B7ZQCNM2.mjs";
|
|
2
|
+
import { Either } from "@mantaq/utils";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
// \0rolldown/runtime.js
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __exportAll = (all, no_symbols) => {
|
|
11
|
+
let target = {};
|
|
12
|
+
for (var name in all) __defProp(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
21
|
+
key = keys[i];
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
23
|
+
get: ((k) => from[k]).bind(null, key),
|
|
24
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
30
|
+
// ../../node_modules/.pnpm/vite-plus@1.4.0_@types+node@26.2.0_@typescript+native-preview@7.0.0-dev.20260707.2_@vit_594a0913d1d55d70a26e6ccc5f2ebd7d/node_modules/vite-plus/dist/test/index.js
|
|
31
|
+
var test_exports = /* @__PURE__ */ __exportAll({});
|
|
32
|
+
import * as import_vitest from "vitest";
|
|
33
|
+
__reExport(test_exports, import_vitest);
|
|
34
|
+
// src/record.ts
|
|
35
|
+
/** Actors built during the running test attempt, flushed when it ends. */
|
|
36
|
+
let pending = [];
|
|
37
|
+
/** Runs recorded so far, per test file, per machine id. */
|
|
38
|
+
const files = /* @__PURE__ */ new Map();
|
|
39
|
+
function actorOf(made) {
|
|
40
|
+
return "actor" in made ? made.actor : made;
|
|
41
|
+
}
|
|
42
|
+
function isJson(value) {
|
|
43
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return true;
|
|
44
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
45
|
+
if (Array.isArray(value)) return value.every(isJson);
|
|
46
|
+
if (typeof value !== "object") return false;
|
|
47
|
+
const proto = Object.getPrototypeOf(value);
|
|
48
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
49
|
+
return Object.values(value).every((each) => each === void 0 || isJson(each));
|
|
50
|
+
}
|
|
51
|
+
function jsonPayload(payload) {
|
|
52
|
+
if (payload !== void 0 && !isJson(payload)) return Either.left("payload is not JSON");
|
|
53
|
+
const text = Either.from(() => JSON.stringify(payload ?? null));
|
|
54
|
+
if (Either.isLeft(text)) return Either.left("payload is not JSON");
|
|
55
|
+
return Either.right(JSON.parse(text[1]));
|
|
56
|
+
}
|
|
57
|
+
function textOf(runs) {
|
|
58
|
+
const body = [...runs].sort(([a], [b]) => a.localeCompare(b)).map(([id, list]) => [id, [...list].sort((a, b) => a.name.localeCompare(b.name))]);
|
|
59
|
+
return `${JSON.stringify(Object.fromEntries(body), null, 2)}\n`;
|
|
60
|
+
}
|
|
61
|
+
function sidecarOf(testPath) {
|
|
62
|
+
return join(dirname(testPath), "__runs__", `${basename(testPath).replace(/\.[cm]?[jt]sx?$/, "")}.json`);
|
|
63
|
+
}
|
|
64
|
+
function check(file, id, run) {
|
|
65
|
+
const committed = Either.from(() => JSON.parse(readFileSync(file, "utf8")));
|
|
66
|
+
const list = Either.isRight(committed) ? committed[1]?.[id] : void 0;
|
|
67
|
+
const found = Array.isArray(list) ? list.find((each) => each?.name === run.name) : void 0;
|
|
68
|
+
if (JSON.stringify(found) !== JSON.stringify(run)) throw new Error(`recordRuns: ${file} is out of date for "${run.name}". Run the tests locally without CI set and commit the result.`);
|
|
69
|
+
}
|
|
70
|
+
function flush(testPath, id, run, runs) {
|
|
71
|
+
const file = sidecarOf(testPath);
|
|
72
|
+
if (process.env["CI"]) return check(file, id, run);
|
|
73
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
74
|
+
writeFileSync(file, textOf(runs));
|
|
75
|
+
}
|
|
76
|
+
function finish(attempt, passed) {
|
|
77
|
+
if (!passed) return;
|
|
78
|
+
const counts = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const each of attempt) {
|
|
80
|
+
if (each.problem !== "") {
|
|
81
|
+
console.warn(`recordRuns: "${each.name}" not recorded, ${each.problem}`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const now = each.actor.clock.now();
|
|
85
|
+
if (now > each.seen) each.steps.push({
|
|
86
|
+
kind: "advance",
|
|
87
|
+
ms: now - each.seen
|
|
88
|
+
});
|
|
89
|
+
const count = (counts.get(each.name) ?? 0) + 1;
|
|
90
|
+
counts.set(each.name, count);
|
|
91
|
+
const name = count === 1 ? each.name : `${each.name} #${count}`;
|
|
92
|
+
const runs = files.get(each.testPath) ?? /* @__PURE__ */ new Map();
|
|
93
|
+
files.set(each.testPath, runs);
|
|
94
|
+
const list = runs.get(each.id) ?? [];
|
|
95
|
+
runs.set(each.id, list);
|
|
96
|
+
const run = {
|
|
97
|
+
name,
|
|
98
|
+
end: each.actor.state.name,
|
|
99
|
+
steps: each.steps
|
|
100
|
+
};
|
|
101
|
+
list.push(run);
|
|
102
|
+
flush(each.testPath, each.id, run, runs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
(0, test_exports.afterEach)(({ task }) => {
|
|
106
|
+
const attempt = pending;
|
|
107
|
+
pending = [];
|
|
108
|
+
finish(attempt, task.result?.state !== "fail");
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Wrap a machine factory in a vitest test file so every passing test that
|
|
112
|
+
* builds an actor with it is recorded to `__runs__/<test file>.json` next to
|
|
113
|
+
* the test: the events it handled and how far its clock moved. Run the whole
|
|
114
|
+
* file to refresh it; with `CI` set a stale file fails the test instead.
|
|
115
|
+
* The factory may be called in the test or in `beforeEach`. A retried test is
|
|
116
|
+
* recorded once, from the attempt that passed.
|
|
117
|
+
*/
|
|
118
|
+
function recordRuns(factory) {
|
|
119
|
+
const id = machineId(factory.name);
|
|
120
|
+
const wrapped = (...args) => {
|
|
121
|
+
const made = factory(...args);
|
|
122
|
+
const actor = actorOf(made);
|
|
123
|
+
const { testPath = "", currentTestName = "" } = test_exports.expect.getState();
|
|
124
|
+
const entry = {
|
|
125
|
+
actor,
|
|
126
|
+
id,
|
|
127
|
+
name: currentTestName,
|
|
128
|
+
testPath,
|
|
129
|
+
steps: [],
|
|
130
|
+
problem: "",
|
|
131
|
+
seen: actor.clock.now()
|
|
132
|
+
};
|
|
133
|
+
pending.push(entry);
|
|
134
|
+
actor.on("transition", { fn: ({ event }) => {
|
|
135
|
+
const now = actor.clock.now();
|
|
136
|
+
if (now > entry.seen) entry.steps.push({
|
|
137
|
+
kind: "advance",
|
|
138
|
+
ms: now - entry.seen
|
|
139
|
+
});
|
|
140
|
+
entry.seen = now;
|
|
141
|
+
const payload = jsonPayload(event.payload);
|
|
142
|
+
if (Either.isLeft(payload)) entry.problem = payload[0];
|
|
143
|
+
else entry.steps.push({
|
|
144
|
+
kind: "event",
|
|
145
|
+
event: event.type,
|
|
146
|
+
payload: payload[1]
|
|
147
|
+
});
|
|
148
|
+
} });
|
|
149
|
+
return made;
|
|
150
|
+
};
|
|
151
|
+
Object.defineProperty(wrapped, "name", { value: factory.name });
|
|
152
|
+
return wrapped;
|
|
153
|
+
}
|
|
154
|
+
export { recordRuns };
|
package/dist/vite.d.mts
CHANGED
package/dist/vite.mjs
CHANGED
|
@@ -1 +1,233 @@
|
|
|
1
|
-
|
|
1
|
+
import { a as vizDocument, i as projectRoot, o as analyzeModule, r as machineId } from "./inspect-B7ZQCNM2.mjs";
|
|
2
|
+
import { i as runOf, n as inspectEntry, r as inspectFile, t as entryId } from "./load-CWubcSZU.mjs";
|
|
3
|
+
import { UPDATE_EVENT, livePageHtml, serializeViz } from "@mantaq/viz";
|
|
4
|
+
import { Either } from "@mantaq/utils";
|
|
5
|
+
import { dirname, isAbsolute, posix, resolve } from "node:path";
|
|
6
|
+
import { existsSync, globSync, readFileSync } from "node:fs";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { searchForWorkspaceRoot } from "vite";
|
|
9
|
+
// src/vite.ts
|
|
10
|
+
/**
|
|
11
|
+
* Vite plugin: a live page of your machines on your own dev server.
|
|
12
|
+
*
|
|
13
|
+
* // vite.config.ts
|
|
14
|
+
* import { mantaqViz } from "@mantaq/inspect/vite";
|
|
15
|
+
* export default defineConfig({ plugins: [mantaqViz({ machines: "src/**\/*.machine.ts" })] });
|
|
16
|
+
*
|
|
17
|
+
* `vite build` with the plugin also writes the page as a static site
|
|
18
|
+
* (`index.html`, `viz.json`, a script) under `base`.
|
|
19
|
+
*
|
|
20
|
+
* Open `/__mantaq/` while the dev server runs. Machines load through your own
|
|
21
|
+
* Vite config (aliases, tsconfig paths), are inspected on the server, and the
|
|
22
|
+
* page redraws, keeping the walk, when you save a file.
|
|
23
|
+
*/
|
|
24
|
+
const STYLESHEETS = ["@mantaq/viz/viewer.css", "@mantaq/viz/page.css"];
|
|
25
|
+
const DEV_ITERATIONS = 500;
|
|
26
|
+
const TARGETS = "virtual:mantaq/targets";
|
|
27
|
+
const TARGETS_RESOLVED = `\0${TARGETS}`;
|
|
28
|
+
function createExpression(found, index) {
|
|
29
|
+
const callee = `m${index}[${JSON.stringify(found.name)}]`;
|
|
30
|
+
const call = found.call === "clock" ? `${callee}(VirtualClock())` : found.call === "options" ? `${callee}({ clock: VirtualClock() })` : `${callee}()`;
|
|
31
|
+
return found.unwrap === "actor" ? `${call}.actor` : call;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Source of `virtual:mantaq/targets`: one factory per exported actor factory,
|
|
35
|
+
* shaped by its type (`call`, `clock`, `options`). A bare actor export cannot
|
|
36
|
+
* be recreated, so it gets none. Like the server-side inspection, plain actors
|
|
37
|
+
* are skipped when a file has factories.
|
|
38
|
+
*/
|
|
39
|
+
function targetsSource(files) {
|
|
40
|
+
const core = fileURLToPath(import.meta.resolve("@mantaq/core"));
|
|
41
|
+
const imports = [];
|
|
42
|
+
const entries = [];
|
|
43
|
+
files.forEach((file, index) => {
|
|
44
|
+
const analysis = analyzeModule({
|
|
45
|
+
file,
|
|
46
|
+
root: projectRoot(file)
|
|
47
|
+
});
|
|
48
|
+
if (Either.isLeft(analysis)) return;
|
|
49
|
+
const factories = analysis[1].machines.filter((found) => found.call !== "instance");
|
|
50
|
+
if (factories.length === 0) return;
|
|
51
|
+
imports.push(`import * as m${index} from ${JSON.stringify(file)};`);
|
|
52
|
+
for (const found of factories) entries.push(` ${JSON.stringify(machineId(found.name))}: () => { const actor = ${createExpression(found, index)}; return { actor, clock: actor.clock }; },`);
|
|
53
|
+
});
|
|
54
|
+
return [
|
|
55
|
+
`import { VirtualClock } from ${JSON.stringify(core)};`,
|
|
56
|
+
...imports,
|
|
57
|
+
"export const targets = {",
|
|
58
|
+
...entries,
|
|
59
|
+
"};"
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
function machineFiles(root, patterns) {
|
|
63
|
+
const files = patterns.flatMap((pattern) => globSync(pattern, { cwd: root }));
|
|
64
|
+
return [...new Set(files.map((file) => isAbsolute(file) ? file : resolve(root, file)))].sort();
|
|
65
|
+
}
|
|
66
|
+
async function documentText(server, context) {
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const file of context.files) {
|
|
69
|
+
const run = runOf(await server.ssrLoadModule(entryId(file)));
|
|
70
|
+
results.push(run === null ? {
|
|
71
|
+
machines: [],
|
|
72
|
+
warnings: [`${file}: could not load the inspector`]
|
|
73
|
+
} : await run({ fuzz: context.fuzz }));
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
text: serializeViz(vizDocument(results.flatMap((result) => result.machines))),
|
|
77
|
+
warnings: results.flatMap((result) => result.warnings)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function stylesheets() {
|
|
81
|
+
return STYLESHEETS.map((id) => readFileSync(fileURLToPath(import.meta.resolve(id)), "utf8")).join("\n");
|
|
82
|
+
}
|
|
83
|
+
/** Build mode: write the page, `viz.json` and the viewer script under `base`. */
|
|
84
|
+
function siteBuild(context) {
|
|
85
|
+
let standalone = false;
|
|
86
|
+
return {
|
|
87
|
+
name: "mantaq-viz-build",
|
|
88
|
+
apply: "build",
|
|
89
|
+
config: (config) => {
|
|
90
|
+
const input = config.build?.rollupOptions?.input;
|
|
91
|
+
const root = resolve(config.root ?? process.cwd());
|
|
92
|
+
standalone = input === void 0 && !existsSync(resolve(root, "index.html"));
|
|
93
|
+
return standalone ? { build: { rollupOptions: { input: { "mantaq-live": context.live } } } } : {};
|
|
94
|
+
},
|
|
95
|
+
buildStart() {
|
|
96
|
+
if (!standalone) this.emitFile({
|
|
97
|
+
type: "chunk",
|
|
98
|
+
id: context.live,
|
|
99
|
+
name: "mantaq-live"
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
async generateBundle(_options, bundle) {
|
|
103
|
+
const results = [];
|
|
104
|
+
for (const file of machineFiles(context.root(), context.patterns)) {
|
|
105
|
+
const load = { config: context.project.configFile };
|
|
106
|
+
try {
|
|
107
|
+
results.push(await inspectFile(file, { fuzz: context.fuzz }, load));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.warn(`[mantaq-viz] ${file}: could not inspect (${String(error)})`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const warning of results.flatMap((result) => result.warnings)) this.warn(`[mantaq-viz] ${warning}`);
|
|
113
|
+
const text = serializeViz(vizDocument(results.flatMap((result) => result.machines)));
|
|
114
|
+
const dir = context.base.replace(/^\//, "");
|
|
115
|
+
const entry = Object.values(bundle).find((chunk) => chunk.type === "chunk" && chunk.isEntry && chunk.name === "mantaq-live");
|
|
116
|
+
if (entry === void 0) {
|
|
117
|
+
this.error("[mantaq-viz] the live page entry was not bundled");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.emitFile({
|
|
121
|
+
type: "asset",
|
|
122
|
+
fileName: `${dir}viz.json`,
|
|
123
|
+
source: text
|
|
124
|
+
});
|
|
125
|
+
this.emitFile({
|
|
126
|
+
type: "asset",
|
|
127
|
+
fileName: `${dir}index.html`,
|
|
128
|
+
source: livePageHtml({
|
|
129
|
+
title: "mantaq viz",
|
|
130
|
+
source: "viz.json",
|
|
131
|
+
entry: posix.relative(dir === "" ? "." : dir, entry.fileName),
|
|
132
|
+
css: stylesheets()
|
|
133
|
+
})
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function mantaqViz(options) {
|
|
139
|
+
const base = (options.base ?? "/__mantaq/").replace(/\/?$/, "/");
|
|
140
|
+
const patterns = Array.isArray(options.machines) ? options.machines : [options.machines];
|
|
141
|
+
const fuzz = options.fuzz ?? { iterations: DEV_ITERATIONS };
|
|
142
|
+
const live = fileURLToPath(import.meta.resolve(import.meta.url.endsWith(".ts") ? "./live-entry.ts" : "./live-entry.mjs"));
|
|
143
|
+
const viz = dirname(dirname(fileURLToPath(import.meta.resolve("@mantaq/viz/live"))));
|
|
144
|
+
let root = process.cwd();
|
|
145
|
+
const project = { configFile: void 0 };
|
|
146
|
+
const targets = {
|
|
147
|
+
name: "mantaq-viz-targets",
|
|
148
|
+
configResolved: (config) => {
|
|
149
|
+
root = config.root;
|
|
150
|
+
project.configFile = config.configFile;
|
|
151
|
+
},
|
|
152
|
+
resolveId: (id) => id === "virtual:mantaq/targets" ? TARGETS_RESOLVED : null,
|
|
153
|
+
load: (id) => id === "\0virtual:mantaq/targets" ? targetsSource(machineFiles(root, patterns)) : null
|
|
154
|
+
};
|
|
155
|
+
const plugin = {
|
|
156
|
+
name: "mantaq-viz",
|
|
157
|
+
apply: "serve",
|
|
158
|
+
config: (config) => ({ server: { fs: { allow: [
|
|
159
|
+
searchForWorkspaceRoot(config.root ?? process.cwd()),
|
|
160
|
+
dirname(dirname(live)),
|
|
161
|
+
viz
|
|
162
|
+
] } } }),
|
|
163
|
+
configureServer(server) {
|
|
164
|
+
let cached = null;
|
|
165
|
+
const build = async () => {
|
|
166
|
+
const { text, warnings } = await documentText(server, {
|
|
167
|
+
files: machineFiles(server.config.root, patterns),
|
|
168
|
+
fuzz
|
|
169
|
+
});
|
|
170
|
+
for (const warning of warnings) server.config.logger.warn(`[mantaq-viz] ${warning}`);
|
|
171
|
+
return text;
|
|
172
|
+
};
|
|
173
|
+
server.watcher.on("all", (_event, path) => {
|
|
174
|
+
if (!/\.[cm]?[jt]sx?$/.test(path)) return;
|
|
175
|
+
cached = null;
|
|
176
|
+
const generated = server.moduleGraph.getModuleById(TARGETS_RESOLVED);
|
|
177
|
+
if (generated !== void 0) server.moduleGraph.invalidateModule(generated);
|
|
178
|
+
server.environments.ssr.moduleGraph.invalidateAll();
|
|
179
|
+
server.ws.send({
|
|
180
|
+
type: "custom",
|
|
181
|
+
event: UPDATE_EVENT
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
server.middlewares.use((request, response, next) => {
|
|
185
|
+
const path = request.url?.split("?")[0];
|
|
186
|
+
if (path === `${base}viz.json`) {
|
|
187
|
+
cached ??= build();
|
|
188
|
+
cached.then((text) => {
|
|
189
|
+
response.setHeader("content-type", "application/json");
|
|
190
|
+
response.end(text);
|
|
191
|
+
}, (error) => {
|
|
192
|
+
cached = null;
|
|
193
|
+
response.statusCode = 500;
|
|
194
|
+
response.end(String(error));
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (path !== base && path !== base.slice(0, -1)) {
|
|
199
|
+
next();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const html = livePageHtml({
|
|
203
|
+
title: "mantaq viz",
|
|
204
|
+
source: `${base}viz.json`,
|
|
205
|
+
entry: `/@fs${live}`,
|
|
206
|
+
css: stylesheets()
|
|
207
|
+
});
|
|
208
|
+
server.transformIndexHtml(request.url ?? base, html).then((page) => {
|
|
209
|
+
response.setHeader("content-type", "text/html");
|
|
210
|
+
response.end(page);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
server.httpServer?.once("listening", () => {
|
|
214
|
+
server.config.logger.info(` mantaq viz: ${base}`);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const site = siteBuild({
|
|
219
|
+
base,
|
|
220
|
+
patterns,
|
|
221
|
+
fuzz,
|
|
222
|
+
live,
|
|
223
|
+
project,
|
|
224
|
+
root: () => root
|
|
225
|
+
});
|
|
226
|
+
return [
|
|
227
|
+
inspectEntry(),
|
|
228
|
+
targets,
|
|
229
|
+
site,
|
|
230
|
+
plugin
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
export { STYLESHEETS, TARGETS, TARGETS_RESOLVED, mantaqViz, targetsSource };
|
package/package.json
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantaq/inspect",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Turn a Mantaq machine into a viz document: structure, handler explanations (AST), fuzz and test coverage.
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Turn a Mantaq machine into a viz document: structure, handler explanations (AST), fuzz and test coverage. Vite plugin included.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/AndersCan/mantaq",
|
|
9
9
|
"directory": "packages/inspect"
|
|
10
10
|
},
|
|
11
|
-
"bin": {
|
|
12
|
-
"mantaq-inspect": "./dist/cli.mjs"
|
|
13
|
-
},
|
|
14
11
|
"files": [
|
|
15
12
|
"dist"
|
|
16
13
|
],
|
|
@@ -18,8 +15,8 @@
|
|
|
18
15
|
"exports": {
|
|
19
16
|
".": "./dist/index.mjs",
|
|
20
17
|
"./drive": "./dist/drive.mjs",
|
|
21
|
-
"./live-embedded": "./dist/live-embedded.mjs",
|
|
22
18
|
"./live-entry": "./dist/live-entry.mjs",
|
|
19
|
+
"./record": "./dist/record.mjs",
|
|
23
20
|
"./vite": "./dist/vite.mjs",
|
|
24
21
|
"./package.json": "./package.json"
|
|
25
22
|
},
|
|
@@ -28,11 +25,11 @@
|
|
|
28
25
|
},
|
|
29
26
|
"dependencies": {
|
|
30
27
|
"typescript": "6.0.3",
|
|
31
|
-
"@mantaq/core": "0.5.
|
|
32
|
-
"@mantaq/traversal": "0.3.3",
|
|
33
|
-
"@mantaq/viz": "0.2.3",
|
|
28
|
+
"@mantaq/core": "0.5.4",
|
|
34
29
|
"@mantaq/pbt": "0.0.4",
|
|
35
|
-
"@mantaq/
|
|
30
|
+
"@mantaq/traversal": "0.3.4",
|
|
31
|
+
"@mantaq/utils": "0.0.5",
|
|
32
|
+
"@mantaq/viz": "0.4.0"
|
|
36
33
|
},
|
|
37
34
|
"devDependencies": {
|
|
38
35
|
"@types/node": "^26.2.0",
|
|
@@ -40,10 +37,20 @@
|
|
|
40
37
|
"happy-dom": "^21.0.4",
|
|
41
38
|
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
42
39
|
"vite-plus": "1.4.0",
|
|
43
|
-
"
|
|
40
|
+
"vitest": "5.0.5",
|
|
41
|
+
"@mantaq/sugar": "2.0.4"
|
|
44
42
|
},
|
|
45
43
|
"peerDependencies": {
|
|
46
|
-
"vite": "*"
|
|
44
|
+
"vite": "*",
|
|
45
|
+
"vitest": "*"
|
|
46
|
+
},
|
|
47
|
+
"peerDependenciesMeta": {
|
|
48
|
+
"vitest": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"inlinedDependencies": {
|
|
53
|
+
"vite-plus": "1.4.0"
|
|
47
54
|
},
|
|
48
55
|
"scripts": {
|
|
49
56
|
"build": "vp pack",
|
package/dist/cli.d.mts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {}
|