state-scenarios-cli 0.2.0 → 0.4.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 +10 -1
- package/dist/browser.d.ts +27 -0
- package/dist/chunk-GCYOLAFU.js +40 -0
- package/dist/chunk-GCYOLAFU.js.map +1 -0
- package/dist/chunk-O6QTNAAZ.js +67 -0
- package/dist/chunk-O6QTNAAZ.js.map +1 -0
- package/dist/chunk-YFNROT3R.js +115 -0
- package/dist/chunk-YFNROT3R.js.map +1 -0
- package/dist/cli.js +260 -43
- package/dist/cli.js.map +1 -1
- package/dist/doctor-URGIFA3W.js +200 -0
- package/dist/doctor-URGIFA3W.js.map +1 -0
- package/dist/doctor.d.ts +21 -0
- package/dist/errors-G7ODRBLT.js +11 -0
- package/dist/errors-G7ODRBLT.js.map +1 -0
- package/dist/errors.d.ts +33 -0
- package/dist/init-MRK4F5SV.js +96 -0
- package/dist/init-MRK4F5SV.js.map +1 -0
- package/dist/init.d.ts +6 -0
- package/dist/inspect-LN4EACWA.js +13 -0
- package/dist/inspect-LN4EACWA.js.map +1 -0
- package/dist/inspect.d.ts +63 -0
- package/dist/mcp-HRQIWAIR.js +342 -0
- package/dist/mcp-HRQIWAIR.js.map +1 -0
- package/dist/mcp-protocol.d.ts +51 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/promote-GSF5UXJL.js +78 -0
- package/dist/promote-GSF5UXJL.js.map +1 -0
- package/dist/promote.d.ts +12 -0
- package/dist/record-WEZRD2VT.js +59 -0
- package/dist/record-WEZRD2VT.js.map +1 -0
- package/dist/record.d.ts +41 -0
- package/dist/skills-NQRJHKHG.js +80 -0
- package/dist/skills-NQRJHKHG.js.map +1 -0
- package/dist/skills.d.ts +13 -0
- package/package.json +5 -4
- package/skills/create-scenario-map/SKILL.md +88 -0
- package/skills/state-scenarios/SKILL.md +91 -0
- package/skills/state-scenarios/references/auth.md +52 -0
- package/skills/state-scenarios/references/capture.md +45 -0
- package/skills/state-scenarios/references/debugging.md +52 -0
- package/skills/state-scenarios/references/mcp.md +39 -0
- package/skills/state-scenarios/references/media.md +50 -0
- package/skills/state-scenarios/references/network.md +67 -0
- package/skills/state-scenarios/references/setup.md +94 -0
- package/skills/state-scenarios/references/verify.md +47 -0
- package/skills/state-scenarios/references/writing-scenarios.md +55 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkServer,
|
|
3
|
+
contextOptions,
|
|
4
|
+
launchChromium,
|
|
5
|
+
scenarioLink
|
|
6
|
+
} from "./chunk-O6QTNAAZ.js";
|
|
7
|
+
import {
|
|
8
|
+
CliError
|
|
9
|
+
} from "./chunk-GCYOLAFU.js";
|
|
10
|
+
import {
|
|
11
|
+
inspectScenario
|
|
12
|
+
} from "./chunk-YFNROT3R.js";
|
|
13
|
+
|
|
14
|
+
// src/doctor.ts
|
|
15
|
+
import { spawnSync } from "child_process";
|
|
16
|
+
import { existsSync, readFileSync } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
var message = (err) => (err instanceof Error ? err.message : err).split("\n")[0] ?? "";
|
|
19
|
+
async function runDoctor({ dir, baseURL, loadCatalog, flags, cwd = process.cwd() }) {
|
|
20
|
+
const checks = [];
|
|
21
|
+
const fail = (name, err, fix) => checks.push({
|
|
22
|
+
name,
|
|
23
|
+
status: "fail",
|
|
24
|
+
detail: message(err),
|
|
25
|
+
code: err instanceof CliError ? err.code : void 0,
|
|
26
|
+
fix
|
|
27
|
+
});
|
|
28
|
+
let catalog;
|
|
29
|
+
try {
|
|
30
|
+
catalog = loadCatalog();
|
|
31
|
+
const problems = catalog.check();
|
|
32
|
+
if (problems.length) {
|
|
33
|
+
fail(
|
|
34
|
+
"scenarios",
|
|
35
|
+
`${problems.length} invalid: ${problems[0]}`,
|
|
36
|
+
"state-scenarios validate lists them all"
|
|
37
|
+
);
|
|
38
|
+
} else checks.push({ name: "scenarios", status: "ok", detail: `${catalog.list.length} in ${dir}` });
|
|
39
|
+
} catch (err) {
|
|
40
|
+
fail("scenarios", err instanceof Error ? err : String(err), "state-scenarios validate explains it");
|
|
41
|
+
}
|
|
42
|
+
checks.push(...setupChecks(cwd));
|
|
43
|
+
try {
|
|
44
|
+
await checkServer(baseURL);
|
|
45
|
+
checks.push({ name: "dev server", status: "ok", detail: baseURL });
|
|
46
|
+
} catch (err) {
|
|
47
|
+
fail("dev server", err instanceof Error ? err : String(err));
|
|
48
|
+
return checks;
|
|
49
|
+
}
|
|
50
|
+
const resolved = (catalog?.list ?? []).flatMap((s) => {
|
|
51
|
+
try {
|
|
52
|
+
return [catalog?.resolve(s) ?? s];
|
|
53
|
+
} catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const mocks = resolved.some((r) => !!r.network?.length || !!r.handlers?.length);
|
|
58
|
+
if (mocks) {
|
|
59
|
+
const worker = await fetch(new URL("/mockServiceWorker.js", baseURL)).catch(() => void 0);
|
|
60
|
+
if (worker?.ok && worker.headers.get("content-type")?.includes("javascript")) {
|
|
61
|
+
checks.push({ name: "MSW worker", status: "ok", detail: "/mockServiceWorker.js is served" });
|
|
62
|
+
} else {
|
|
63
|
+
checks.push({
|
|
64
|
+
name: "MSW worker",
|
|
65
|
+
status: "fail",
|
|
66
|
+
code: "WORKER_NOT_SERVED",
|
|
67
|
+
detail: "Scenarios mock the network, but /mockServiceWorker.js is not served",
|
|
68
|
+
fix: "Vite: add msw({ mode: 'worker-only' }) from msw/vite to plugins. Otherwise: npx msw init public"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const browser = await launchChromium().catch((err) => err);
|
|
73
|
+
if (browser instanceof Error) {
|
|
74
|
+
fail("browser", browser);
|
|
75
|
+
return checks;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const context = await browser.newContext({ baseURL, ...contextOptions(void 0, flags) });
|
|
79
|
+
const page = await context.newPage();
|
|
80
|
+
page.setDefaultTimeout(1e4);
|
|
81
|
+
const scenario = resolved.find((r) => r.name === "default") ?? resolved[0];
|
|
82
|
+
if (!scenario) {
|
|
83
|
+
await page.goto("/?scenario=");
|
|
84
|
+
const started = await page.waitForFunction(() => globalThis.stateScenariosPage, null, {
|
|
85
|
+
timeout: 1e4
|
|
86
|
+
}).then(() => true, () => false);
|
|
87
|
+
checks.push(
|
|
88
|
+
started ? { name: "session", status: "ok", detail: "startScenarios runs on page load" } : {
|
|
89
|
+
name: "session",
|
|
90
|
+
status: "fail",
|
|
91
|
+
code: "SESSION_NOT_STARTED",
|
|
92
|
+
detail: "startScenarios never ran on page load",
|
|
93
|
+
fix: SESSION_FIX
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
const result = await inspectScenario(page, scenario, { link: scenarioLink(baseURL, scenario) });
|
|
98
|
+
if (result.codes.includes("SESSION_NOT_STARTED")) {
|
|
99
|
+
checks.push({
|
|
100
|
+
name: "session",
|
|
101
|
+
status: "fail",
|
|
102
|
+
code: "SESSION_NOT_STARTED",
|
|
103
|
+
detail: `"${scenario.name}" never became active: ${result.error ?? ""}`,
|
|
104
|
+
fix: SESSION_FIX
|
|
105
|
+
});
|
|
106
|
+
} else if (result.codes.includes("SCENARIO_NOT_IN_APP")) {
|
|
107
|
+
checks.push({
|
|
108
|
+
name: "session",
|
|
109
|
+
status: "fail",
|
|
110
|
+
code: "SCENARIO_NOT_IN_APP",
|
|
111
|
+
detail: `The session runs, but the app has no scenario "${scenario.name}"`,
|
|
112
|
+
fix: `The app loads scenarios from a different folder or list than ${dir}: pass the folder the app uses, add the file to the app's list (Next.js lists imports by hand), or restart the dev server if the file is new`
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
checks.push({ name: "session", status: "ok", detail: `"${scenario.name}" opens: ${result.link}` });
|
|
116
|
+
if (result.codes.includes("READY_TIMEOUT")) {
|
|
117
|
+
checks.push({
|
|
118
|
+
name: "ready",
|
|
119
|
+
status: "warn",
|
|
120
|
+
code: "READY_TIMEOUT",
|
|
121
|
+
detail: `"${scenario.name}" never showed ${scenario.ready}`,
|
|
122
|
+
fix: result.ready?.suggestions.length ? `Selectors on the page: ${result.ready.suggestions.join(", ")}` : void 0
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (result.unhandled.length) {
|
|
126
|
+
checks.push({
|
|
127
|
+
name: "network",
|
|
128
|
+
status: "warn",
|
|
129
|
+
code: "UNMOCKED_REQUEST",
|
|
130
|
+
detail: `"${scenario.name}" left ${result.unhandled.length} request(s) unmocked, e.g. ${result.unhandled[0]}`,
|
|
131
|
+
fix: "Mock them in the scenario, or list third-party SDKs in startScenarios({ ignore })"
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const hooks = await page.evaluate(() => !!globalThis.stateScenariosPage);
|
|
135
|
+
if (!hooks) {
|
|
136
|
+
checks.push({
|
|
137
|
+
name: "agent hooks",
|
|
138
|
+
status: "warn",
|
|
139
|
+
detail: "The app runs an older state-scenarios: shoot can't explain ready failures and mcp can't save",
|
|
140
|
+
fix: "Upgrade state-scenarios in the app to match the CLI"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const save = await page.evaluate(async (endpoint) => {
|
|
144
|
+
const res = await fetch(endpoint).catch(() => void 0);
|
|
145
|
+
return res?.ok ? JSON.stringify(await res.json()) : null;
|
|
146
|
+
}, "/__state-scenarios/save");
|
|
147
|
+
checks.push(
|
|
148
|
+
save ? {
|
|
149
|
+
name: "save to project",
|
|
150
|
+
status: "ok",
|
|
151
|
+
detail: `The panel saves into ${JSON.parse(save).dir}`
|
|
152
|
+
} : {
|
|
153
|
+
name: "save to project",
|
|
154
|
+
status: "warn",
|
|
155
|
+
detail: "The panel can only Download JSON",
|
|
156
|
+
fix: "Vite: stateScenarios({ dir }) from 'state-scenarios/vite'. Next.js: createSaveRoute (README)"
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
fail("session", err instanceof Error ? err : String(err));
|
|
163
|
+
} finally {
|
|
164
|
+
await browser.close();
|
|
165
|
+
}
|
|
166
|
+
return checks;
|
|
167
|
+
}
|
|
168
|
+
var SESSION_FIX = 'Await startScenarios before rendering, outside production-only code. If the router is created when its module loads, import it after startScenarios: const { router } = await import("./router")';
|
|
169
|
+
function setupChecks(cwd) {
|
|
170
|
+
const ffmpeg = spawnSync(process.env.FFMPEG_PATH || "ffmpeg", ["-version"]);
|
|
171
|
+
const mcp = [".mcp.json", ".cursor/mcp.json"].some((f) => {
|
|
172
|
+
const path = join(cwd, f);
|
|
173
|
+
return existsSync(path) && readFileSync(path, "utf8").includes('"state-scenarios"');
|
|
174
|
+
});
|
|
175
|
+
return [
|
|
176
|
+
ffmpeg.error ? {
|
|
177
|
+
name: "ffmpeg",
|
|
178
|
+
status: "warn",
|
|
179
|
+
code: "FFMPEG_MISSING",
|
|
180
|
+
detail: "Not found: record can only save uncropped WebM",
|
|
181
|
+
fix: "Install from https://ffmpeg.org/download.html or set FFMPEG_PATH"
|
|
182
|
+
} : { name: "ffmpeg", status: "ok", detail: "Found, for record" },
|
|
183
|
+
existsSync(join(cwd, ".agents/skills/state-scenarios")) ? { name: "agent skills", status: "ok", detail: ".agents/skills/state-scenarios" } : {
|
|
184
|
+
name: "agent skills",
|
|
185
|
+
status: "warn",
|
|
186
|
+
detail: "Not installed",
|
|
187
|
+
fix: "npx state-scenarios init installs them"
|
|
188
|
+
},
|
|
189
|
+
mcp ? { name: "MCP server", status: "ok", detail: "Registered for coding agents" } : {
|
|
190
|
+
name: "MCP server",
|
|
191
|
+
status: "warn",
|
|
192
|
+
detail: "Not registered",
|
|
193
|
+
fix: "npx state-scenarios init registers it"
|
|
194
|
+
}
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
export {
|
|
198
|
+
runDoctor
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=doctor-URGIFA3W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/doctor.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Catalog } from 'state-scenarios';\nimport { type BrowserFlags, checkServer, contextOptions, launchChromium, scenarioLink } from './browser.js';\nimport { CliError, type ErrorCode } from './errors.js';\nimport { inspectScenario } from './inspect.js';\n\nexport interface Check {\n name: string;\n status: 'ok' | 'warn' | 'fail';\n detail: string;\n code?: ErrorCode;\n fix?: string;\n}\n\nconst message = (err: Error | string) => (err instanceof Error ? err.message : err).split('\\n')[0] ?? '';\n\n/**\n * Answer \"is this app wired up and worth driving?\" one check at a time, each with its fix.\n * Read-only: it opens one scenario and writes nothing.\n */\nexport async function runDoctor(\n { dir, baseURL, loadCatalog, flags, cwd = process.cwd() }: {\n dir: string;\n baseURL: string;\n loadCatalog: () => Catalog;\n flags: BrowserFlags;\n cwd?: string;\n },\n): Promise<Check[]> {\n const checks: Check[] = [];\n\n const fail = (name: string, err: Error | string, fix?: string) =>\n checks.push({\n name,\n status: 'fail',\n detail: message(err),\n code: err instanceof CliError ? err.code : undefined,\n fix,\n });\n\n let catalog: Catalog | undefined;\n\n try {\n catalog = loadCatalog();\n const problems = catalog.check();\n\n if (problems.length) {\n fail(\n 'scenarios',\n `${problems.length} invalid: ${problems[0]}`,\n 'state-scenarios validate lists them all',\n );\n } else checks.push({ name: 'scenarios', status: 'ok', detail: `${catalog.list.length} in ${dir}` });\n } catch (err) {\n fail('scenarios', err instanceof Error ? err : String(err), 'state-scenarios validate explains it');\n }\n\n checks.push(...setupChecks(cwd));\n\n try {\n await checkServer(baseURL);\n checks.push({ name: 'dev server', status: 'ok', detail: baseURL });\n } catch (err) {\n fail('dev server', err instanceof Error ? err : String(err));\n\n return checks;\n }\n\n // Scenarios that fail to resolve are reported above; the rest can still be checked.\n const resolved = (catalog?.list ?? []).flatMap((s) => {\n try {\n return [catalog?.resolve(s) ?? s];\n } catch {\n return [];\n }\n });\n\n const mocks = resolved.some((r) => !!r.network?.length || !!r.handlers?.length);\n\n if (mocks) {\n const worker = await fetch(new URL('/mockServiceWorker.js', baseURL)).catch(() => undefined);\n\n if (worker?.ok && worker.headers.get('content-type')?.includes('javascript')) {\n checks.push({ name: 'MSW worker', status: 'ok', detail: '/mockServiceWorker.js is served' });\n } else {\n checks.push({\n name: 'MSW worker',\n status: 'fail',\n code: 'WORKER_NOT_SERVED',\n detail: 'Scenarios mock the network, but /mockServiceWorker.js is not served',\n fix:\n \"Vite: add msw({ mode: 'worker-only' }) from msw/vite to plugins. Otherwise: npx msw init public\",\n });\n }\n }\n\n const browser = await launchChromium().catch((err: Error) => err);\n\n if (browser instanceof Error) {\n fail('browser', browser);\n\n return checks;\n }\n\n try {\n const context = await browser.newContext({ baseURL, ...contextOptions(undefined, flags) });\n const page = await context.newPage();\n page.setDefaultTimeout(10_000);\n const scenario = resolved.find((r) => r.name === 'default') ?? resolved[0];\n\n if (!scenario) {\n await page.goto('/?scenario=');\n\n const started = await page.waitForFunction(() => globalThis.stateScenariosPage, null, {\n timeout: 10_000,\n })\n .then(() => true, () => false);\n\n checks.push(\n started\n ? { name: 'session', status: 'ok', detail: 'startScenarios runs on page load' }\n : {\n name: 'session',\n status: 'fail',\n code: 'SESSION_NOT_STARTED',\n detail: 'startScenarios never ran on page load',\n fix: SESSION_FIX,\n },\n );\n } else {\n const result = await inspectScenario(page, scenario, { link: scenarioLink(baseURL, scenario) });\n\n if (result.codes.includes('SESSION_NOT_STARTED')) {\n checks.push({\n name: 'session',\n status: 'fail',\n code: 'SESSION_NOT_STARTED',\n detail: `\"${scenario.name}\" never became active: ${result.error ?? ''}`,\n fix: SESSION_FIX,\n });\n } else if (result.codes.includes('SCENARIO_NOT_IN_APP')) {\n checks.push({\n name: 'session',\n status: 'fail',\n code: 'SCENARIO_NOT_IN_APP',\n detail: `The session runs, but the app has no scenario \"${scenario.name}\"`,\n fix:\n `The app loads scenarios from a different folder or list than ${dir}: pass the folder the app uses, add the file to the app's list (Next.js lists imports by hand), or restart the dev server if the file is new`,\n });\n } else {\n checks.push({ name: 'session', status: 'ok', detail: `\"${scenario.name}\" opens: ${result.link}` });\n\n if (result.codes.includes('READY_TIMEOUT')) {\n checks.push({\n name: 'ready',\n status: 'warn',\n code: 'READY_TIMEOUT',\n detail: `\"${scenario.name}\" never showed ${scenario.ready}`,\n fix: result.ready?.suggestions.length\n ? `Selectors on the page: ${result.ready.suggestions.join(', ')}`\n : undefined,\n });\n }\n\n if (result.unhandled.length) {\n checks.push({\n name: 'network',\n status: 'warn',\n code: 'UNMOCKED_REQUEST',\n detail: `\"${scenario.name}\" left ${result.unhandled.length} request(s) unmocked, e.g. ${\n result.unhandled[0]\n }`,\n fix: 'Mock them in the scenario, or list third-party SDKs in startScenarios({ ignore })',\n });\n }\n\n const hooks = await page.evaluate(() => !!globalThis.stateScenariosPage);\n\n if (!hooks) {\n checks.push({\n name: 'agent hooks',\n status: 'warn',\n detail:\n \"The app runs an older state-scenarios: shoot can't explain ready failures and mcp can't save\",\n fix: 'Upgrade state-scenarios in the app to match the CLI',\n });\n }\n\n const save = await page.evaluate(async (endpoint) => {\n const res = await fetch(endpoint).catch(() => undefined);\n\n return res?.ok ? JSON.stringify(await res.json()) : null;\n }, '/__state-scenarios/save');\n\n checks.push(\n save\n ? {\n name: 'save to project',\n status: 'ok',\n detail: `The panel saves into ${JSON.parse(save).dir}`,\n }\n : {\n name: 'save to project',\n status: 'warn',\n detail: 'The panel can only Download JSON',\n fix:\n \"Vite: stateScenarios({ dir }) from 'state-scenarios/vite'. Next.js: createSaveRoute (README)\",\n },\n );\n }\n }\n } catch (err) {\n // Report, don't throw: the checks so far are the answer, and --json must stay valid.\n fail('session', err instanceof Error ? err : String(err));\n } finally {\n await browser.close();\n }\n\n return checks;\n}\n\nconst SESSION_FIX =\n 'Await startScenarios before rendering, outside production-only code. If the router is created when its module loads, import it after startScenarios: const { router } = await import(\"./router\")';\n\n/** Checks that need no browser: ffmpeg for record, and whether agents can find the skills and MCP server. */\nfunction setupChecks(cwd: string): Check[] {\n const ffmpeg = spawnSync(process.env.FFMPEG_PATH || 'ffmpeg', ['-version']);\n\n const mcp = ['.mcp.json', '.cursor/mcp.json'].some((f) => {\n const path = join(cwd, f);\n\n return existsSync(path) && readFileSync(path, 'utf8').includes('\"state-scenarios\"');\n });\n\n return [\n ffmpeg.error\n ? {\n name: 'ffmpeg',\n status: 'warn',\n code: 'FFMPEG_MISSING',\n detail: 'Not found: record can only save uncropped WebM',\n fix: 'Install from https://ffmpeg.org/download.html or set FFMPEG_PATH',\n }\n : { name: 'ffmpeg', status: 'ok', detail: 'Found, for record' },\n existsSync(join(cwd, '.agents/skills/state-scenarios'))\n ? { name: 'agent skills', status: 'ok', detail: '.agents/skills/state-scenarios' }\n : {\n name: 'agent skills',\n status: 'warn',\n detail: 'Not installed',\n fix: 'npx state-scenarios init installs them',\n },\n mcp\n ? { name: 'MCP server', status: 'ok', detail: 'Registered for coding agents' }\n : {\n name: 'MCP server',\n status: 'warn',\n detail: 'Not registered',\n fix: 'npx state-scenarios init registers it',\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,SAAS,iBAAiB;AAC1B,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AAcrB,IAAM,UAAU,CAAC,SAAyB,eAAe,QAAQ,IAAI,UAAU,KAAK,MAAM,IAAI,EAAE,CAAC,KAAK;AAMtG,eAAsB,UACpB,EAAE,KAAK,SAAS,aAAa,OAAO,MAAM,QAAQ,IAAI,EAAE,GAOtC;AAClB,QAAM,SAAkB,CAAC;AAEzB,QAAM,OAAO,CAAC,MAAc,KAAqB,QAC/C,OAAO,KAAK;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ,QAAQ,GAAG;AAAA,IACnB,MAAM,eAAe,WAAW,IAAI,OAAO;AAAA,IAC3C;AAAA,EACF,CAAC;AAEH,MAAI;AAEJ,MAAI;AACF,cAAU,YAAY;AACtB,UAAM,WAAW,QAAQ,MAAM;AAE/B,QAAI,SAAS,QAAQ;AACnB;AAAA,QACE;AAAA,QACA,GAAG,SAAS,MAAM,aAAa,SAAS,CAAC,CAAC;AAAA,QAC1C;AAAA,MACF;AAAA,IACF,MAAO,QAAO,KAAK,EAAE,MAAM,aAAa,QAAQ,MAAM,QAAQ,GAAG,QAAQ,KAAK,MAAM,OAAO,GAAG,GAAG,CAAC;AAAA,EACpG,SAAS,KAAK;AACZ,SAAK,aAAa,eAAe,QAAQ,MAAM,OAAO,GAAG,GAAG,sCAAsC;AAAA,EACpG;AAEA,SAAO,KAAK,GAAG,YAAY,GAAG,CAAC;AAE/B,MAAI;AACF,UAAM,YAAY,OAAO;AACzB,WAAO,KAAK,EAAE,MAAM,cAAc,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AAAA,EACnE,SAAS,KAAK;AACZ,SAAK,cAAc,eAAe,QAAQ,MAAM,OAAO,GAAG,CAAC;AAE3D,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,SAAS,QAAQ,CAAC,GAAG,QAAQ,CAAC,MAAM;AACpD,QAAI;AACF,aAAO,CAAC,SAAS,QAAQ,CAAC,KAAK,CAAC;AAAA,IAClC,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS,UAAU,CAAC,CAAC,EAAE,UAAU,MAAM;AAE9E,MAAI,OAAO;AACT,UAAM,SAAS,MAAM,MAAM,IAAI,IAAI,yBAAyB,OAAO,CAAC,EAAE,MAAM,MAAM,MAAS;AAE3F,QAAI,QAAQ,MAAM,OAAO,QAAQ,IAAI,cAAc,GAAG,SAAS,YAAY,GAAG;AAC5E,aAAO,KAAK,EAAE,MAAM,cAAc,QAAQ,MAAM,QAAQ,kCAAkC,CAAC;AAAA,IAC7F,OAAO;AACL,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,KACE;AAAA,MACJ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,eAAe,EAAE,MAAM,CAAC,QAAe,GAAG;AAEhE,MAAI,mBAAmB,OAAO;AAC5B,SAAK,WAAW,OAAO;AAEvB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,WAAW,EAAE,SAAS,GAAG,eAAe,QAAW,KAAK,EAAE,CAAC;AACzF,UAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,SAAK,kBAAkB,GAAM;AAC7B,UAAM,WAAW,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,SAAS,KAAK,SAAS,CAAC;AAEzE,QAAI,CAAC,UAAU;AACb,YAAM,KAAK,KAAK,aAAa;AAE7B,YAAM,UAAU,MAAM,KAAK,gBAAgB,MAAM,WAAW,oBAAoB,MAAM;AAAA,QACpF,SAAS;AAAA,MACX,CAAC,EACE,KAAK,MAAM,MAAM,MAAM,KAAK;AAE/B,aAAO;AAAA,QACL,UACI,EAAE,MAAM,WAAW,QAAQ,MAAM,QAAQ,mCAAmC,IAC5E;AAAA,UACA,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,KAAK;AAAA,QACP;AAAA,MACJ;AAAA,IACF,OAAO;AACL,YAAM,SAAS,MAAM,gBAAgB,MAAM,UAAU,EAAE,MAAM,aAAa,SAAS,QAAQ,EAAE,CAAC;AAE9F,UAAI,OAAO,MAAM,SAAS,qBAAqB,GAAG;AAChD,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,QAAQ,IAAI,SAAS,IAAI,0BAA0B,OAAO,SAAS,EAAE;AAAA,UACrE,KAAK;AAAA,QACP,CAAC;AAAA,MACH,WAAW,OAAO,MAAM,SAAS,qBAAqB,GAAG;AACvD,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,MAAM;AAAA,UACN,QAAQ,kDAAkD,SAAS,IAAI;AAAA,UACvE,KACE,gEAAgE,GAAG;AAAA,QACvE,CAAC;AAAA,MACH,OAAO;AACL,eAAO,KAAK,EAAE,MAAM,WAAW,QAAQ,MAAM,QAAQ,IAAI,SAAS,IAAI,YAAY,OAAO,IAAI,GAAG,CAAC;AAEjG,YAAI,OAAO,MAAM,SAAS,eAAe,GAAG;AAC1C,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,QAAQ,IAAI,SAAS,IAAI,kBAAkB,SAAS,KAAK;AAAA,YACzD,KAAK,OAAO,OAAO,YAAY,SAC3B,0BAA0B,OAAO,MAAM,YAAY,KAAK,IAAI,CAAC,KAC7D;AAAA,UACN,CAAC;AAAA,QACH;AAEA,YAAI,OAAO,UAAU,QAAQ;AAC3B,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,MAAM;AAAA,YACN,QAAQ,IAAI,SAAS,IAAI,UAAU,OAAO,UAAU,MAAM,8BACxD,OAAO,UAAU,CAAC,CACpB;AAAA,YACA,KAAK;AAAA,UACP,CAAC;AAAA,QACH;AAEA,cAAM,QAAQ,MAAM,KAAK,SAAS,MAAM,CAAC,CAAC,WAAW,kBAAkB;AAEvE,YAAI,CAAC,OAAO;AACV,iBAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,QACE;AAAA,YACF,KAAK;AAAA,UACP,CAAC;AAAA,QACH;AAEA,cAAM,OAAO,MAAM,KAAK,SAAS,OAAO,aAAa;AACnD,gBAAM,MAAM,MAAM,MAAM,QAAQ,EAAE,MAAM,MAAM,MAAS;AAEvD,iBAAO,KAAK,KAAK,KAAK,UAAU,MAAM,IAAI,KAAK,CAAC,IAAI;AAAA,QACtD,GAAG,yBAAyB;AAE5B,eAAO;AAAA,UACL,OACI;AAAA,YACA,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,QAAQ,wBAAwB,KAAK,MAAM,IAAI,EAAE,GAAG;AAAA,UACtD,IACE;AAAA,YACA,MAAM;AAAA,YACN,QAAQ;AAAA,YACR,QAAQ;AAAA,YACR,KACE;AAAA,UACJ;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AAEZ,SAAK,WAAW,eAAe,QAAQ,MAAM,OAAO,GAAG,CAAC;AAAA,EAC1D,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AAEA,SAAO;AACT;AAEA,IAAM,cACJ;AAGF,SAAS,YAAY,KAAsB;AACzC,QAAM,SAAS,UAAU,QAAQ,IAAI,eAAe,UAAU,CAAC,UAAU,CAAC;AAE1E,QAAM,MAAM,CAAC,aAAa,kBAAkB,EAAE,KAAK,CAAC,MAAM;AACxD,UAAM,OAAO,KAAK,KAAK,CAAC;AAExB,WAAO,WAAW,IAAI,KAAK,aAAa,MAAM,MAAM,EAAE,SAAS,mBAAmB;AAAA,EACpF,CAAC;AAED,SAAO;AAAA,IACL,OAAO,QACH;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,KAAK;AAAA,IACP,IACE,EAAE,MAAM,UAAU,QAAQ,MAAM,QAAQ,oBAAoB;AAAA,IAChE,WAAW,KAAK,KAAK,gCAAgC,CAAC,IAClD,EAAE,MAAM,gBAAgB,QAAQ,MAAM,QAAQ,iCAAiC,IAC/E;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,KAAK;AAAA,IACP;AAAA,IACF,MACI,EAAE,MAAM,cAAc,QAAQ,MAAM,QAAQ,+BAA+B,IAC3E;AAAA,MACA,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,KAAK;AAAA,IACP;AAAA,EACJ;AACF;","names":[]}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Catalog } from 'state-scenarios';
|
|
2
|
+
import { type BrowserFlags } from './browser.js';
|
|
3
|
+
import { type ErrorCode } from './errors.js';
|
|
4
|
+
export interface Check {
|
|
5
|
+
name: string;
|
|
6
|
+
status: 'ok' | 'warn' | 'fail';
|
|
7
|
+
detail: string;
|
|
8
|
+
code?: ErrorCode;
|
|
9
|
+
fix?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Answer "is this app wired up and worth driving?" one check at a time, each with its fix.
|
|
13
|
+
* Read-only: it opens one scenario and writes nothing.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runDoctor({ dir, baseURL, loadCatalog, flags, cwd }: {
|
|
16
|
+
dir: string;
|
|
17
|
+
baseURL: string;
|
|
18
|
+
loadCatalog: () => Catalog;
|
|
19
|
+
flags: BrowserFlags;
|
|
20
|
+
cwd?: string;
|
|
21
|
+
}): Promise<Check[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable error codes. The class sets the exit code, so scripts and agents can branch on
|
|
3
|
+
* either without reading messages: 1 a scenario failed, 2 configuration, 3 infrastructure.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ERROR_CLASSES: {
|
|
6
|
+
readonly SESSION_NOT_STARTED: 'failed';
|
|
7
|
+
readonly SCENARIO_NOT_IN_APP: 'failed';
|
|
8
|
+
readonly READY_TIMEOUT: 'failed';
|
|
9
|
+
readonly UNMOCKED_REQUEST: 'failed';
|
|
10
|
+
readonly PAGE_ERROR: 'failed';
|
|
11
|
+
readonly CONSOLE_ERROR: 'failed';
|
|
12
|
+
readonly ELEMENT_NOT_FOUND: 'failed';
|
|
13
|
+
readonly STEPS_FAILED: 'failed';
|
|
14
|
+
readonly RULE_VIOLATION: 'failed';
|
|
15
|
+
readonly INVALID_ARGUMENT: 'config';
|
|
16
|
+
readonly INVALID_SCENARIO: 'config';
|
|
17
|
+
readonly UNKNOWN_SCENARIO: 'config';
|
|
18
|
+
readonly DEV_SERVER_DOWN: 'infra';
|
|
19
|
+
readonly BROWSER_MISSING: 'infra';
|
|
20
|
+
readonly FFMPEG_MISSING: 'infra';
|
|
21
|
+
readonly WORKER_NOT_SERVED: 'infra';
|
|
22
|
+
};
|
|
23
|
+
export type ErrorCode = keyof typeof ERROR_CLASSES;
|
|
24
|
+
export declare const EXIT_CODES: {
|
|
25
|
+
readonly failed: 1;
|
|
26
|
+
readonly config: 2;
|
|
27
|
+
readonly infra: 3;
|
|
28
|
+
};
|
|
29
|
+
export declare class CliError extends Error {
|
|
30
|
+
readonly code: ErrorCode;
|
|
31
|
+
constructor(code: ErrorCode, message: string);
|
|
32
|
+
get exitCode(): 1 | 2 | 3;
|
|
33
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/init.ts
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "path";
|
|
4
|
+
import { scenarioJsonSchema } from "state-scenarios";
|
|
5
|
+
function initScenarios(scenariosDir, cwd = process.cwd()) {
|
|
6
|
+
const dir = resolve(cwd, scenariosDir);
|
|
7
|
+
mkdirSync(dir, { recursive: true });
|
|
8
|
+
const schemaPath = join(dirname(dir), "scenario.schema.json");
|
|
9
|
+
const defaultPath = join(dir, "default.json");
|
|
10
|
+
const schemaRel = relative(dir, schemaPath).replaceAll("\\", "/");
|
|
11
|
+
const schemaExisted = existsSync(schemaPath);
|
|
12
|
+
const defaultExisted = existsSync(defaultPath);
|
|
13
|
+
if (!schemaExisted) {
|
|
14
|
+
writeFileSync(schemaPath, `${JSON.stringify(scenarioJsonSchema(), null, 2)}
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
17
|
+
if (!defaultExisted) {
|
|
18
|
+
writeFileSync(
|
|
19
|
+
defaultPath,
|
|
20
|
+
`${JSON.stringify(
|
|
21
|
+
{
|
|
22
|
+
$schema: schemaRel,
|
|
23
|
+
name: "default",
|
|
24
|
+
description: "Happy path: record the real network, then Save as scenario from the panel"
|
|
25
|
+
},
|
|
26
|
+
null,
|
|
27
|
+
2
|
|
28
|
+
)}
|
|
29
|
+
`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const stack = detectStack(cwd);
|
|
33
|
+
const scenariosImport = relative(cwd, dir).replaceAll("\\", "/");
|
|
34
|
+
console.log(`${defaultExisted ? "Kept existing" : "Created"} ${relative(cwd, defaultPath)}`);
|
|
35
|
+
console.log(`${schemaExisted ? "Kept existing" : "Created"} ${relative(cwd, schemaPath)}`);
|
|
36
|
+
console.log("");
|
|
37
|
+
console.log("Wire the runtime (DEV only), then open the app with ?scenario-record,");
|
|
38
|
+
console.log("click through the flow, Mark ready, and Download JSON into this folder.");
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(wiringSnippet(stack, scenariosImport));
|
|
41
|
+
return { stack };
|
|
42
|
+
}
|
|
43
|
+
function detectStack(cwd) {
|
|
44
|
+
if (hasFile(cwd, ["next.config.ts", "next.config.js", "next.config.mjs", "next.config.cjs"])) {
|
|
45
|
+
return "next";
|
|
46
|
+
}
|
|
47
|
+
if (hasFile(cwd, ["vite.config.ts", "vite.config.js", "vite.config.mjs", "vite.config.cjs"])) {
|
|
48
|
+
return "vite";
|
|
49
|
+
}
|
|
50
|
+
return "unknown";
|
|
51
|
+
}
|
|
52
|
+
function hasFile(cwd, names) {
|
|
53
|
+
return names.some((name) => existsSync(join(cwd, name)));
|
|
54
|
+
}
|
|
55
|
+
function wiringSnippet(stack, scenariosImport) {
|
|
56
|
+
const glob = `import.meta.glob<ScenarioInput>('${scenariosImport ? `/${scenariosImport}` : ""}/*.json', { eager: true, import: 'default' })`;
|
|
57
|
+
if (stack === "next") {
|
|
58
|
+
return `// Next.js (App Router): wrap children in a client <Scenarios> gate (see README).
|
|
59
|
+
// List scenario JSON imports explicitly (Next has no import.meta.glob).
|
|
60
|
+
// Serve the MSW worker with: npx msw init public
|
|
61
|
+
//
|
|
62
|
+
// mountPanel(session, { schemaPath: '../scenario.schema.json' });
|
|
63
|
+
//
|
|
64
|
+
// Let the panel save into ${scenariosImport || "."}: create app/%5F%5Fstate-scenarios/save/route.ts with
|
|
65
|
+
// import { createSaveRoute } from 'state-scenarios/next';
|
|
66
|
+
// export const { GET } = createSaveRoute({ dir: '${scenariosImport || "."}' });`;
|
|
67
|
+
}
|
|
68
|
+
const plugins = `import { msw } from 'msw/vite';
|
|
69
|
+
// import { stateScenarios } from 'state-scenarios/vite';
|
|
70
|
+
// plugins: [msw({ mode: 'worker-only' }), stateScenarios({ dir: '${scenariosImport || "."}' })]
|
|
71
|
+
// stateScenarios lets the panel save scenarios straight into this folder.`;
|
|
72
|
+
const viteNote = stack === "vite" ? `// In vite.config.ts:
|
|
73
|
+
// ${plugins}` : `// Vite, in vite.config.ts:
|
|
74
|
+
// ${plugins}
|
|
75
|
+
// Next: see README "Next.js (App Router)"`;
|
|
76
|
+
return `${viteNote}
|
|
77
|
+
|
|
78
|
+
import { type ScenarioInput, startScenarios } from 'state-scenarios';
|
|
79
|
+
import { mountPanel } from 'state-scenarios/panel';
|
|
80
|
+
// import { tanstackQuery } from 'state-scenarios/tanstack-query'; // if you use it
|
|
81
|
+
|
|
82
|
+
if (import.meta.env.DEV) {
|
|
83
|
+
const session = await startScenarios({
|
|
84
|
+
scenarios: Object.values(${glob}),
|
|
85
|
+
// adapters: [tanstackQuery(queryClient)],
|
|
86
|
+
// refresh: () => queryClient.resetQueries(),
|
|
87
|
+
});
|
|
88
|
+
mountPanel(session, { schemaPath: '../scenario.schema.json' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Then: open /?scenario-record \u2192 use the app \u2192 panel \u2192 Mark ready \u2192 Download JSON`;
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
initScenarios
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=init-MRK4F5SV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { scenarioJsonSchema } from 'state-scenarios';\n\n/** Create a scenarios dir, schema, default stub, and print a wiring snippet. */\nexport function initScenarios(scenariosDir: string, cwd = process.cwd()) {\n const dir = resolve(cwd, scenariosDir);\n mkdirSync(dir, { recursive: true });\n\n const schemaPath = join(dirname(dir), 'scenario.schema.json');\n const defaultPath = join(dir, 'default.json');\n const schemaRel = relative(dir, schemaPath).replaceAll('\\\\', '/');\n\n const schemaExisted = existsSync(schemaPath);\n const defaultExisted = existsSync(defaultPath);\n\n if (!schemaExisted) {\n writeFileSync(schemaPath, `${JSON.stringify(scenarioJsonSchema(), null, 2)}\\n`);\n }\n\n if (!defaultExisted) {\n writeFileSync(\n defaultPath,\n `${\n JSON.stringify(\n {\n $schema: schemaRel,\n name: 'default',\n description: 'Happy path: record the real network, then Save as scenario from the panel',\n },\n null,\n 2,\n )\n }\\n`,\n );\n }\n\n const stack = detectStack(cwd);\n const scenariosImport = relative(cwd, dir).replaceAll('\\\\', '/');\n\n console.log(`${defaultExisted ? 'Kept existing' : 'Created'} ${relative(cwd, defaultPath)}`);\n console.log(`${schemaExisted ? 'Kept existing' : 'Created'} ${relative(cwd, schemaPath)}`);\n console.log('');\n console.log('Wire the runtime (DEV only), then open the app with ?scenario-record,');\n console.log('click through the flow, Mark ready, and Download JSON into this folder.');\n console.log('');\n console.log(wiringSnippet(stack, scenariosImport));\n\n return { stack };\n}\n\ntype Stack = 'vite' | 'next' | 'unknown';\n\nfunction detectStack(cwd: string): Stack {\n if (hasFile(cwd, ['next.config.ts', 'next.config.js', 'next.config.mjs', 'next.config.cjs'])) {\n return 'next';\n }\n\n if (hasFile(cwd, ['vite.config.ts', 'vite.config.js', 'vite.config.mjs', 'vite.config.cjs'])) {\n return 'vite';\n }\n\n return 'unknown';\n}\n\nfunction hasFile(cwd: string, names: string[]): boolean {\n return names.some((name) => existsSync(join(cwd, name)));\n}\n\nfunction wiringSnippet(stack: Stack, scenariosImport: string): string {\n // Root-relative: Vite resolves '/…' globs from the project root, wherever the snippet is pasted.\n const glob = `import.meta.glob<ScenarioInput>('${\n scenariosImport ? `/${scenariosImport}` : ''\n }/*.json', { eager: true, import: 'default' })`;\n\n if (stack === 'next') {\n return `// Next.js (App Router): wrap children in a client <Scenarios> gate (see README).\n// List scenario JSON imports explicitly (Next has no import.meta.glob).\n// Serve the MSW worker with: npx msw init public\n//\n// mountPanel(session, { schemaPath: '../scenario.schema.json' });\n//\n// Let the panel save into ${scenariosImport || '.'}: create app/%5F%5Fstate-scenarios/save/route.ts with\n// import { createSaveRoute } from 'state-scenarios/next';\n// export const { GET } = createSaveRoute({ dir: '${scenariosImport || '.'}' });`;\n }\n\n const plugins = `import { msw } from 'msw/vite';\n// import { stateScenarios } from 'state-scenarios/vite';\n// plugins: [msw({ mode: 'worker-only' }), stateScenarios({ dir: '${scenariosImport || '.'}' })]\n// stateScenarios lets the panel save scenarios straight into this folder.`;\n\n const viteNote = stack === 'vite'\n ? `// In vite.config.ts:\\n// ${plugins}`\n : `// Vite, in vite.config.ts:\\n// ${plugins}\n// Next: see README \"Next.js (App Router)\"`;\n\n return `${viteNote}\n\nimport { type ScenarioInput, startScenarios } from 'state-scenarios';\nimport { mountPanel } from 'state-scenarios/panel';\n// import { tanstackQuery } from 'state-scenarios/tanstack-query'; // if you use it\n\nif (import.meta.env.DEV) {\n const session = await startScenarios({\n scenarios: Object.values(${glob}),\n // adapters: [tanstackQuery(queryClient)],\n // refresh: () => queryClient.resetQueries(),\n });\n mountPanel(session, { schemaPath: '../scenario.schema.json' });\n}\n\n// Then: open /?scenario-record → use the app → panel → Mark ready → Download JSON`;\n}\n"],"mappings":";AAAA,SAAS,YAAY,WAAW,qBAAqB;AACrD,SAAS,SAAS,MAAM,UAAU,eAAe;AACjD,SAAS,0BAA0B;AAG5B,SAAS,cAAc,cAAsB,MAAM,QAAQ,IAAI,GAAG;AACvE,QAAM,MAAM,QAAQ,KAAK,YAAY;AACrC,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAElC,QAAM,aAAa,KAAK,QAAQ,GAAG,GAAG,sBAAsB;AAC5D,QAAM,cAAc,KAAK,KAAK,cAAc;AAC5C,QAAM,YAAY,SAAS,KAAK,UAAU,EAAE,WAAW,MAAM,GAAG;AAEhE,QAAM,gBAAgB,WAAW,UAAU;AAC3C,QAAM,iBAAiB,WAAW,WAAW;AAE7C,MAAI,CAAC,eAAe;AAClB,kBAAc,YAAY,GAAG,KAAK,UAAU,mBAAmB,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAChF;AAEA,MAAI,CAAC,gBAAgB;AACnB;AAAA,MACE;AAAA,MACA,GACE,KAAK;AAAA,QACH;AAAA,UACE,SAAS;AAAA,UACT,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA;AAAA,QACA;AAAA,MACF,CACF;AAAA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,YAAY,GAAG;AAC7B,QAAM,kBAAkB,SAAS,KAAK,GAAG,EAAE,WAAW,MAAM,GAAG;AAE/D,UAAQ,IAAI,GAAG,iBAAiB,kBAAkB,SAAS,IAAI,SAAS,KAAK,WAAW,CAAC,EAAE;AAC3F,UAAQ,IAAI,GAAG,gBAAgB,kBAAkB,SAAS,IAAI,SAAS,KAAK,UAAU,CAAC,EAAE;AACzF,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,uEAAuE;AACnF,UAAQ,IAAI,yEAAyE;AACrF,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,cAAc,OAAO,eAAe,CAAC;AAEjD,SAAO,EAAE,MAAM;AACjB;AAIA,SAAS,YAAY,KAAoB;AACvC,MAAI,QAAQ,KAAK,CAAC,kBAAkB,kBAAkB,mBAAmB,iBAAiB,CAAC,GAAG;AAC5F,WAAO;AAAA,EACT;AAEA,MAAI,QAAQ,KAAK,CAAC,kBAAkB,kBAAkB,mBAAmB,iBAAiB,CAAC,GAAG;AAC5F,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,SAAS,QAAQ,KAAa,OAA0B;AACtD,SAAO,MAAM,KAAK,CAAC,SAAS,WAAW,KAAK,KAAK,IAAI,CAAC,CAAC;AACzD;AAEA,SAAS,cAAc,OAAc,iBAAiC;AAEpE,QAAM,OAAO,oCACX,kBAAkB,IAAI,eAAe,KAAK,EAC5C;AAEA,MAAI,UAAU,QAAQ;AACpB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAMkB,mBAAmB,GAAG;AAAA;AAAA,sDAEG,mBAAmB,GAAG;AAAA,EAC1E;AAEA,QAAM,UAAU;AAAA;AAAA,sEAEoD,mBAAmB,GAAG;AAAA;AAG1F,QAAM,WAAW,UAAU,SACvB;AAAA,OAA+B,OAAO,KACtC;AAAA,OAAqC,OAAO;AAAA;AAGhD,SAAO,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+BAQW,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQnC;","names":[]}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Page } from '@playwright/test';
|
|
2
|
+
import type { ReadyReport, Scenario } from 'state-scenarios';
|
|
3
|
+
import type { ErrorCode } from './errors.js';
|
|
4
|
+
export interface Inspection {
|
|
5
|
+
name: string;
|
|
6
|
+
ok: boolean;
|
|
7
|
+
/** Why it failed, as stable codes; empty when ok. */
|
|
8
|
+
codes: ErrorCode[];
|
|
9
|
+
/** Reopens this state in strict mode. */
|
|
10
|
+
link: string;
|
|
11
|
+
unhandled: string[];
|
|
12
|
+
pageErrors: string[];
|
|
13
|
+
consoleErrors: string[];
|
|
14
|
+
error: string | null;
|
|
15
|
+
/** Set when the ready selector didn't show: what it matched and what would. */
|
|
16
|
+
ready: (ReadyReport & {
|
|
17
|
+
selector: string;
|
|
18
|
+
}) | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Collect uncaught exceptions and console errors from now on. Browser network logs
|
|
22
|
+
* ("Failed to load resource") are left out: error scenarios cause them on purpose, and
|
|
23
|
+
* requests the scenario didn't mock are reported as unhandled instead.
|
|
24
|
+
*/
|
|
25
|
+
export declare function watchErrors(page: Page): {
|
|
26
|
+
pageErrors: string[];
|
|
27
|
+
consoleErrors: string[];
|
|
28
|
+
};
|
|
29
|
+
/** Open a scenario and report everything an agent needs to fix it. */
|
|
30
|
+
export declare function inspectScenario(page: Page, scenario: Scenario, { link, strict, panel }: {
|
|
31
|
+
link: string;
|
|
32
|
+
strict?: boolean;
|
|
33
|
+
panel?: boolean;
|
|
34
|
+
}): Promise<Inspection>;
|
|
35
|
+
interface DraftEntry {
|
|
36
|
+
method: string;
|
|
37
|
+
path: string;
|
|
38
|
+
query?: Record<string, string>;
|
|
39
|
+
response: {
|
|
40
|
+
status: number;
|
|
41
|
+
body: Record<string, never>;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
interface DraftEntry {
|
|
45
|
+
method: string;
|
|
46
|
+
path: string;
|
|
47
|
+
query?: Record<string, string>;
|
|
48
|
+
response: {
|
|
49
|
+
status: number;
|
|
50
|
+
body: Record<string, never>;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/** A network entry that answers an unmocked request, ready to paste and fill in. */
|
|
54
|
+
export declare function networkEntryFor(label: string, appOrigin: string): DraftEntry;
|
|
55
|
+
/**
|
|
56
|
+
* The page an agent reads when a scenario fails: what went wrong, the link that reopens it,
|
|
57
|
+
* and the fix written out where one can be.
|
|
58
|
+
*/
|
|
59
|
+
export declare function failurePage(r: Inspection, { screenshot, appOrigin }: {
|
|
60
|
+
screenshot: string;
|
|
61
|
+
appOrigin: string;
|
|
62
|
+
}): string;
|
|
63
|
+
export {};
|