executable-stories-showcase 0.0.0 → 0.1.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/dist/{capture-GCESXDQD.js → capture-CIKR46QE.js} +3 -3
- package/dist/{chunk-QDLRQPTA.js → chunk-C755RCRL.js} +3 -3
- package/dist/chunk-C755RCRL.js.map +1 -0
- package/dist/{chunk-Z664X6AQ.js → chunk-COLHEOSE.js} +2 -2
- package/dist/{chunk-7VEOUHJP.js → chunk-DETES767.js} +5 -2
- package/dist/chunk-DETES767.js.map +1 -0
- package/dist/{chunk-PKGBYTBX.js → chunk-ESIS3D6R.js} +3 -2
- package/dist/chunk-ESIS3D6R.js.map +1 -0
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/link.d.ts +2 -1
- package/dist/network-I5SB4F6T.js +7 -0
- package/dist/next.js +2 -2
- package/dist/panel.d.ts +1 -1
- package/dist/panel.js +3 -4
- package/dist/panel.js.map +1 -1
- package/dist/playwright.js +1 -1
- package/dist/redact.d.ts +5 -6
- package/dist/vite.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-7VEOUHJP.js.map +0 -1
- package/dist/chunk-PKGBYTBX.js.map +0 -1
- package/dist/chunk-QDLRQPTA.js.map +0 -1
- package/dist/network-4UFPO74C.js +0 -7
- /package/dist/{capture-GCESXDQD.js.map → capture-CIKR46QE.js.map} +0 -0
- /package/dist/{chunk-Z664X6AQ.js.map → chunk-COLHEOSE.js.map} +0 -0
- /package/dist/{network-4UFPO74C.js.map → network-I5SB4F6T.js.map} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
captureScenario
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-C755RCRL.js";
|
|
4
|
+
import "./chunk-ESIS3D6R.js";
|
|
5
5
|
import "./chunk-MCC3CAWQ.js";
|
|
6
6
|
import "./chunk-BK4EEQXV.js";
|
|
7
7
|
export {
|
|
8
8
|
captureScenario
|
|
9
9
|
};
|
|
10
|
-
//# sourceMappingURL=capture-
|
|
10
|
+
//# sourceMappingURL=capture-CIKR46QE.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
PARAM,
|
|
3
3
|
RECORD_PARAM,
|
|
4
4
|
STRICT_PARAM
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ESIS3D6R.js";
|
|
6
6
|
import {
|
|
7
7
|
diffScenario
|
|
8
8
|
} from "./chunk-MCC3CAWQ.js";
|
|
@@ -64,7 +64,7 @@ function redactSecrets(value) {
|
|
|
64
64
|
if (Array.isArray(node)) return node.map((item, i) => walk(item, `${path}[${i}]`, key));
|
|
65
65
|
if (node instanceof Object) {
|
|
66
66
|
return Object.fromEntries(
|
|
67
|
-
Object.entries(node).map(([k, v]) => [k, walk(v, path ? `${path}.${k}` : k, k)])
|
|
67
|
+
Object.entries(node).map(([k, v]) => [k, walk(v, path ? `${path}.${k}` : k, secret ? key : k)])
|
|
68
68
|
);
|
|
69
69
|
}
|
|
70
70
|
return node;
|
|
@@ -122,4 +122,4 @@ export {
|
|
|
122
122
|
withoutSecretParams,
|
|
123
123
|
captureScenario
|
|
124
124
|
};
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
125
|
+
//# sourceMappingURL=chunk-C755RCRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/redact.ts","../src/jwt.ts","../src/capture.ts"],"sourcesContent":["import { z } from 'zod';\nimport { unsignedJwt } from './jwt.js';\nimport type { Json, JsonObject } from './schema.js';\n\n/** Keys whose values are secrets whatever they hold. */\nconst SECRET_KEY =\n /pass(word|phrase)?$|secret|api[-_]?key|private[-_]?key|access[-_]?key|credential|^otp$|card[-_]?number|^cvc$|^cvv$/i;\n\n/** Keys holding bearer values: a JWT keeps its claims, anything else is replaced. */\nconst TOKEN_KEY = /token|session[-_]?id|^sid$|authorization|cookie/i;\n\n/** Numeric bearer values: session ids. */\nconst NUMERIC_ID_KEY = /session[-_]?id$|^sid$/i;\n\nconst JWT = /^[\\w-]+\\.[\\w-]+\\.[\\w-]*$/;\n\n/**\n * Values that are credentials whatever key holds them: an auth header, and the key formats of\n * Stripe, GitHub, AWS and Slack.\n */\nconst SECRET_VALUE = /^(Bearer|Basic) \\S|^[sr]k_(live|test)_\\w|^gh[pousr]_\\w{20}|^github_pat_\\w|^AKIA[0-9A-Z]{16}$|^xox[abprs]-/;\n\n/** URL params that carry a one-time credential, such as an OAuth authorization `code`. */\nconst SECRET_PARAM = /^(code|state|nonce)$/i;\n\nconst PLACEHOLDER = '[redacted]';\n\nconst decode = (part: string): JsonObject | undefined => {\n try {\n const bytes = Uint8Array.from(atob(part.replace(/-/g, '+').replace(/_/g, '/')), (c) => c.charCodeAt(0));\n const value: Json = JSON.parse(new TextDecoder().decode(bytes));\n\n return value instanceof Object && !Array.isArray(value) ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** A JWT re-issued unsigned with the same claims, so the app still decodes it but no server accepts it. */\nconst unsign = (token: string) => {\n if (!JWT.test(token)) return undefined;\n const [header = '', payload = ''] = token.split('.');\n\n if (!decode(header)) return undefined;\n const claims = decode(payload);\n\n return claims && unsignedJwt(claims);\n};\n\nexport interface Redacted {\n value: Json;\n /** Paths that changed, e.g. `user.accessToken`. */\n redacted: string[];\n}\n\n/**\n * Remove secrets from recorded responses and captured state: strings under secret-like keys\n * become \"[redacted]\" and numbers become 0, and signed JWTs anywhere lose their signature but\n * keep their claims.\n */\nexport function redactSecrets(value: Json): Redacted {\n const redacted: string[] = [];\n\n const walk = (node: Json, path: string, key: string): Json => {\n const text = z.string().safeParse(node);\n // A JWT keeps its claims, whatever key holds it: the app may decode it.\n const jwt = text.success ? unsign(text.data) : undefined;\n\n if (jwt !== undefined) {\n if (jwt !== text.data) redacted.push(path);\n\n return jwt;\n }\n\n // Checked before the value's type: a card number or one-time code is often a number.\n // Token-like keys only for strings: `tokens: 3` is usually a count, not a credential.\n const secret = SECRET_KEY.test(key) || TOKEN_KEY.test(key);\n const number = z.number().safeParse(node);\n\n if ((SECRET_KEY.test(key) || NUMERIC_ID_KEY.test(key)) && number.success) {\n redacted.push(path);\n\n return 0; // still a number, so the app's parsing and formatting keep working\n }\n\n if (text.success && ((secret && text.data) || SECRET_VALUE.test(text.data))) {\n redacted.push(path);\n\n return PLACEHOLDER;\n }\n\n if (text.success || number.success) return node;\n\n if (Array.isArray(node)) return node.map((item, i) => walk(item, `${path}[${i}]`, key));\n\n if (node instanceof Object) {\n // Everything under a secret key is secret too, e.g. a private key's JWK fields.\n return Object.fromEntries(\n Object.entries(node).map(([k, v]) => [k, walk(v, path ? `${path}.${k}` : k, secret ? key : k)]),\n );\n }\n\n return node;\n };\n\n return { value: walk(value, '', ''), redacted };\n}\n\n/**\n * Drops URL params whose value redaction would change (`?api_key=…`, `?token=…`), and OAuth's\n * one-time `code`, `state` and `nonce`. Saved params are matched or replayed as written, so `\"[redacted]\"`\n * would be wrong; without the param, a recorded request matches whatever value the app sends.\n */\nexport function withoutSecretParams(params: Record<string, string>, where: string): Record<string, string> {\n const redacted = z.record(z.string(), z.unknown()).parse(redactSecrets(params).value);\n const dropped = Object.keys(params).filter((key) => SECRET_PARAM.test(key) || redacted[key] !== params[key]);\n\n if (dropped.length) console.info(`[executable-stories-showcase] left ${dropped.join(', ')} out of ${where}`);\n\n return Object.fromEntries(Object.entries(params).filter(([key]) => !dropped.includes(key)));\n}\n","import type { JsonObject } from './schema.js';\n\n/** Base64url of UTF-8, so claims in any script (\"José\", \"李雷\", \"Zoë\") survive decoding. */\nconst encode = (value: JsonObject) =>\n btoa(Array.from(new TextEncoder().encode(JSON.stringify(value)), (b) => String.fromCharCode(b)).join(''))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\n/**\n * An unsigned JWT (`alg: \"none\"`) carrying `claims`, for mocked responses in \"signed-in\" scenarios\n * when the app only decodes the token it gets back. Anything that verifies signatures rejects it,\n * which keeps it out of real backends. Set `exp` in the future if the app checks expiry.\n */\nexport const unsignedJwt = (claims: JsonObject): string =>\n `${encode({ alg: 'none', typ: 'JWT' })}.${encode(claims)}.`;\n","import { diffScenario } from './catalog.js';\nimport { PARAM, RECORD_PARAM, STRICT_PARAM } from './link.js';\nimport { redactSecrets, withoutSecretParams } from './redact.js';\nimport type { ScenarioSession } from './runtime.js';\nimport { parseScenario, type Scenario, type ScenarioInput } from './schema.js';\n\nconst SCENARIO_PARAMS = new Set([PARAM, STRICT_PARAM, RECORD_PARAM]);\n\nexport interface CaptureOptions {\n name: string;\n description?: string;\n /** Playwright selector that marks this UI state ready. */\n ready?: string;\n /**\n * Catalog scenario to `extends`. Diff uses its resolved (flattened) form.\n * Omit for a full self-contained snapshot.\n */\n base?: Scenario;\n /** Relative `$schema` path stamped on the catalog file only, e.g. `../scenario.schema.json`. */\n schemaPath?: string;\n}\n\nexport interface CapturedScenario {\n /** Flattened snapshot for inline share links. */\n flat: Scenario;\n /** File to commit: diffs against `base` when set, otherwise the flat snapshot as input. */\n forCatalog: ScenarioInput;\n}\n\n/**\n * Snapshot what the app shows now: current route and URL params, the active network\n * fixtures (plus recorded responses), and every connected adapter's live state.\n */\nexport function captureScenario(session: ScenarioSession, options: CaptureOptions): CapturedScenario {\n const params: Record<string, string> = {};\n new URLSearchParams(location.search).forEach((v, k) => {\n if (!SCENARIO_PARAMS.has(k)) params[k] = v;\n });\n // A page URL can carry a token or a magic-link code; it is saved like recorded traffic.\n const url = withoutSecretParams(params, 'the captured URL');\n\n const state = Object.fromEntries(\n session.keys().flatMap((key) => {\n const live = session.read(key);\n if (live === undefined) return [];\n\n // A store or cache can hold a session token; captured scenarios get committed and shared,\n // so they get the same redaction as recorded responses.\n const { value, redacted } = redactSecrets(live);\n\n if (redacted.length) {\n console.info(`[executable-stories-showcase] redacted ${redacted.join(', ')} in state[\"${key}\"]`);\n }\n\n return [[key, value] as const];\n }),\n );\n\n // Recorded responses go after the scenario's own: they answered requests it didn't mock.\n const network = [...(session.resolved?.network ?? []), ...session.recorded()];\n\n const draft = parseScenario({\n name: options.name,\n description: options.description || undefined,\n path: location.pathname,\n url,\n network: network.length ? network : undefined,\n handlers: session.resolved?.handlers,\n viewport: session.resolved?.viewport,\n state,\n ready: options.ready || undefined,\n });\n\n const flat = session.catalog.resolve(draft);\n const flatBase = options.base ? session.catalog.resolve(options.base) : undefined;\n const forCatalog = diffScenario(flat, flatBase);\n\n if (options.schemaPath) forCatalog.$schema = options.schemaPath;\n\n return { flat, forCatalog };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SAVE_ENDPOINT
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ESIS3D6R.js";
|
|
4
4
|
import {
|
|
5
5
|
JsonSchema,
|
|
6
6
|
ScenarioSchema
|
|
@@ -145,4 +145,4 @@ export {
|
|
|
145
145
|
createSaveHandler,
|
|
146
146
|
startSaveServer
|
|
147
147
|
};
|
|
148
|
-
//# sourceMappingURL=chunk-
|
|
148
|
+
//# sourceMappingURL=chunk-COLHEOSE.js.map
|
|
@@ -26,7 +26,10 @@ function toHandlers(entries = []) {
|
|
|
26
26
|
}
|
|
27
27
|
const r = responses[Math.min(calls++, responses.length - 1)];
|
|
28
28
|
if (r.delay !== void 0) await delay(r.delay);
|
|
29
|
-
const
|
|
29
|
+
const headers = new Headers(r.headers);
|
|
30
|
+
headers.set("x-content-type-options", "nosniff");
|
|
31
|
+
if (/script|wasm/i.test(headers.get("content-type") ?? "")) return void 0;
|
|
32
|
+
const init = { status: r.status, headers };
|
|
30
33
|
if (r.text !== void 0) return HttpResponse.text(r.text, init);
|
|
31
34
|
if (r.body === void 0) return new HttpResponse(null, init);
|
|
32
35
|
return HttpResponse.json(r.body, init);
|
|
@@ -37,4 +40,4 @@ function toHandlers(entries = []) {
|
|
|
37
40
|
export {
|
|
38
41
|
toHandlers
|
|
39
42
|
};
|
|
40
|
-
//# sourceMappingURL=chunk-
|
|
43
|
+
//# sourceMappingURL=chunk-DETES767.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/network.ts"],"sourcesContent":["import { delay, http, HttpResponse } from 'msw';\nimport type { NetworkEntry } from './schema.js';\n\nconst verbs = {\n GET: http.get,\n POST: http.post,\n PUT: http.put,\n PATCH: http.patch,\n DELETE: http.delete,\n HEAD: http.head,\n OPTIONS: http.options,\n} satisfies Record<NetworkEntry['method'], typeof http.get>;\n\n// The worker can't see a request's destination, so code is recognised by its path.\nconst isCode = (href: string) => {\n const { pathname } = new URL(href);\n\n return /\\.(m?[jt]sx?|css|s[ac]ss|less|html?|wasm)$/i.test(pathname)\n || pathname.startsWith('/@') || pathname.includes('/node_modules/');\n};\n\n/** Turn scenario network entries into MSW handlers. First match wins. */\nexport function toHandlers(entries: NetworkEntry[] = []) {\n return entries.map((entry) => {\n const responses = entry.sequence ?? [entry.response!];\n let calls = 0;\n\n return verbs[entry.method](entry.path, async ({ request }): Promise<Response | undefined> => {\n // Fixtures answer the app's requests, never a page load or code: a scenario from a shared\n // link could otherwise serve its own HTML or scripts (a lazy chunk) on the app's origin.\n if (request.mode === 'navigate' || isCode(request.url)) return undefined;\n\n if (entry.query) {\n const params = new URL(request.url).searchParams;\n const matches = Object.entries(entry.query).every(([k, v]) => params.get(k) === v);\n\n if (!matches) return undefined; // fall through to the next handler\n }\n\n const r = responses[Math.min(calls++, responses.length - 1)];\n\n if (r.delay !== undefined) await delay(r.delay);\n // nosniff stops a browser running a fixture as a script, whatever URL loaded it.\n const headers = new Headers(r.headers);\n headers.set('x-content-type-options', 'nosniff');\n\n if (/script|wasm/i.test(headers.get('content-type') ?? '')) return undefined;\n const init = { status: r.status, headers };\n\n if (r.text !== undefined) return HttpResponse.text(r.text, init);\n\n if (r.body === undefined) return new HttpResponse(null, init);\n\n return HttpResponse.json(r.body, init);\n });\n });\n}\n"],"mappings":";AAAA,SAAS,OAAO,MAAM,oBAAoB;AAG1C,IAAM,QAAQ;AAAA,EACZ,KAAK,KAAK;AAAA,EACV,MAAM,KAAK;AAAA,EACX,KAAK,KAAK;AAAA,EACV,OAAO,KAAK;AAAA,EACZ,QAAQ,KAAK;AAAA,EACb,MAAM,KAAK;AAAA,EACX,SAAS,KAAK;AAChB;AAGA,IAAM,SAAS,CAAC,SAAiB;AAC/B,QAAM,EAAE,SAAS,IAAI,IAAI,IAAI,IAAI;AAEjC,SAAO,8CAA8C,KAAK,QAAQ,KAC7D,SAAS,WAAW,IAAI,KAAK,SAAS,SAAS,gBAAgB;AACtE;AAGO,SAAS,WAAW,UAA0B,CAAC,GAAG;AACvD,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,UAAM,YAAY,MAAM,YAAY,CAAC,MAAM,QAAS;AACpD,QAAI,QAAQ;AAEZ,WAAO,MAAM,MAAM,MAAM,EAAE,MAAM,MAAM,OAAO,EAAE,QAAQ,MAAqC;AAG3F,UAAI,QAAQ,SAAS,cAAc,OAAO,QAAQ,GAAG,EAAG,QAAO;AAE/D,UAAI,MAAM,OAAO;AACf,cAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE;AACpC,cAAM,UAAU,OAAO,QAAQ,MAAM,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,CAAC;AAEjF,YAAI,CAAC,QAAS,QAAO;AAAA,MACvB;AAEA,YAAM,IAAI,UAAU,KAAK,IAAI,SAAS,UAAU,SAAS,CAAC,CAAC;AAE3D,UAAI,EAAE,UAAU,OAAW,OAAM,MAAM,EAAE,KAAK;AAE9C,YAAM,UAAU,IAAI,QAAQ,EAAE,OAAO;AACrC,cAAQ,IAAI,0BAA0B,SAAS;AAE/C,UAAI,eAAe,KAAK,QAAQ,IAAI,cAAc,KAAK,EAAE,EAAG,QAAO;AACnE,YAAM,OAAO,EAAE,QAAQ,EAAE,QAAQ,QAAQ;AAEzC,UAAI,EAAE,SAAS,OAAW,QAAO,aAAa,KAAK,EAAE,MAAM,IAAI;AAE/D,UAAI,EAAE,SAAS,OAAW,QAAO,IAAI,aAAa,MAAM,IAAI;AAE5D,aAAO,aAAa,KAAK,EAAE,MAAM,IAAI;AAAA,IACvC,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
|
|
@@ -13,7 +13,7 @@ var isLoopbackSaveUrl = (value) => {
|
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
15
|
var DATA_KEY = "scenario-data";
|
|
16
|
-
function scenarioHref(resolved, { base = location.href, inline = false, strict = false } = {}) {
|
|
16
|
+
function scenarioHref(resolved, { base = location.href, inline = false, strict = false, record = false } = {}) {
|
|
17
17
|
const url = new URL(base);
|
|
18
18
|
url.search = "";
|
|
19
19
|
url.hash = "";
|
|
@@ -28,6 +28,7 @@ function scenarioHref(resolved, { base = location.href, inline = false, strict =
|
|
|
28
28
|
url.searchParams.set(PARAM, resolved.name);
|
|
29
29
|
}
|
|
30
30
|
if (strict) url.searchParams.set(STRICT_PARAM, "");
|
|
31
|
+
if (record) url.searchParams.set(RECORD_PARAM, "");
|
|
31
32
|
return url.toString();
|
|
32
33
|
}
|
|
33
34
|
var readInline = (hash) => new URLSearchParams(hash.slice(1)).get(DATA_KEY);
|
|
@@ -43,4 +44,4 @@ export {
|
|
|
43
44
|
scenarioHref,
|
|
44
45
|
readInline
|
|
45
46
|
};
|
|
46
|
-
//# sourceMappingURL=chunk-
|
|
47
|
+
//# sourceMappingURL=chunk-ESIS3D6R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/link.ts"],"sourcesContent":["import type { Scenario } from './schema.js';\n\ndeclare global {\n /** Requests the active scenario didn't cover, for tests and agents (see `unhandledRequests`). */\n var executableStoriesShowcaseUnhandled: string[] | undefined;\n}\n\nexport const PARAM = 'scenario';\n\n/** Fail unmatched requests instead of letting them reach the network. */\nexport const STRICT_PARAM = 'scenario-strict';\n\n/** Record real responses so they can be saved as a scenario. */\nexport const RECORD_PARAM = 'scenario-record';\n\n/** Dev-server route the `executable-stories-showcase/vite` plugin serves for the panel's \"Save to project\". */\nexport const SAVE_ENDPOINT = '/__executable-stories-showcase/save';\n\nexport const LOOPBACK = new Set(['127.0.0.1', 'localhost', '[::1]']);\n\n/**\n * Whether a save URL the app's save route hands the panel is a loopback save server, such as\n * the one the Next.js route starts on 127.0.0.1. The panel posts captured state there, so a\n * URL on any other host is refused.\n */\nexport const isLoopbackSaveUrl = (value: string): boolean => {\n try {\n const url = new URL(value);\n\n return url.protocol === 'http:' && LOOPBACK.has(url.hostname) && url.pathname === SAVE_ENDPOINT;\n } catch {\n return false;\n }\n};\n\n/** Inline scenario JSON, kept in the URL hash so it never reaches a server. */\nexport const DATA_KEY = 'scenario-data';\n\n/**\n * A shareable URL that opens the app in a resolved scenario.\n * `inline` embeds the flattened scenario, so the link works without the catalog.\n */\nexport function scenarioHref(\n resolved: Scenario,\n { base = location.href, inline = false, strict = false, record = false } = {},\n): string {\n const url = new URL(base);\n url.search = '';\n url.hash = '';\n\n if (resolved.path) url.pathname = resolved.path;\n\n for (const [k, v] of Object.entries(resolved.url ?? {})) {\n if (v !== null) url.searchParams.set(k, v);\n }\n\n if (inline) {\n const { $schema: _schema, extends: _extends, ...snapshot } = resolved;\n url.hash = new URLSearchParams({ [DATA_KEY]: JSON.stringify(snapshot) }).toString();\n } else {\n url.searchParams.set(PARAM, resolved.name);\n }\n\n if (strict) url.searchParams.set(STRICT_PARAM, '');\n\n if (record) url.searchParams.set(RECORD_PARAM, '');\n\n return url.toString();\n}\n\n/** Inline scenario JSON from a URL hash, if present. */\nexport const readInline = (hash: string) => new URLSearchParams(hash.slice(1)).get(DATA_KEY);\n"],"mappings":";AAOO,IAAM,QAAQ;AAGd,IAAM,eAAe;AAGrB,IAAM,eAAe;AAGrB,IAAM,gBAAgB;AAEtB,IAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAO5D,IAAM,oBAAoB,CAAC,UAA2B;AAC3D,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AAEzB,WAAO,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ,KAAK,IAAI,aAAa;AAAA,EACpF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,IAAM,WAAW;AAMjB,SAAS,aACd,UACA,EAAE,OAAO,SAAS,MAAM,SAAS,OAAO,SAAS,OAAO,SAAS,MAAM,IAAI,CAAC,GACpE;AACR,QAAM,MAAM,IAAI,IAAI,IAAI;AACxB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,MAAI,SAAS,KAAM,KAAI,WAAW,SAAS;AAE3C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,OAAO,CAAC,CAAC,GAAG;AACvD,QAAI,MAAM,KAAM,KAAI,aAAa,IAAI,GAAG,CAAC;AAAA,EAC3C;AAEA,MAAI,QAAQ;AACV,UAAM,EAAE,SAAS,SAAS,SAAS,UAAU,GAAG,SAAS,IAAI;AAC7D,QAAI,OAAO,IAAI,gBAAgB,EAAE,CAAC,QAAQ,GAAG,KAAK,UAAU,QAAQ,EAAE,CAAC,EAAE,SAAS;AAAA,EACpF,OAAO;AACL,QAAI,aAAa,IAAI,OAAO,SAAS,IAAI;AAAA,EAC3C;AAEA,MAAI,OAAQ,KAAI,aAAa,IAAI,cAAc,EAAE;AAEjD,MAAI,OAAQ,KAAI,aAAa,IAAI,cAAc,EAAE;AAEjD,SAAO,IAAI,SAAS;AACtB;AAGO,IAAM,aAAa,CAAC,SAAiB,IAAI,gBAAgB,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,QAAQ;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
toHandlers
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DETES767.js";
|
|
4
4
|
import {
|
|
5
5
|
captureScenario,
|
|
6
6
|
redactSecrets,
|
|
7
7
|
unsignedJwt,
|
|
8
8
|
withoutSecretParams
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-C755RCRL.js";
|
|
10
10
|
import {
|
|
11
11
|
checkReadySelector,
|
|
12
12
|
selectorForElement,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
STRICT_PARAM,
|
|
22
22
|
readInline,
|
|
23
23
|
scenarioHref
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-ESIS3D6R.js";
|
|
25
25
|
import {
|
|
26
26
|
createCatalog,
|
|
27
27
|
deepMerge,
|
|
@@ -206,7 +206,7 @@ async function startScenarios(options) {
|
|
|
206
206
|
const boundAdapters = () => [...connected.values()].map((entry) => entry.adapter);
|
|
207
207
|
const setNetwork = async (resolved2) => {
|
|
208
208
|
if (!mocks(resolved2) && !session.strict && !session.recording && !worker) return;
|
|
209
|
-
const [{ toHandlers: toHandlers2 }, msw] = await Promise.all([import("./network-
|
|
209
|
+
const [{ toHandlers: toHandlers2 }, msw] = await Promise.all([import("./network-I5SB4F6T.js"), import("msw")]);
|
|
210
210
|
if (destroyed) return;
|
|
211
211
|
const fallback = msw.http.all("*", ({ request }) => {
|
|
212
212
|
const { pathname } = new URL(request.url);
|
|
@@ -259,7 +259,7 @@ async function startScenarios(options) {
|
|
|
259
259
|
const nextKeys = Object.keys(resolved2.state ?? {});
|
|
260
260
|
named(resolved2);
|
|
261
261
|
const mocksNetwork = worker !== null || mocks(resolved2) || session.strict;
|
|
262
|
-
const href = scenarioHref(resolved2, { inline, strict: session.strict });
|
|
262
|
+
const href = scenarioHref(resolved2, { inline, strict: session.strict, record: session.recording });
|
|
263
263
|
const inPlace = prev !== null && new URL(href).pathname === location.pathname && JSON.stringify(appParams(location.search)) === JSON.stringify(sortedParams(resolved2.url)) && (!mocksNetwork || !!options.refresh) && Object.keys(prev.state ?? {}).every((k) => nextKeys.includes(k)) && nextKeys.every((k) => connected.get(k)?.adapter.inPlace === true);
|
|
264
264
|
if (!inPlace) {
|
|
265
265
|
navigate(href);
|
|
@@ -379,7 +379,7 @@ async function startScenarios(options) {
|
|
|
379
379
|
return check.status === "match" ? { status: "match", count: check.count, visible: check.visible, suggestions } : { ...check, suggestions };
|
|
380
380
|
},
|
|
381
381
|
async capture({ extends: base, ...options2 }) {
|
|
382
|
-
const { captureScenario: captureScenario2 } = await import("./capture-
|
|
382
|
+
const { captureScenario: captureScenario2 } = await import("./capture-CIKR46QE.js");
|
|
383
383
|
await session.settleRecording();
|
|
384
384
|
return captureScenario2(session, { ...options2, base: base ? lookup(catalog, base) : void 0 }).forCatalog;
|
|
385
385
|
}
|
|
@@ -427,7 +427,7 @@ var replaceUrl = (url) => {
|
|
|
427
427
|
history.replaceState(__NA === void 0 ? state : rest, "", url);
|
|
428
428
|
};
|
|
429
429
|
var byKey = ([a], [b]) => a.localeCompare(b);
|
|
430
|
-
var appParams = (search) => [...new URLSearchParams(search)].filter(([k]) => k !== PARAM && k !== STRICT_PARAM).toSorted(byKey);
|
|
430
|
+
var appParams = (search) => [...new URLSearchParams(search)].filter(([k]) => k !== PARAM && k !== STRICT_PARAM && k !== RECORD_PARAM).toSorted(byKey);
|
|
431
431
|
var sortedParams = (url) => Object.entries(url ?? {}).filter(([, v]) => v !== null).toSorted(byKey);
|
|
432
432
|
function navigate(href) {
|
|
433
433
|
const next = new URL(href, location.href);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/record.ts","../src/runtime.ts"],"sourcesContent":["import { SAVE_ENDPOINT } from './link.js';\nimport { redactSecrets, withoutSecretParams } from './redact.js';\nimport { type Json, MethodSchema, type NetworkEntry, parseJson } from './schema.js';\n\n/** One real response, placed by when its request started. */\nexport interface RecordedCall {\n /** Request start order: responses can arrive in any order. */\n order: number;\n method: NetworkEntry['method'];\n path: string;\n query: Record<string, string>;\n status: number;\n body?: Json;\n}\n\n/**\n * Records responses that reached the real network, from MSW's lifecycle events. Only JSON is\n * kept: pages, scripts and other assets aren't app state.\n */\nexport function createRecorder() {\n const started = new Map<string, number>();\n const calls: RecordedCall[] = [];\n const pending = new Set<Promise<void>>();\n let next = 0;\n\n return {\n start(requestId: string) {\n started.set(requestId, next++);\n },\n /** A mocked response: nothing to record. */\n drop(requestId: string) {\n started.delete(requestId);\n },\n bypass(requestId: string, request: Request, response: Response): Promise<void> {\n const order = started.get(requestId) ?? next++;\n started.delete(requestId);\n const method = MethodSchema.safeParse(request.method);\n // The panel's own save calls aren't part of the app's traffic.\n const own = new URL(request.url).pathname.startsWith(SAVE_ENDPOINT);\n\n if (own || !method.success || !response.headers.get('content-type')?.includes('json')) {\n return Promise.resolve();\n }\n\n const url = new URL(request.url);\n\n // Scenario queries hold one value per key.\n if (new Set(url.searchParams.keys()).size < [...url.searchParams.keys()].length) {\n console.warn(`[executable-stories-showcase] not recorded: ${request.method} ${url} repeats a query key`);\n\n return Promise.resolve();\n }\n\n const read = response.clone().text().then((text) => {\n const call: RecordedCall = {\n order,\n method: method.data,\n path: url.origin === location.origin ? url.pathname : url.origin + url.pathname,\n query: withoutSecretParams(Object.fromEntries(url.searchParams), `${method.data} ${url.pathname}`),\n status: response.status,\n };\n\n // Parsed before the push, so a body that fails to parse leaves no half-recorded call.\n if (text) {\n // Recorded scenarios get committed, linked and pasted into chats: keep secrets out.\n const { value, redacted } = redactSecrets(parseJson(text));\n call.body = value;\n\n if (redacted.length) {\n console.info(`[executable-stories-showcase] redacted ${redacted.join(', ')} in ${method.data} ${call.path}`);\n }\n }\n\n calls.push(call);\n }).catch((err) => console.warn('[executable-stories-showcase] skipped a recorded response', err))\n .finally(() => pending.delete(read));\n\n pending.add(read);\n\n return read;\n },\n /** Responses whose bodies have been read, as network entries. */\n entries: () => toNetwork(calls),\n /** Wait for bodies still being read. */\n settled: async () => {\n await Promise.all(pending);\n },\n };\n}\n\n/**\n * Recorded calls as scenario network entries: repeated calls to one endpoint become a\n * `sequence` in request order, or a single `response` when every call answered the same.\n */\nexport function toNetwork(calls: RecordedCall[]): NetworkEntry[] {\n const groups = new Map<string, RecordedCall[]>();\n\n for (const call of calls.toSorted((a, b) => a.order - b.order)) {\n // Sorted, because replay matches query params in any order.\n const query = Object.fromEntries(Object.entries(call.query).toSorted(([a], [b]) => a.localeCompare(b)));\n const key = JSON.stringify([call.method, call.path, query]);\n groups.set(key, [...(groups.get(key) ?? []), { ...call, query }]);\n }\n\n return [...groups.values()]\n .map(([first, ...rest]) => {\n const responses = [first, ...rest].map(({ status, body }) => ({ status, body }));\n const same = responses.every((r) => JSON.stringify(r) === JSON.stringify(responses[0]));\n const entry: NetworkEntry = { method: first.method, path: first.path };\n\n if (Object.keys(first.query).length) entry.query = first.query;\n\n if (same) entry.response = responses[0];\n else entry.sequence = responses;\n\n return entry;\n })\n // An entry without `query` matches any query string: try the more specific entries first.\n .toSorted((a, b) => Object.keys(b.query ?? {}).length - Object.keys(a.query ?? {}).length);\n}\n","import type { RequestHandler } from 'msw';\nimport type { SetupWorker } from 'msw/browser';\nimport type { z } from 'zod';\nimport { type Catalog, createCatalog, type ScenarioRule } from './catalog.js';\nimport {\n DATA_KEY,\n LOOPBACK,\n PARAM,\n readInline,\n RECORD_PARAM,\n SAVE_ENDPOINT,\n scenarioHref,\n STRICT_PARAM,\n} from './link.js';\nimport { createRecorder } from './record.js';\nimport {\n type Json,\n type NetworkEntry,\n parseJson,\n parseScenario,\n type Scenario,\n type ScenarioInput,\n type Serializable,\n} from './schema.js';\n\nconst STORAGE_KEY = 'executable-stories-showcase:active';\n\ninterface AdapterBase {\n /** Key under `scenario.state` this adapter reads. */\n key: string;\n /** Current state as JSON, for the panel's live editor and \"Save as scenario\". */\n read?(): Json;\n /** Call `onChange` when the state changes; return an unsubscribe. */\n subscribe?(onChange: () => void): () => void;\n /** False when new state only takes effect on a reload, e.g. a snapshot an actor starts from. */\n inPlace?: boolean;\n /**\n * False to apply this state from catalog scenarios only, and skip it for inline links.\n * For state that outlives the scenario, such as a cookie the real backend reads.\n */\n fromLinks?: boolean;\n}\n\n/** Decodes scenario JSON with `schema`, so `apply` receives typed state. */\nexport interface TypedAdapter<T> extends AdapterBase {\n /** Also feeds `scenarioJsonSchema`. */\n schema: z.ZodType<T>;\n apply(state: T): void | Promise<void>;\n}\n\n/** Receives the scenario's JSON as it is. */\nexport interface JsonAdapter extends AdapterBase {\n schema?: undefined;\n apply(state: Json): void | Promise<void>;\n}\n\n/** Connects one slice of app state (a store, a cache, a reducer) to scenarios and the panel. */\nexport type Adapter<T = Json> = TypedAdapter<T> | JsonAdapter;\n\nexport interface Connection<T extends Serializable> {\n /** Push the app's current state (for state the library can't `read`, e.g. useReducer). */\n update(state: T): void;\n disconnect(): void;\n}\n\nexport interface StartOptions {\n /** Scenario objects, e.g. `import.meta.glob<ScenarioInput>('./scenarios/*.json')`, or built in code. */\n scenarios: ScenarioInput[];\n /** Scenario to use when the URL names none. Omit to leave the app untouched. */\n defaultScenario?: string;\n /** Adapters available before render. Components can `connect` more later. */\n adapters?: Adapter<unknown>[];\n rules?: ScenarioRule[];\n /**\n * Open inline share links (`#scenario-data=...`). Anyone can write one, and it fakes API\n * responses for whoever opens it, so by default they only open on localhost.\n * Pass `true` on a staging or preview deploy that should open them; `false` refuses them everywhere.\n */\n allowLinks?: boolean;\n /**\n * Answer unmatched requests with 501 instead of passing them to the real network.\n * Assets and page loads always pass. Also switched on by `?scenario-strict`.\n */\n strict?: boolean;\n /**\n * Let unmatched requests reach the real network and record their JSON responses, so\n * \"Save as scenario\" can replay them offline. Also switched on by `?scenario-record`.\n */\n record?: boolean;\n /**\n * Requests that may reach the real network in strict mode (URL substring or RegExp),\n * e.g. images from a CDN or a maps API the screen needs.\n */\n allow?: (string | RegExp)[];\n /**\n * Requests the scenario doesn't care about, such as analytics, error reporting and captcha\n * checks (URL substring or RegExp). They are never reported as unhandled, and in strict mode\n * they get an empty 204 instead of a 501, so SDKs don't log errors or retry.\n */\n ignore?: (string | RegExp)[];\n /**\n * MSW handlers a scenario can use by name with `\"handlers\": [\"name\"]`, e.g. the ones your\n * unit tests already use. They answer after the scenario's `network` entries.\n */\n handlers?: Record<string, RequestHandler | RequestHandler[]>;\n /**\n * Called after an in-place switch changes network fixtures, so the app refetches,\n * e.g. `() => queryClient.resetQueries()`. Without it, such switches reload the page.\n */\n refresh?: () => void | Promise<void>;\n /**\n * The app's own MSW worker (or a `setupServer()` in tests), instead of starting one.\n * Its handlers stay active beneath the scenario's, and are restored by `destroy()`.\n * The app owns its lifecycle: start it before `startScenarios`.\n */\n worker?: MswWorker;\n /** Passed to `worker.start()` when executable-stories-showcase starts its own worker. */\n workerOptions?: Parameters<SetupWorker['start']>[0];\n}\n\n/** The parts of an MSW worker or server executable-stories-showcase uses. `events` is needed to record. */\nexport type MswWorker = Pick<SetupWorker, 'resetHandlers' | 'listHandlers'> & {\n events?: Pick<RecordingEvents, 'on' | 'removeListener'>;\n};\n\ntype StartListener = (e: { requestId: string; }) => void;\n\ntype ResponseListener = (e: { requestId: string; request: Request; response: Response; }) => void;\n\n/** MSW's lifecycle events that recording listens to, as both MSW 2 and 3 type them. */\ninterface RecordingEvents {\n on(event: 'request:start', listener: StartListener): void;\n on(event: 'response:mocked' | 'response:bypass', listener: ResponseListener): void;\n removeListener(event: 'request:start', listener: StartListener): void;\n removeListener(event: 'response:mocked' | 'response:bypass', listener: ResponseListener): void;\n}\n\nexport interface ScenarioSession {\n catalog: Catalog;\n /** The scenario as authored (with `extends`), or null when none is active. */\n active: Scenario | null;\n /** The active scenario with `extends` flattened. */\n resolved: Scenario | null;\n strict: boolean;\n /** Non-asset requests no scenario entry matched (live list). */\n unhandled: string[];\n /** Whether real responses are being recorded (`record` or `?scenario-record`). */\n recording: boolean;\n /** Real responses recorded so far, as network entries. Await `settleRecording()` first for all of them. */\n recorded(): NetworkEntry[];\n /** Resolves once every recorded response body has been read. */\n settleRecording(): Promise<void>;\n /** Keys of connected adapters. */\n keys(): string[];\n /** The connected adapter's current state, as JSON. */\n read(key: string): Json | undefined;\n /** Validate and apply state to a connected adapter now, without changing scenario. */\n applyState(key: string, state: Json): Promise<void>;\n /** Connect an adapter; the active scenario's state for its key is applied at once. */\n connect<T extends Serializable>(adapter: Adapter<T>): Connection<T>;\n /**\n * Switch to a catalog scenario: in place when only connected state (or network, with\n * `refresh`) changes, otherwise by reloading. Names combine: \"signed-in,server-error\".\n */\n open(names: string): Promise<void>;\n /** Switch to a scenario that isn't in the catalog, e.g. one edited in the panel. */\n openInline(scenario: Scenario): Promise<void>;\n onChange(listener: () => void): () => void;\n /**\n * Tear down: unsubscribe adapters, drop listeners and queued switches, and stop the\n * worker this session started (or restore the app's own worker's handlers).\n * For HMR and unmounting. The page keeps whatever state it shows.\n */\n destroy(): void;\n}\n\n/** An adapter with its type erased behind decode-then-apply. */\ninterface Bound {\n key: string;\n /** Validate now; the returned function applies. */\n decode(state: Json, scenario: string): () => void | Promise<void>;\n /** Undefined when the adapter can't read (state is pushed through `connection.update`). */\n read(): Json | undefined;\n subscribe(onChange: () => void): (() => void) | undefined;\n inPlace: boolean;\n fromLinks: boolean;\n}\n\nfunction bind<T>(adapter: Adapter<T>): Bound {\n return {\n key: adapter.key,\n read: () => adapter.read?.(),\n subscribe: (onChange) => adapter.subscribe?.(onChange),\n inPlace: adapter.inPlace !== false,\n fromLinks: adapter.fromLinks !== false,\n decode(state, scenario) {\n if (adapter.schema === undefined) return () => adapter.apply(state);\n const parsed = adapter.schema.safeParse(state);\n\n if (!parsed.success) {\n throw new Error(`Scenario \"${scenario}\": invalid state[\"${adapter.key}\"]: ${parsed.error.message}`);\n }\n\n return () => adapter.apply(parsed.data);\n },\n };\n}\n\ninterface Connected {\n adapter: Bound;\n /** Last state pushed through `connection.update`; converted to JSON when read. */\n value?: Serializable;\n unsubscribe?: () => void;\n}\n\n/**\n * What the CLI's `shoot`, `doctor` and `mcp` call inside the page, as JSON in and out.\n * The modules behind it load on first call, so apps that never use it don't pay for it.\n */\nexport interface PageHooks {\n /** The active scenario's name, or null. */\n scenario(): string | null;\n /** Whether a ready selector matches now, plus selectors that would. */\n checkReady(selector: string): Promise<ReadyReport>;\n /** The app's current state as a catalog file, after recorded responses settle. */\n capture(\n options: { name: string; description?: string; ready?: string; extends?: string; },\n ): Promise<ScenarioInput>;\n}\n\nexport interface ReadyReport {\n status: 'match' | 'none' | 'unchecked' | 'invalid';\n count?: number;\n visible?: boolean;\n error?: string;\n /** Distinctive selectors on the page now, best first. */\n suggestions: string[];\n}\n\ndeclare global {\n /** Set by `startScenarios` for the CLI and agents; see `PageHooks`. */\n var executableStoriesShowcasePage: PageHooks | undefined;\n}\n\nlet current: ScenarioSession | null = null;\n\n/** The session whose scenario the <html> markers describe; only it may clear them. */\nlet markerOwner: ScenarioSession | null = null;\n\n/** The session started by `startScenarios`, if any (null in production builds that skip it). */\nexport const currentSession = () => current;\n\ninterface Selection {\n name?: string;\n data?: string;\n strict?: boolean;\n}\n\n/**\n * Read the scenario from the URL (or the tab's last one), mock the network if the\n * scenario needs it, apply URL and adapter state. Await this before rendering the app.\n */\nexport async function startScenarios(options: StartOptions): Promise<ScenarioSession> {\n const catalog = createCatalog(options.scenarios, { rules: options.rules });\n const url = new URL(location.href);\n const selection = select(url, options.defaultScenario, options.allowLinks ?? LOOPBACK.has(url.hostname));\n const unhandled: string[] = [];\n globalThis.executableStoriesShowcaseUnhandled = unhandled;\n\n const connected = new Map<string, Connected>();\n const listeners = new Set<() => void>();\n\n // One failing listener (a panel, an app hook) must not stop the others or fail a switch.\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch (err) {\n console.error('[executable-stories-showcase] onChange listener failed', err);\n }\n }\n };\n\n const recorder = createRecorder();\n // Set while recording listeners are attached; removes them.\n let stopListening: (() => void) | null = null;\n\n const listen = (target: MswWorker) => {\n if (!session.recording || stopListening) return;\n\n if (!target.events) {\n console.warn('[executable-stories-showcase] recording needs a worker with `events`');\n\n return;\n }\n\n const { events } = target;\n // Positions are taken when requests start, so a slow response keeps its place.\n const start: StartListener = ({ requestId }) => recorder.start(requestId);\n const mocked: ResponseListener = ({ requestId }) => recorder.drop(requestId);\n\n const bypass: ResponseListener = ({ requestId, request, response }) => {\n void recorder.bypass(requestId, request, response).then(emit);\n };\n\n events.on('request:start', start);\n events.on('response:mocked', mocked);\n events.on('response:bypass', bypass);\n\n stopListening = () => {\n events.removeListener('request:start', start);\n events.removeListener('response:mocked', mocked);\n events.removeListener('response:bypass', bypass);\n stopListening = null;\n };\n };\n\n /** The scenario's named handlers, in order. */\n const named = (resolved: Scenario) =>\n (resolved.handlers ?? []).flatMap((name) => {\n const found = options.handlers?.[name];\n\n if (!found) {\n const known = Object.keys(options.handlers ?? {}).join(', ') || 'none';\n throw new Error(\n `Scenario \"${resolved.name}\" uses unknown handler \"${name}\". Known handlers: ${known}`,\n );\n }\n\n return found;\n });\n\n // The app's worker, if given, else one this session starts on first need.\n let worker: MswWorker | null = options.worker ?? null;\n let ownWorker: SetupWorker | null = null;\n const appHandlers = options.worker ? [...options.worker.listHandlers()] : [];\n let destroyed = false;\n\n // Whether the active scenario came from an inline link.\n let fromLink = false;\n\n /** Validate every adapter's state up front, so nothing changes if any of it is invalid. */\n const prepareStates = (resolved: Scenario, adapters: Bound[], link = fromLink) =>\n adapters.flatMap((adapter) => {\n const state = resolved.state?.[adapter.key];\n\n if (state === undefined) return [];\n\n if (link && !adapter.fromLinks) {\n console.warn(`[executable-stories-showcase] ignored state.${adapter.key}: a link can't set it`);\n\n return [];\n }\n\n return [adapter.decode(state, resolved.name)];\n });\n\n const applyPrepared = async (prepared: ReturnType<typeof prepareStates>) => {\n for (const apply of prepared) {\n // Destroyed while an earlier adapter was applying: leave the rest untouched.\n if (destroyed) return;\n await apply();\n }\n };\n\n const boundAdapters = () => [...connected.values()].map((entry) => entry.adapter);\n\n // MSW loads only when a scenario mocks the network or strict mode needs a catch-all.\n const setNetwork = async (resolved: Scenario) => {\n if (!mocks(resolved) && !session.strict && !session.recording && !worker) return;\n const [{ toHandlers }, msw] = await Promise.all([import('./network.js'), import('msw')]);\n\n if (destroyed) return;\n\n const fallback = msw.http.all('*', ({ request }) => {\n const { pathname } = new URL(request.url);\n const isPage = request.mode === 'navigate' || !!request.headers.get('accept')?.includes('text/html');\n\n if (\n isPage || isAsset(request.url) || pathname.startsWith('/@') || pathname.startsWith(SAVE_ENDPOINT)\n || matches(options.allow, request.url)\n ) {\n return msw.passthrough();\n }\n\n if (matches(options.ignore, request.url)) {\n return session.strict ? new msw.HttpResponse(null, { status: 204 }) : msw.passthrough();\n }\n\n const label = `${request.method} ${request.url}`;\n unhandled.push(label);\n emit();\n\n if (!session.strict) return msw.passthrough();\n console.error(`[executable-stories-showcase] \"${resolved.name}\" has no handler for ${label}`);\n\n return msw.HttpResponse.json({ message: `No scenario handler for ${label}` }, { status: 501 });\n });\n\n // Scenario first, then its named handlers, then the app's own, then the catch-all.\n const handlers = [...toHandlers(resolved.network), ...named(resolved), ...appHandlers, fallback];\n\n if (worker) {\n worker.resetHandlers(...handlers);\n listen(worker);\n\n return;\n }\n\n const { setupWorker } = await import('#msw-browser');\n const started = setupWorker(...handlers);\n await started.start({ quiet: true, ...options.workerOptions });\n\n // Destroyed while starting: don't leave a worker running.\n if (destroyed) {\n void started.stop();\n\n return;\n }\n\n worker = ownWorker = started;\n listen(started);\n };\n\n const activate = (active: Scenario, resolved: Scenario, link: boolean) => {\n fromLink = link;\n session.active = active;\n session.resolved = resolved;\n // Tests and agents wait on these: html[data-scenario=\"name\"], then the ready selector.\n const html = document.documentElement;\n markerOwner = session;\n html.dataset.scenario = resolved.name;\n\n if (resolved.ready) html.dataset.scenarioReady = resolved.ready;\n else delete html.dataset.scenarioReady;\n };\n\n let transition = 0;\n let queue: Promise<void> = Promise.resolve();\n\n const switchTo = async (next: Scenario, inline: boolean) => {\n const resolved = catalog.resolve(next);\n // Validate before choosing reload or in place: a rejected switch must leave the app as it was.\n const prepared = prepareStates(resolved, boundAdapters(), inline);\n\n const prev = session.resolved;\n const nextKeys = Object.keys(resolved.state ?? {});\n named(resolved); // throws on an unknown handler name, before anything changes\n // Opening a scenario always restarts its network: fresh handler sequences, refetched data.\n const mocksNetwork = worker !== null || mocks(resolved) || session.strict;\n\n const href = scenarioHref(resolved, { inline, strict: session.strict });\n\n const inPlace = prev !== null\n // Compare with the app's actual URL: URL-driven UI only resets on a reload.\n && new URL(href).pathname === location.pathname\n && JSON.stringify(appParams(location.search)) === JSON.stringify(sortedParams(resolved.url))\n && (!mocksNetwork || !!options.refresh)\n // Can't un-apply state, and can't apply state nobody is connected to receive.\n && Object.keys(prev.state ?? {}).every((k) => nextKeys.includes(k))\n && nextKeys.every((k) => connected.get(k)?.adapter.inPlace === true);\n\n if (!inPlace) {\n navigate(href);\n\n return;\n }\n\n // After every await: a destroyed session must not touch the app, URL or storage.\n if (mocksNetwork) {\n await setNetwork(resolved);\n\n if (destroyed) return;\n // Kick off the refetch but don't wait: a scenario may hold requests open forever.\n Promise.resolve(options.refresh?.()).catch((err) => console.error('[executable-stories-showcase]', err));\n }\n\n await applyPrepared(prepared);\n\n if (destroyed) return;\n unhandled.splice(0);\n activate(next, resolved, inline);\n const at = new URL(href);\n replaceUrl(at);\n const data = readInline(at.hash);\n store(data ? { data, strict: session.strict } : { name: next.name, strict: session.strict });\n emit();\n };\n\n // Switches run one at a time, and one superseded while queued is skipped,\n // so a slow earlier switch can never land on top of a later one.\n const enqueue = (next: () => Scenario, inline: boolean) => {\n if (destroyed) return Promise.reject(new Error('This scenario session was destroyed'));\n const id = ++transition;\n const run = queue.then(() => (id === transition ? switchTo(next(), inline) : undefined));\n queue = run.catch(() => {});\n\n return run;\n };\n\n const attach = (adapter: Bound): Connection<Serializable> => {\n const entry: Connected = { adapter };\n connected.set(adapter.key, entry);\n entry.unsubscribe = adapter.subscribe?.(emit);\n emit();\n\n return {\n update(state) {\n entry.value = state;\n emit();\n },\n disconnect() {\n entry.unsubscribe?.();\n\n if (connected.get(adapter.key) === entry) connected.delete(adapter.key);\n emit();\n },\n };\n };\n\n const session: ScenarioSession = {\n catalog,\n active: null,\n resolved: null,\n // Read apart from the selection: `?scenario-strict` alone still blocks the real network.\n strict: options.strict ?? (!!selection?.strict || url.searchParams.has(STRICT_PARAM)),\n unhandled,\n // Held in memory: a full page load starts a new recording.\n recording: options.record ?? url.searchParams.has(RECORD_PARAM),\n recorded: recorder.entries,\n settleRecording: recorder.settled,\n keys: () => [...connected.keys()],\n read(key) {\n const entry = connected.get(key);\n const read = entry?.adapter.read();\n\n if (read !== undefined) return read;\n\n return entry?.value === undefined ? undefined : parseJson(JSON.stringify(entry.value));\n },\n async applyState(key, state) {\n const entry = connected.get(key);\n\n if (!entry) throw new Error(`No adapter connected for \"${key}\"`);\n await entry.adapter.decode(state, session.active?.name ?? 'live')();\n },\n connect(adapter) {\n const bound = bind(adapter);\n const noop = { update() {}, disconnect() {} };\n\n if (destroyed) return noop;\n const connection = attach(bound);\n\n if (session.resolved) {\n applyPrepared(prepareStates(session.resolved, [bound])).catch((err) =>\n console.error('[executable-stories-showcase]', err)\n );\n }\n\n return connection;\n },\n open: (names) => enqueue(() => lookup(catalog, names), false),\n openInline: (scenario) => enqueue(() => scenario, true),\n onChange(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n transition++; // drop queued switches\n\n for (const entry of connected.values()) entry.unsubscribe?.();\n connected.clear();\n listeners.clear();\n\n // An app-owned worker outlives the session: detach from it.\n stopListening?.();\n\n if (ownWorker) void ownWorker.stop();\n else worker?.resetHandlers(...appHandlers);\n worker = ownWorker = null;\n\n if (current === session) current = null;\n\n if (globalThis.executableStoriesShowcaseUnhandled === unhandled) globalThis.executableStoriesShowcaseUnhandled = undefined;\n\n if (globalThis.executableStoriesShowcasePage === hooks) globalThis.executableStoriesShowcasePage = undefined;\n\n if (markerOwner === session) {\n markerOwner = null;\n delete document.documentElement.dataset.scenario;\n delete document.documentElement.dataset.scenarioReady;\n }\n },\n };\n\n current = session;\n\n const hooks: PageHooks = {\n scenario: () => session.resolved?.name ?? null,\n async checkReady(selector) {\n const { checkReadySelector, suggestReadySelectors } = await import('./ready.js');\n const check = checkReadySelector(selector);\n const suggestions = suggestReadySelectors(null, { limit: 5 });\n\n return check.status === 'match'\n ? { status: 'match', count: check.count, visible: check.visible, suggestions }\n : { ...check, suggestions };\n },\n async capture({ extends: base, ...options }) {\n const { captureScenario } = await import('./capture.js');\n await session.settleRecording();\n\n return captureScenario(session, { ...options, base: base ? lookup(catalog, base) : undefined })\n .forCatalog;\n },\n };\n\n globalThis.executableStoriesShowcasePage = hooks;\n\n // Adapters connect whether or not a scenario is selected, so the plain app can be\n // inspected and captured too. Only applying scenario state is conditional.\n const initial = (options.adapters ?? []).map(bind);\n\n for (const adapter of initial) attach(adapter);\n\n if (!selection) {\n // Nothing to mock, but recording needs the worker to see the real responses,\n // and strict mode needs its catch-all.\n if (session.recording || session.strict) await setNetwork({ name: 'live' });\n\n return session;\n }\n\n // A selection that doesn't load must not stick to the tab and break every later page load.\n const loaded = (() => {\n try {\n const active = selection.data\n ? parseScenario(JSON.parse(selection.data), 'inline scenario')\n : lookup(catalog, selection.name ?? '');\n\n const resolved = catalog.resolve(active);\n named(resolved);\n\n return { active, resolved, prepared: prepareStates(resolved, initial, !!selection.data) };\n } catch (err) {\n store(null);\n throw err;\n }\n })();\n\n const { active, resolved, prepared } = loaded;\n await setNetwork(resolved);\n\n if (destroyed) return session;\n\n // Only fill params that are missing, so in-app navigation survives a reload.\n for (const [k, v] of Object.entries(resolved.url ?? {})) {\n if (v !== null && !url.searchParams.has(k)) url.searchParams.set(k, v);\n }\n\n replaceUrl(url);\n // Apply (awaiting async adapters) before the app renders.\n await applyPrepared(prepared);\n\n if (destroyed) return session;\n activate(active, resolved, !!selection.data);\n emit();\n\n return session;\n}\n\nconst mocks = (s: Scenario) => !!s.network?.length || !!s.handlers?.length;\n\nconst matches = (rules: (string | RegExp)[] = [], url: string) =>\n rules.some((rule) => (rule instanceof RegExp ? rule.test(url) : url.includes(rule)));\n\n/** msw's `isCommonAssetRequest`, inlined so msw 2.4 and later all work. */\nconst isAsset = (href: string) => {\n const url = new URL(href);\n\n return url.protocol === 'file:' || url.hostname === 'fonts.googleapis.com'\n || url.pathname.includes('node_modules') || url.pathname.includes('@vite')\n || /\\.(s?css|less|m?jsx?|m?tsx?|html|ttf|otf|woff2?|eot|gif|jpe?g|png|avif|webp|svg|mp4|webm|ogg|mov|mp3|wav|flac|aac|pdf|txt|csv|json|xml|md|zip|tar|gz|rar|7z)$/i\n .test(url.pathname);\n};\n\n// Keeps the router's history state. Next.js ignores replaceState calls whose state carries its\n// own marker (__NA), so drop that marker: Next then re-adds its state and updates useSearchParams.\nconst replaceUrl = (url: URL) => {\n const state: unknown = history.state;\n const { __NA, ...rest }: { __NA?: unknown; } = Object(state);\n\n history.replaceState(__NA === undefined ? state : rest, '', url);\n};\n\nconst byKey = ([a]: [string, string | null], [b]: [string, string | null]) => a.localeCompare(b);\n\n/** The app's own query params (scenario params excluded), sorted for comparison. */\nconst appParams = (search: string) =>\n [...new URLSearchParams(search)].filter(([k]) => k !== PARAM && k !== STRICT_PARAM).toSorted(byKey);\n\nconst sortedParams = (url: Scenario['url']) =>\n Object.entries(url ?? {}).filter(([, v]) => v !== null).toSorted(byKey);\n\n/**\n * Go to a scenario link, reloading even when only the hash changes. Scenario links stay on the\n * app, so another origin (or a `javascript:` URL, whose origin is `null`) is refused.\n */\nexport function navigate(href: string) {\n const next = new URL(href, location.href);\n\n if (next.origin !== location.origin) {\n throw new Error(`[executable-stories-showcase] navigate: ${next.origin} is not this app (${location.origin})`);\n }\n\n if (next.pathname + next.search === location.pathname + location.search) {\n replaceUrl(next);\n location.reload();\n } else {\n location.href = next.toString();\n }\n}\n\n/**\n * URL first (`?scenario=name` or `#scenario-data=...`), then the tab's last\n * selection, so navigation that drops the query keeps the scenario.\n * `?scenario=` (empty) resets to the default.\n */\nfunction select(url: URL, fallback: string | undefined, allowLinks: boolean): Selection | null {\n let data = readInline(url.hash);\n\n if (data && !allowLinks) {\n refuseLink(url);\n data = null;\n }\n const name = url.searchParams.get(PARAM);\n const strict = url.searchParams.has(STRICT_PARAM);\n\n let selection: Selection | null = null;\n\n if (data) selection = { data, strict };\n else if (name) selection = { name, strict };\n else if (name === '') {\n url.searchParams.delete(PARAM);\n store(null);\n } else {\n let stored = load();\n\n if (stored?.data && !allowLinks) {\n refuseLink(url);\n store(null);\n stored = null;\n }\n\n // An explicit ?scenario-strict in the current URL wins over the stored flag.\n selection = stored && { ...stored, strict: stored.strict || strict };\n\n // Restore the selection into the URL so it stays shareable.\n if (selection?.name) url.searchParams.set(PARAM, selection.name);\n\n if (selection?.data) url.hash = new URLSearchParams({ [DATA_KEY]: selection.data }).toString();\n\n if (selection?.strict) url.searchParams.set(STRICT_PARAM, '');\n }\n\n if (selection) store(selection);\n\n return selection ?? (fallback ? { name: fallback, strict } : null);\n}\n\nfunction refuseLink(url: URL) {\n url.hash = '';\n console.warn(\n `[executable-stories-showcase] ignored an inline scenario link: links only open on localhost unless startScenarios({ allowLinks: true })`,\n );\n}\n\n/** One name, or several joined with commas: later ones win, like `extends`. */\nfunction lookup(catalog: Catalog, names: string): Scenario {\n const list = names.split(',').map((n) => n.trim());\n\n for (const name of list) {\n if (catalog.get(name)) continue;\n const known = catalog.list.map((s) => s.name).join(', ');\n throw new Error(`Unknown scenario \"${name}\". Known scenarios: ${known}`);\n }\n\n const [only] = list;\n const single = list.length === 1 && only !== undefined ? catalog.get(only) : undefined;\n\n return single ?? { name: list.join(','), extends: list };\n}\n\nfunction store(selection: Selection | null) {\n try {\n if (selection) sessionStorage.setItem(STORAGE_KEY, JSON.stringify(selection));\n else sessionStorage.removeItem(STORAGE_KEY);\n } catch {\n // sessionStorage throws when storage is blocked; the selection just is not kept\n }\n}\n\nfunction load(): Selection | null {\n try {\n return JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? 'null');\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["resolved","prepared","toHandlers","active","checkReadySelector","suggestReadySelectors","options","captureScenario"]}
|
|
1
|
+
{"version":3,"sources":["../src/record.ts","../src/runtime.ts"],"sourcesContent":["import { SAVE_ENDPOINT } from './link.js';\nimport { redactSecrets, withoutSecretParams } from './redact.js';\nimport { type Json, MethodSchema, type NetworkEntry, parseJson } from './schema.js';\n\n/** One real response, placed by when its request started. */\nexport interface RecordedCall {\n /** Request start order: responses can arrive in any order. */\n order: number;\n method: NetworkEntry['method'];\n path: string;\n query: Record<string, string>;\n status: number;\n body?: Json;\n}\n\n/**\n * Records responses that reached the real network, from MSW's lifecycle events. Only JSON is\n * kept: pages, scripts and other assets aren't app state.\n */\nexport function createRecorder() {\n const started = new Map<string, number>();\n const calls: RecordedCall[] = [];\n const pending = new Set<Promise<void>>();\n let next = 0;\n\n return {\n start(requestId: string) {\n started.set(requestId, next++);\n },\n /** A mocked response: nothing to record. */\n drop(requestId: string) {\n started.delete(requestId);\n },\n bypass(requestId: string, request: Request, response: Response): Promise<void> {\n const order = started.get(requestId) ?? next++;\n started.delete(requestId);\n const method = MethodSchema.safeParse(request.method);\n // The panel's own save calls aren't part of the app's traffic.\n const own = new URL(request.url).pathname.startsWith(SAVE_ENDPOINT);\n\n if (own || !method.success || !response.headers.get('content-type')?.includes('json')) {\n return Promise.resolve();\n }\n\n const url = new URL(request.url);\n\n // Scenario queries hold one value per key.\n if (new Set(url.searchParams.keys()).size < [...url.searchParams.keys()].length) {\n console.warn(`[executable-stories-showcase] not recorded: ${request.method} ${url} repeats a query key`);\n\n return Promise.resolve();\n }\n\n const read = response.clone().text().then((text) => {\n const call: RecordedCall = {\n order,\n method: method.data,\n path: url.origin === location.origin ? url.pathname : url.origin + url.pathname,\n query: withoutSecretParams(Object.fromEntries(url.searchParams), `${method.data} ${url.pathname}`),\n status: response.status,\n };\n\n // Parsed before the push, so a body that fails to parse leaves no half-recorded call.\n if (text) {\n // Recorded scenarios get committed, linked and pasted into chats: keep secrets out.\n const { value, redacted } = redactSecrets(parseJson(text));\n call.body = value;\n\n if (redacted.length) {\n console.info(`[executable-stories-showcase] redacted ${redacted.join(', ')} in ${method.data} ${call.path}`);\n }\n }\n\n calls.push(call);\n }).catch((err) => console.warn('[executable-stories-showcase] skipped a recorded response', err))\n .finally(() => pending.delete(read));\n\n pending.add(read);\n\n return read;\n },\n /** Responses whose bodies have been read, as network entries. */\n entries: () => toNetwork(calls),\n /** Wait for bodies still being read. */\n settled: async () => {\n await Promise.all(pending);\n },\n };\n}\n\n/**\n * Recorded calls as scenario network entries: repeated calls to one endpoint become a\n * `sequence` in request order, or a single `response` when every call answered the same.\n */\nexport function toNetwork(calls: RecordedCall[]): NetworkEntry[] {\n const groups = new Map<string, RecordedCall[]>();\n\n for (const call of calls.toSorted((a, b) => a.order - b.order)) {\n // Sorted, because replay matches query params in any order.\n const query = Object.fromEntries(Object.entries(call.query).toSorted(([a], [b]) => a.localeCompare(b)));\n const key = JSON.stringify([call.method, call.path, query]);\n groups.set(key, [...(groups.get(key) ?? []), { ...call, query }]);\n }\n\n return [...groups.values()]\n .map(([first, ...rest]) => {\n const responses = [first, ...rest].map(({ status, body }) => ({ status, body }));\n const same = responses.every((r) => JSON.stringify(r) === JSON.stringify(responses[0]));\n const entry: NetworkEntry = { method: first.method, path: first.path };\n\n if (Object.keys(first.query).length) entry.query = first.query;\n\n if (same) entry.response = responses[0];\n else entry.sequence = responses;\n\n return entry;\n })\n // An entry without `query` matches any query string: try the more specific entries first.\n .toSorted((a, b) => Object.keys(b.query ?? {}).length - Object.keys(a.query ?? {}).length);\n}\n","import type { RequestHandler } from 'msw';\nimport type { SetupWorker } from 'msw/browser';\nimport type { z } from 'zod';\nimport { type Catalog, createCatalog, type ScenarioRule } from './catalog.js';\nimport {\n DATA_KEY,\n LOOPBACK,\n PARAM,\n readInline,\n RECORD_PARAM,\n SAVE_ENDPOINT,\n scenarioHref,\n STRICT_PARAM,\n} from './link.js';\nimport { createRecorder } from './record.js';\nimport {\n type Json,\n type NetworkEntry,\n parseJson,\n parseScenario,\n type Scenario,\n type ScenarioInput,\n type Serializable,\n} from './schema.js';\n\nconst STORAGE_KEY = 'executable-stories-showcase:active';\n\ninterface AdapterBase {\n /** Key under `scenario.state` this adapter reads. */\n key: string;\n /** Current state as JSON, for the panel's live editor and \"Save as scenario\". */\n read?(): Json;\n /** Call `onChange` when the state changes; return an unsubscribe. */\n subscribe?(onChange: () => void): () => void;\n /** False when new state only takes effect on a reload, e.g. a snapshot an actor starts from. */\n inPlace?: boolean;\n /**\n * False to apply this state from catalog scenarios only, and skip it for inline links.\n * For state that outlives the scenario, such as a cookie the real backend reads.\n */\n fromLinks?: boolean;\n}\n\n/** Decodes scenario JSON with `schema`, so `apply` receives typed state. */\nexport interface TypedAdapter<T> extends AdapterBase {\n /** Also feeds `scenarioJsonSchema`. */\n schema: z.ZodType<T>;\n apply(state: T): void | Promise<void>;\n}\n\n/** Receives the scenario's JSON as it is. */\nexport interface JsonAdapter extends AdapterBase {\n schema?: undefined;\n apply(state: Json): void | Promise<void>;\n}\n\n/** Connects one slice of app state (a store, a cache, a reducer) to scenarios and the panel. */\nexport type Adapter<T = Json> = TypedAdapter<T> | JsonAdapter;\n\nexport interface Connection<T extends Serializable> {\n /** Push the app's current state (for state the library can't `read`, e.g. useReducer). */\n update(state: T): void;\n disconnect(): void;\n}\n\nexport interface StartOptions {\n /** Scenario objects, e.g. `import.meta.glob<ScenarioInput>('./scenarios/*.json')`, or built in code. */\n scenarios: ScenarioInput[];\n /** Scenario to use when the URL names none. Omit to leave the app untouched. */\n defaultScenario?: string;\n /** Adapters available before render. Components can `connect` more later. */\n adapters?: Adapter<unknown>[];\n rules?: ScenarioRule[];\n /**\n * Open inline share links (`#scenario-data=...`). Anyone can write one, and it fakes API\n * responses for whoever opens it, so by default they only open on localhost.\n * Pass `true` on a staging or preview deploy that should open them; `false` refuses them everywhere.\n */\n allowLinks?: boolean;\n /**\n * Answer unmatched requests with 501 instead of passing them to the real network.\n * Assets and page loads always pass. Also switched on by `?scenario-strict`.\n */\n strict?: boolean;\n /**\n * Let unmatched requests reach the real network and record their JSON responses, so\n * \"Save as scenario\" can replay them offline. Also switched on by `?scenario-record`.\n */\n record?: boolean;\n /**\n * Requests that may reach the real network in strict mode (URL substring or RegExp),\n * e.g. images from a CDN or a maps API the screen needs.\n */\n allow?: (string | RegExp)[];\n /**\n * Requests the scenario doesn't care about, such as analytics, error reporting and captcha\n * checks (URL substring or RegExp). They are never reported as unhandled, and in strict mode\n * they get an empty 204 instead of a 501, so SDKs don't log errors or retry.\n */\n ignore?: (string | RegExp)[];\n /**\n * MSW handlers a scenario can use by name with `\"handlers\": [\"name\"]`, e.g. the ones your\n * unit tests already use. They answer after the scenario's `network` entries.\n */\n handlers?: Record<string, RequestHandler | RequestHandler[]>;\n /**\n * Called after an in-place switch changes network fixtures, so the app refetches,\n * e.g. `() => queryClient.resetQueries()`. Without it, such switches reload the page.\n */\n refresh?: () => void | Promise<void>;\n /**\n * The app's own MSW worker (or a `setupServer()` in tests), instead of starting one.\n * Its handlers stay active beneath the scenario's, and are restored by `destroy()`.\n * The app owns its lifecycle: start it before `startScenarios`.\n */\n worker?: MswWorker;\n /** Passed to `worker.start()` when executable-stories-showcase starts its own worker. */\n workerOptions?: Parameters<SetupWorker['start']>[0];\n}\n\n/** The parts of an MSW worker or server executable-stories-showcase uses. `events` is needed to record. */\nexport type MswWorker = Pick<SetupWorker, 'resetHandlers' | 'listHandlers'> & {\n events?: Pick<RecordingEvents, 'on' | 'removeListener'>;\n};\n\ntype StartListener = (e: { requestId: string; }) => void;\n\ntype ResponseListener = (e: { requestId: string; request: Request; response: Response; }) => void;\n\n/** MSW's lifecycle events that recording listens to, as both MSW 2 and 3 type them. */\ninterface RecordingEvents {\n on(event: 'request:start', listener: StartListener): void;\n on(event: 'response:mocked' | 'response:bypass', listener: ResponseListener): void;\n removeListener(event: 'request:start', listener: StartListener): void;\n removeListener(event: 'response:mocked' | 'response:bypass', listener: ResponseListener): void;\n}\n\nexport interface ScenarioSession {\n catalog: Catalog;\n /** The scenario as authored (with `extends`), or null when none is active. */\n active: Scenario | null;\n /** The active scenario with `extends` flattened. */\n resolved: Scenario | null;\n strict: boolean;\n /** Non-asset requests no scenario entry matched (live list). */\n unhandled: string[];\n /** Whether real responses are being recorded (`record` or `?scenario-record`). */\n recording: boolean;\n /** Real responses recorded so far, as network entries. Await `settleRecording()` first for all of them. */\n recorded(): NetworkEntry[];\n /** Resolves once every recorded response body has been read. */\n settleRecording(): Promise<void>;\n /** Keys of connected adapters. */\n keys(): string[];\n /** The connected adapter's current state, as JSON. */\n read(key: string): Json | undefined;\n /** Validate and apply state to a connected adapter now, without changing scenario. */\n applyState(key: string, state: Json): Promise<void>;\n /** Connect an adapter; the active scenario's state for its key is applied at once. */\n connect<T extends Serializable>(adapter: Adapter<T>): Connection<T>;\n /**\n * Switch to a catalog scenario: in place when only connected state (or network, with\n * `refresh`) changes, otherwise by reloading. Names combine: \"signed-in,server-error\".\n */\n open(names: string): Promise<void>;\n /** Switch to a scenario that isn't in the catalog, e.g. one edited in the panel. */\n openInline(scenario: Scenario): Promise<void>;\n onChange(listener: () => void): () => void;\n /**\n * Tear down: unsubscribe adapters, drop listeners and queued switches, and stop the\n * worker this session started (or restore the app's own worker's handlers).\n * For HMR and unmounting. The page keeps whatever state it shows.\n */\n destroy(): void;\n}\n\n/** An adapter with its type erased behind decode-then-apply. */\ninterface Bound {\n key: string;\n /** Validate now; the returned function applies. */\n decode(state: Json, scenario: string): () => void | Promise<void>;\n /** Undefined when the adapter can't read (state is pushed through `connection.update`). */\n read(): Json | undefined;\n subscribe(onChange: () => void): (() => void) | undefined;\n inPlace: boolean;\n fromLinks: boolean;\n}\n\nfunction bind<T>(adapter: Adapter<T>): Bound {\n return {\n key: adapter.key,\n read: () => adapter.read?.(),\n subscribe: (onChange) => adapter.subscribe?.(onChange),\n inPlace: adapter.inPlace !== false,\n fromLinks: adapter.fromLinks !== false,\n decode(state, scenario) {\n if (adapter.schema === undefined) return () => adapter.apply(state);\n const parsed = adapter.schema.safeParse(state);\n\n if (!parsed.success) {\n throw new Error(`Scenario \"${scenario}\": invalid state[\"${adapter.key}\"]: ${parsed.error.message}`);\n }\n\n return () => adapter.apply(parsed.data);\n },\n };\n}\n\ninterface Connected {\n adapter: Bound;\n /** Last state pushed through `connection.update`; converted to JSON when read. */\n value?: Serializable;\n unsubscribe?: () => void;\n}\n\n/**\n * What the CLI's `shoot`, `doctor` and `mcp` call inside the page, as JSON in and out.\n * The modules behind it load on first call, so apps that never use it don't pay for it.\n */\nexport interface PageHooks {\n /** The active scenario's name, or null. */\n scenario(): string | null;\n /** Whether a ready selector matches now, plus selectors that would. */\n checkReady(selector: string): Promise<ReadyReport>;\n /** The app's current state as a catalog file, after recorded responses settle. */\n capture(\n options: { name: string; description?: string; ready?: string; extends?: string; },\n ): Promise<ScenarioInput>;\n}\n\nexport interface ReadyReport {\n status: 'match' | 'none' | 'unchecked' | 'invalid';\n count?: number;\n visible?: boolean;\n error?: string;\n /** Distinctive selectors on the page now, best first. */\n suggestions: string[];\n}\n\ndeclare global {\n /** Set by `startScenarios` for the CLI and agents; see `PageHooks`. */\n var executableStoriesShowcasePage: PageHooks | undefined;\n}\n\nlet current: ScenarioSession | null = null;\n\n/** The session whose scenario the <html> markers describe; only it may clear them. */\nlet markerOwner: ScenarioSession | null = null;\n\n/** The session started by `startScenarios`, if any (null in production builds that skip it). */\nexport const currentSession = () => current;\n\ninterface Selection {\n name?: string;\n data?: string;\n strict?: boolean;\n}\n\n/**\n * Read the scenario from the URL (or the tab's last one), mock the network if the\n * scenario needs it, apply URL and adapter state. Await this before rendering the app.\n */\nexport async function startScenarios(options: StartOptions): Promise<ScenarioSession> {\n const catalog = createCatalog(options.scenarios, { rules: options.rules });\n const url = new URL(location.href);\n const selection = select(url, options.defaultScenario, options.allowLinks ?? LOOPBACK.has(url.hostname));\n const unhandled: string[] = [];\n globalThis.executableStoriesShowcaseUnhandled = unhandled;\n\n const connected = new Map<string, Connected>();\n const listeners = new Set<() => void>();\n\n // One failing listener (a panel, an app hook) must not stop the others or fail a switch.\n const emit = () => {\n for (const listener of listeners) {\n try {\n listener();\n } catch (err) {\n console.error('[executable-stories-showcase] onChange listener failed', err);\n }\n }\n };\n\n const recorder = createRecorder();\n // Set while recording listeners are attached; removes them.\n let stopListening: (() => void) | null = null;\n\n const listen = (target: MswWorker) => {\n if (!session.recording || stopListening) return;\n\n if (!target.events) {\n console.warn('[executable-stories-showcase] recording needs a worker with `events`');\n\n return;\n }\n\n const { events } = target;\n // Positions are taken when requests start, so a slow response keeps its place.\n const start: StartListener = ({ requestId }) => recorder.start(requestId);\n const mocked: ResponseListener = ({ requestId }) => recorder.drop(requestId);\n\n const bypass: ResponseListener = ({ requestId, request, response }) => {\n void recorder.bypass(requestId, request, response).then(emit);\n };\n\n events.on('request:start', start);\n events.on('response:mocked', mocked);\n events.on('response:bypass', bypass);\n\n stopListening = () => {\n events.removeListener('request:start', start);\n events.removeListener('response:mocked', mocked);\n events.removeListener('response:bypass', bypass);\n stopListening = null;\n };\n };\n\n /** The scenario's named handlers, in order. */\n const named = (resolved: Scenario) =>\n (resolved.handlers ?? []).flatMap((name) => {\n const found = options.handlers?.[name];\n\n if (!found) {\n const known = Object.keys(options.handlers ?? {}).join(', ') || 'none';\n throw new Error(\n `Scenario \"${resolved.name}\" uses unknown handler \"${name}\". Known handlers: ${known}`,\n );\n }\n\n return found;\n });\n\n // The app's worker, if given, else one this session starts on first need.\n let worker: MswWorker | null = options.worker ?? null;\n let ownWorker: SetupWorker | null = null;\n const appHandlers = options.worker ? [...options.worker.listHandlers()] : [];\n let destroyed = false;\n\n // Whether the active scenario came from an inline link.\n let fromLink = false;\n\n /** Validate every adapter's state up front, so nothing changes if any of it is invalid. */\n const prepareStates = (resolved: Scenario, adapters: Bound[], link = fromLink) =>\n adapters.flatMap((adapter) => {\n const state = resolved.state?.[adapter.key];\n\n if (state === undefined) return [];\n\n if (link && !adapter.fromLinks) {\n console.warn(`[executable-stories-showcase] ignored state.${adapter.key}: a link can't set it`);\n\n return [];\n }\n\n return [adapter.decode(state, resolved.name)];\n });\n\n const applyPrepared = async (prepared: ReturnType<typeof prepareStates>) => {\n for (const apply of prepared) {\n // Destroyed while an earlier adapter was applying: leave the rest untouched.\n if (destroyed) return;\n await apply();\n }\n };\n\n const boundAdapters = () => [...connected.values()].map((entry) => entry.adapter);\n\n // MSW loads only when a scenario mocks the network or strict mode needs a catch-all.\n const setNetwork = async (resolved: Scenario) => {\n if (!mocks(resolved) && !session.strict && !session.recording && !worker) return;\n const [{ toHandlers }, msw] = await Promise.all([import('./network.js'), import('msw')]);\n\n if (destroyed) return;\n\n const fallback = msw.http.all('*', ({ request }) => {\n const { pathname } = new URL(request.url);\n const isPage = request.mode === 'navigate' || !!request.headers.get('accept')?.includes('text/html');\n\n if (\n isPage || isAsset(request.url) || pathname.startsWith('/@') || pathname.startsWith(SAVE_ENDPOINT)\n || matches(options.allow, request.url)\n ) {\n return msw.passthrough();\n }\n\n if (matches(options.ignore, request.url)) {\n return session.strict ? new msw.HttpResponse(null, { status: 204 }) : msw.passthrough();\n }\n\n const label = `${request.method} ${request.url}`;\n unhandled.push(label);\n emit();\n\n if (!session.strict) return msw.passthrough();\n console.error(`[executable-stories-showcase] \"${resolved.name}\" has no handler for ${label}`);\n\n return msw.HttpResponse.json({ message: `No scenario handler for ${label}` }, { status: 501 });\n });\n\n // Scenario first, then its named handlers, then the app's own, then the catch-all.\n const handlers = [...toHandlers(resolved.network), ...named(resolved), ...appHandlers, fallback];\n\n if (worker) {\n worker.resetHandlers(...handlers);\n listen(worker);\n\n return;\n }\n\n const { setupWorker } = await import('#msw-browser');\n const started = setupWorker(...handlers);\n await started.start({ quiet: true, ...options.workerOptions });\n\n // Destroyed while starting: don't leave a worker running.\n if (destroyed) {\n void started.stop();\n\n return;\n }\n\n worker = ownWorker = started;\n listen(started);\n };\n\n const activate = (active: Scenario, resolved: Scenario, link: boolean) => {\n fromLink = link;\n session.active = active;\n session.resolved = resolved;\n // Tests and agents wait on these: html[data-scenario=\"name\"], then the ready selector.\n const html = document.documentElement;\n markerOwner = session;\n html.dataset.scenario = resolved.name;\n\n if (resolved.ready) html.dataset.scenarioReady = resolved.ready;\n else delete html.dataset.scenarioReady;\n };\n\n let transition = 0;\n let queue: Promise<void> = Promise.resolve();\n\n const switchTo = async (next: Scenario, inline: boolean) => {\n const resolved = catalog.resolve(next);\n // Validate before choosing reload or in place: a rejected switch must leave the app as it was.\n const prepared = prepareStates(resolved, boundAdapters(), inline);\n\n const prev = session.resolved;\n const nextKeys = Object.keys(resolved.state ?? {});\n named(resolved); // throws on an unknown handler name, before anything changes\n // Opening a scenario always restarts its network: fresh handler sequences, refetched data.\n const mocksNetwork = worker !== null || mocks(resolved) || session.strict;\n\n const href = scenarioHref(resolved, { inline, strict: session.strict, record: session.recording });\n\n const inPlace = prev !== null\n // Compare with the app's actual URL: URL-driven UI only resets on a reload.\n && new URL(href).pathname === location.pathname\n && JSON.stringify(appParams(location.search)) === JSON.stringify(sortedParams(resolved.url))\n && (!mocksNetwork || !!options.refresh)\n // Can't un-apply state, and can't apply state nobody is connected to receive.\n && Object.keys(prev.state ?? {}).every((k) => nextKeys.includes(k))\n && nextKeys.every((k) => connected.get(k)?.adapter.inPlace === true);\n\n if (!inPlace) {\n navigate(href);\n\n return;\n }\n\n // After every await: a destroyed session must not touch the app, URL or storage.\n if (mocksNetwork) {\n await setNetwork(resolved);\n\n if (destroyed) return;\n // Kick off the refetch but don't wait: a scenario may hold requests open forever.\n Promise.resolve(options.refresh?.()).catch((err) => console.error('[executable-stories-showcase]', err));\n }\n\n await applyPrepared(prepared);\n\n if (destroyed) return;\n unhandled.splice(0);\n activate(next, resolved, inline);\n const at = new URL(href);\n replaceUrl(at);\n const data = readInline(at.hash);\n store(data ? { data, strict: session.strict } : { name: next.name, strict: session.strict });\n emit();\n };\n\n // Switches run one at a time, and one superseded while queued is skipped,\n // so a slow earlier switch can never land on top of a later one.\n const enqueue = (next: () => Scenario, inline: boolean) => {\n if (destroyed) return Promise.reject(new Error('This scenario session was destroyed'));\n const id = ++transition;\n const run = queue.then(() => (id === transition ? switchTo(next(), inline) : undefined));\n queue = run.catch(() => {});\n\n return run;\n };\n\n const attach = (adapter: Bound): Connection<Serializable> => {\n const entry: Connected = { adapter };\n connected.set(adapter.key, entry);\n entry.unsubscribe = adapter.subscribe?.(emit);\n emit();\n\n return {\n update(state) {\n entry.value = state;\n emit();\n },\n disconnect() {\n entry.unsubscribe?.();\n\n if (connected.get(adapter.key) === entry) connected.delete(adapter.key);\n emit();\n },\n };\n };\n\n const session: ScenarioSession = {\n catalog,\n active: null,\n resolved: null,\n // Read apart from the selection: `?scenario-strict` alone still blocks the real network.\n strict: options.strict ?? (!!selection?.strict || url.searchParams.has(STRICT_PARAM)),\n unhandled,\n // Held in memory: a full page load starts a new recording.\n recording: options.record ?? url.searchParams.has(RECORD_PARAM),\n recorded: recorder.entries,\n settleRecording: recorder.settled,\n keys: () => [...connected.keys()],\n read(key) {\n const entry = connected.get(key);\n const read = entry?.adapter.read();\n\n if (read !== undefined) return read;\n\n return entry?.value === undefined ? undefined : parseJson(JSON.stringify(entry.value));\n },\n async applyState(key, state) {\n const entry = connected.get(key);\n\n if (!entry) throw new Error(`No adapter connected for \"${key}\"`);\n await entry.adapter.decode(state, session.active?.name ?? 'live')();\n },\n connect(adapter) {\n const bound = bind(adapter);\n const noop = { update() {}, disconnect() {} };\n\n if (destroyed) return noop;\n const connection = attach(bound);\n\n if (session.resolved) {\n applyPrepared(prepareStates(session.resolved, [bound])).catch((err) =>\n console.error('[executable-stories-showcase]', err)\n );\n }\n\n return connection;\n },\n open: (names) => enqueue(() => lookup(catalog, names), false),\n openInline: (scenario) => enqueue(() => scenario, true),\n onChange(listener) {\n listeners.add(listener);\n\n return () => listeners.delete(listener);\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n transition++; // drop queued switches\n\n for (const entry of connected.values()) entry.unsubscribe?.();\n connected.clear();\n listeners.clear();\n\n // An app-owned worker outlives the session: detach from it.\n stopListening?.();\n\n if (ownWorker) void ownWorker.stop();\n else worker?.resetHandlers(...appHandlers);\n worker = ownWorker = null;\n\n if (current === session) current = null;\n\n if (globalThis.executableStoriesShowcaseUnhandled === unhandled) globalThis.executableStoriesShowcaseUnhandled = undefined;\n\n if (globalThis.executableStoriesShowcasePage === hooks) globalThis.executableStoriesShowcasePage = undefined;\n\n if (markerOwner === session) {\n markerOwner = null;\n delete document.documentElement.dataset.scenario;\n delete document.documentElement.dataset.scenarioReady;\n }\n },\n };\n\n current = session;\n\n const hooks: PageHooks = {\n scenario: () => session.resolved?.name ?? null,\n async checkReady(selector) {\n const { checkReadySelector, suggestReadySelectors } = await import('./ready.js');\n const check = checkReadySelector(selector);\n const suggestions = suggestReadySelectors(null, { limit: 5 });\n\n return check.status === 'match'\n ? { status: 'match', count: check.count, visible: check.visible, suggestions }\n : { ...check, suggestions };\n },\n async capture({ extends: base, ...options }) {\n const { captureScenario } = await import('./capture.js');\n await session.settleRecording();\n\n return captureScenario(session, { ...options, base: base ? lookup(catalog, base) : undefined })\n .forCatalog;\n },\n };\n\n globalThis.executableStoriesShowcasePage = hooks;\n\n // Adapters connect whether or not a scenario is selected, so the plain app can be\n // inspected and captured too. Only applying scenario state is conditional.\n const initial = (options.adapters ?? []).map(bind);\n\n for (const adapter of initial) attach(adapter);\n\n if (!selection) {\n // Nothing to mock, but recording needs the worker to see the real responses,\n // and strict mode needs its catch-all.\n if (session.recording || session.strict) await setNetwork({ name: 'live' });\n\n return session;\n }\n\n // A selection that doesn't load must not stick to the tab and break every later page load.\n const loaded = (() => {\n try {\n const active = selection.data\n ? parseScenario(JSON.parse(selection.data), 'inline scenario')\n : lookup(catalog, selection.name ?? '');\n\n const resolved = catalog.resolve(active);\n named(resolved);\n\n return { active, resolved, prepared: prepareStates(resolved, initial, !!selection.data) };\n } catch (err) {\n store(null);\n throw err;\n }\n })();\n\n const { active, resolved, prepared } = loaded;\n await setNetwork(resolved);\n\n if (destroyed) return session;\n\n // Only fill params that are missing, so in-app navigation survives a reload.\n for (const [k, v] of Object.entries(resolved.url ?? {})) {\n if (v !== null && !url.searchParams.has(k)) url.searchParams.set(k, v);\n }\n\n replaceUrl(url);\n // Apply (awaiting async adapters) before the app renders.\n await applyPrepared(prepared);\n\n if (destroyed) return session;\n activate(active, resolved, !!selection.data);\n emit();\n\n return session;\n}\n\nconst mocks = (s: Scenario) => !!s.network?.length || !!s.handlers?.length;\n\nconst matches = (rules: (string | RegExp)[] = [], url: string) =>\n rules.some((rule) => (rule instanceof RegExp ? rule.test(url) : url.includes(rule)));\n\n/** msw's `isCommonAssetRequest`, inlined so msw 2.4 and later all work. */\nconst isAsset = (href: string) => {\n const url = new URL(href);\n\n return url.protocol === 'file:' || url.hostname === 'fonts.googleapis.com'\n || url.pathname.includes('node_modules') || url.pathname.includes('@vite')\n || /\\.(s?css|less|m?jsx?|m?tsx?|html|ttf|otf|woff2?|eot|gif|jpe?g|png|avif|webp|svg|mp4|webm|ogg|mov|mp3|wav|flac|aac|pdf|txt|csv|json|xml|md|zip|tar|gz|rar|7z)$/i\n .test(url.pathname);\n};\n\n// Keeps the router's history state. Next.js ignores replaceState calls whose state carries its\n// own marker (__NA), so drop that marker: Next then re-adds its state and updates useSearchParams.\nconst replaceUrl = (url: URL) => {\n const state: unknown = history.state;\n const { __NA, ...rest }: { __NA?: unknown; } = Object(state);\n\n history.replaceState(__NA === undefined ? state : rest, '', url);\n};\n\nconst byKey = ([a]: [string, string | null], [b]: [string, string | null]) => a.localeCompare(b);\n\n/** The app's own query params (scenario params excluded), sorted for comparison. */\nconst appParams = (search: string) =>\n [...new URLSearchParams(search)].filter(([k]) => k !== PARAM && k !== STRICT_PARAM && k !== RECORD_PARAM).toSorted(byKey);\n\nconst sortedParams = (url: Scenario['url']) =>\n Object.entries(url ?? {}).filter(([, v]) => v !== null).toSorted(byKey);\n\n/**\n * Go to a scenario link, reloading even when only the hash changes. Scenario links stay on the\n * app, so another origin (or a `javascript:` URL, whose origin is `null`) is refused.\n */\nexport function navigate(href: string) {\n const next = new URL(href, location.href);\n\n if (next.origin !== location.origin) {\n throw new Error(`[executable-stories-showcase] navigate: ${next.origin} is not this app (${location.origin})`);\n }\n\n if (next.pathname + next.search === location.pathname + location.search) {\n replaceUrl(next);\n location.reload();\n } else {\n location.href = next.toString();\n }\n}\n\n/**\n * URL first (`?scenario=name` or `#scenario-data=...`), then the tab's last\n * selection, so navigation that drops the query keeps the scenario.\n * `?scenario=` (empty) resets to the default.\n */\nfunction select(url: URL, fallback: string | undefined, allowLinks: boolean): Selection | null {\n let data = readInline(url.hash);\n\n if (data && !allowLinks) {\n refuseLink(url);\n data = null;\n }\n const name = url.searchParams.get(PARAM);\n const strict = url.searchParams.has(STRICT_PARAM);\n\n let selection: Selection | null = null;\n\n if (data) selection = { data, strict };\n else if (name) selection = { name, strict };\n else if (name === '') {\n url.searchParams.delete(PARAM);\n store(null);\n } else {\n let stored = load();\n\n if (stored?.data && !allowLinks) {\n refuseLink(url);\n store(null);\n stored = null;\n }\n\n // An explicit ?scenario-strict in the current URL wins over the stored flag.\n selection = stored && { ...stored, strict: stored.strict || strict };\n\n // Restore the selection into the URL so it stays shareable.\n if (selection?.name) url.searchParams.set(PARAM, selection.name);\n\n if (selection?.data) url.hash = new URLSearchParams({ [DATA_KEY]: selection.data }).toString();\n\n if (selection?.strict) url.searchParams.set(STRICT_PARAM, '');\n }\n\n if (selection) store(selection);\n\n return selection ?? (fallback ? { name: fallback, strict } : null);\n}\n\nfunction refuseLink(url: URL) {\n url.hash = '';\n console.warn(\n `[executable-stories-showcase] ignored an inline scenario link: links only open on localhost unless startScenarios({ allowLinks: true })`,\n );\n}\n\n/** One name, or several joined with commas: later ones win, like `extends`. */\nfunction lookup(catalog: Catalog, names: string): Scenario {\n const list = names.split(',').map((n) => n.trim());\n\n for (const name of list) {\n if (catalog.get(name)) continue;\n const known = catalog.list.map((s) => s.name).join(', ');\n throw new Error(`Unknown scenario \"${name}\". Known scenarios: ${known}`);\n }\n\n const [only] = list;\n const single = list.length === 1 && only !== undefined ? catalog.get(only) : undefined;\n\n return single ?? { name: list.join(','), extends: list };\n}\n\nfunction store(selection: Selection | null) {\n try {\n if (selection) sessionStorage.setItem(STORAGE_KEY, JSON.stringify(selection));\n else sessionStorage.removeItem(STORAGE_KEY);\n } catch {\n // sessionStorage throws when storage is blocked; the selection just is not kept\n }\n}\n\nfunction load(): Selection | null {\n try {\n return JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? 'null');\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["resolved","prepared","toHandlers","active","checkReadySelector","suggestReadySelectors","options","captureScenario"]}
|
package/dist/link.d.ts
CHANGED
|
@@ -23,10 +23,11 @@ export declare const DATA_KEY = "scenario-data";
|
|
|
23
23
|
* A shareable URL that opens the app in a resolved scenario.
|
|
24
24
|
* `inline` embeds the flattened scenario, so the link works without the catalog.
|
|
25
25
|
*/
|
|
26
|
-
export declare function scenarioHref(resolved: Scenario, { base, inline, strict }?: {
|
|
26
|
+
export declare function scenarioHref(resolved: Scenario, { base, inline, strict, record }?: {
|
|
27
27
|
base?: string | undefined;
|
|
28
28
|
inline?: boolean | undefined;
|
|
29
29
|
strict?: boolean | undefined;
|
|
30
|
+
record?: boolean | undefined;
|
|
30
31
|
}): string;
|
|
31
32
|
/** Inline scenario JSON from a URL hash, if present. */
|
|
32
33
|
export declare const readInline: (hash: string) => string | null;
|
package/dist/next.js
CHANGED
package/dist/panel.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export interface PanelOptions {
|
|
|
17
17
|
* Remove it with `panel.remove()`; it unsubscribes from the session.
|
|
18
18
|
*/
|
|
19
19
|
export declare function mountPanel(session: ScenarioSession, options?: PanelOptions): ScenarioPanel;
|
|
20
|
-
export { type CapturedScenario, type CaptureOptions, captureScenario } from './capture.js';
|
|
21
20
|
declare const Base: typeof HTMLElement;
|
|
22
21
|
declare class ScenarioPanel extends Base {
|
|
23
22
|
session: ScenarioSession;
|
|
@@ -28,3 +27,4 @@ declare class ScenarioPanel extends Base {
|
|
|
28
27
|
disconnectedCallback(): void;
|
|
29
28
|
connectedCallback(): void;
|
|
30
29
|
}
|
|
30
|
+
export {};
|
package/dist/panel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
captureScenario
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-C755RCRL.js";
|
|
4
4
|
import {
|
|
5
5
|
checkReadySelector,
|
|
6
6
|
closestReadySelector,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isLoopbackSaveUrl,
|
|
13
13
|
readInline,
|
|
14
14
|
scenarioHref
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ESIS3D6R.js";
|
|
16
16
|
import "./chunk-MCC3CAWQ.js";
|
|
17
17
|
import {
|
|
18
18
|
parseScenario
|
|
@@ -127,7 +127,7 @@ var ScenarioPanel = class extends Base {
|
|
|
127
127
|
};
|
|
128
128
|
const href = (s) => {
|
|
129
129
|
try {
|
|
130
|
-
return { href: scenarioHref(catalog.resolve(s), { strict: session.strict }), invalid: false };
|
|
130
|
+
return { href: scenarioHref(catalog.resolve(s), { strict: session.strict, record: session.recording }), invalid: false };
|
|
131
131
|
} catch {
|
|
132
132
|
return { href: `?${PARAM}=${encodeURIComponent(s.name)}`, invalid: true };
|
|
133
133
|
}
|
|
@@ -602,7 +602,6 @@ var STYLES = `
|
|
|
602
602
|
footer a { color: var(--accent); }
|
|
603
603
|
`;
|
|
604
604
|
export {
|
|
605
|
-
captureScenario,
|
|
606
605
|
mountPanel
|
|
607
606
|
};
|
|
608
607
|
//# sourceMappingURL=panel.js.map
|
package/dist/panel.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/panel.ts"],"sourcesContent":["import { z } from 'zod';\nimport { captureScenario } from './capture.js';\nimport { isLoopbackSaveUrl, PARAM, readInline, SAVE_ENDPOINT, scenarioHref } from './link.js';\nimport { checkReadySelector, closestReadySelector, suggestReadySelectors } from './ready.js';\nimport type { ScenarioSession } from './runtime.js';\nimport { parseScenario, type Scenario } from './schema.js';\n\nconst OPEN_KEY = 'scenario-panel:open';\n\n// \"Saved …\" survives the reload Vite does when a new file lands in the scenarios glob.\nconst FLASH_KEY = 'scenario-panel:flash';\n\n// The panel posts the captured scenario to `url`, so only a loopback save server is accepted.\nconst SaveProbeSchema = z.object({ dir: z.string(), url: z.string().refine(isLoopbackSaveUrl).optional() });\n\nconst SaveResultSchema = z.object({ path: z.string().optional(), error: z.string().optional() });\n\nconst FlashSchema = z.object({ message: z.string(), at: z.number() });\n\nexport interface PanelOptions {\n /** Corner to dock in, e.g. to avoid a chat widget. Default \"bottom-right\". */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Default: the maximum, so the panel stays above app overlays. */\n zIndex?: number;\n /** \"auto\" follows the OS colour scheme. Default \"auto\". */\n theme?: 'auto' | 'light' | 'dark';\n /**\n * Relative `$schema` path stamped on Download JSON, e.g. `../scenario.schema.json`.\n * Copy link stays schema-free.\n */\n schemaPath?: string;\n}\n\n/**\n * Add the floating scenario panel to the page. Framework-agnostic.\n * Remove it with `panel.remove()`; it unsubscribes from the session.\n */\nexport function mountPanel(session: ScenarioSession, options: PanelOptions = {}) {\n if (!customElements.get('scenario-panel')) {\n customElements.define('scenario-panel', ScenarioPanel);\n }\n\n const el = new ScenarioPanel();\n el.session = session;\n el.schemaPath = options.schemaPath;\n const [v, h] = (options.position ?? 'bottom-right').split('-');\n el.dataset.v = v;\n el.dataset.h = h;\n\n if (options.zIndex !== undefined) el.style.zIndex = String(options.zIndex);\n\n if (options.theme && options.theme !== 'auto') el.dataset.theme = options.theme;\n document.body.append(el);\n\n return el;\n}\n\n// Kept for imports from before captureScenario moved to the main entry.\nexport { type CapturedScenario, type CaptureOptions, captureScenario } from './capture.js';\n\n// Next.js loads client modules on the server too, where HTMLElement is undefined. Only\n// mountPanel creates a panel, and it runs in a browser, so on the server any base will do.\nconst Base: typeof HTMLElement = globalThis.HTMLElement ?? Object;\n\nclass ScenarioPanel extends Base {\n session!: ScenarioSession;\n schemaPath?: string;\n private render?: () => void;\n private unsubscribe?: () => void;\n private stopPick?: () => void;\n\n disconnectedCallback() {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n this.stopPick?.();\n this.stopPick = undefined;\n }\n\n connectedCallback() {\n // Re-attached (e.g. moved in the DOM): resubscribe and redraw.\n if (this.render) {\n this.unsubscribe = this.session.onChange(this.render);\n this.render();\n\n return;\n }\n\n const session = this.session;\n const { catalog } = session;\n const root = this.attachShadow({ mode: 'open' });\n\n root.innerHTML = `\n <style>${STYLES}</style>\n <button class=\"toggle\" aria-expanded=\"false\" aria-controls=\"drawer\"></button>\n <section id=\"drawer\" aria-label=\"Scenarios\" hidden>\n <header>\n <h2>Scenarios</h2>\n <button class=\"close\" aria-label=\"Close scenarios panel\">×</button>\n </header>\n <p class=\"error\" role=\"alert\"></p>\n <input type=\"search\" placeholder=\"Filter by name, description or tag\" aria-label=\"Filter scenarios\">\n <ul class=\"list\"></ul>\n <div class=\"unmocked\" role=\"status\"></div>\n <section class=\"live\" aria-label=\"Live state\"></section>\n <details class=\"save\">\n <summary>Save as scenario</summary>\n <p class=\"hint\">Saves the current URL, network responses and live state. <b>Copy link</b> shares it as is; <b>Download JSON</b> writes a file for your scenarios folder that keeps only what differs from the base.</p>\n <label>Name <input name=\"save-name\" placeholder=\"e.g. checkout-card-declined\" aria-label=\"Scenario name\"></label>\n <label>Description <input name=\"save-description\" aria-label=\"Scenario description\"></label>\n <label>Ready: what's on screen once this state has rendered <input name=\"save-ready\" placeholder='Use Mark ready, or type text=\"…\"' aria-label=\"Ready selector\"></label>\n <p class=\"ready-check\" role=\"status\"></p>\n <div class=\"row\">\n <button type=\"button\" data-act=\"mark-ready\">Mark ready</button>\n <button type=\"button\" data-act=\"clear-ready\">Clear</button>\n </div>\n <div class=\"candidates\" role=\"group\" aria-label=\"Suggested ready selectors\"></div>\n <div class=\"pick-box\" hidden><span></span></div>\n <label>Extends\n <select name=\"save-base\" aria-label=\"Base scenario to extend\"></select>\n </label>\n <div class=\"row\">\n <button data-act=\"save-project\" hidden>Save to project</button>\n <button data-act=\"save-link\">Copy link</button>\n <button data-act=\"save-json\">Download JSON</button>\n </div>\n <p class=\"save-status\" role=\"status\"></p>\n <input class=\"saved\" readonly aria-label=\"Saved scenario link\" hidden>\n </details>\n <details class=\"edit\">\n <summary>Edit current scenario</summary>\n <textarea spellcheck=\"false\" aria-label=\"Scenario JSON\"></textarea>\n <button data-act=\"apply\">Apply edited scenario</button>\n </details>\n <footer>\n <button data-act=\"copy\">Copy link</button>\n <a class=\"reset\">Reset</a>\n </footer>\n </section>`;\n\n const find = <T extends Element>(selector: string, type: new() => T): T => {\n const el = root.querySelector(selector);\n\n if (!(el instanceof type)) throw new Error(`executable-stories-showcase panel: missing ${selector}`);\n\n return el;\n };\n\n const toggle = find('.toggle', HTMLButtonElement);\n const drawer = find('#drawer', HTMLElement);\n const error = find('.error', HTMLParagraphElement);\n\n const run = (fn: () => void | Promise<void>) => async () => {\n error.textContent = '';\n\n try {\n await fn();\n } catch (err) {\n error.textContent = err instanceof Error ? err.message : String(err);\n }\n };\n\n const href = (s: Scenario) => {\n try {\n return { href: scenarioHref(catalog.resolve(s), { strict: session.strict }), invalid: false };\n } catch {\n return { href: `?${PARAM}=${encodeURIComponent(s.name)}`, invalid: true };\n }\n };\n\n find('.list', HTMLUListElement).innerHTML = catalog.list\n .map((s) => ({ s, ...href(s) }))\n .map(\n ({ s, href, invalid }) => `\n <li data-name=\"${esc(s.name)}\" data-search=\"${\n esc([s.name, s.description, ...(s.tags ?? [])].join(' ').toLowerCase())\n }\">\n <a href=\"${esc(href)}\">\n <strong>${esc(s.name)}${invalid ? ' <em>(invalid, opens with error)</em>' : ''}</strong>\n ${s.description ? `<span>${esc(s.description)}</span>` : ''}\n ${s.tags?.length ? `<small>${s.tags.map(esc).join(' · ')}</small>` : ''}\n </a>\n <button class=\"add\" aria-label=\"Combine ${\n esc(s.name)\n } with the current scenario\" title=\"Combine with the current scenario\">+</button>\n </li>`,\n )\n .join('');\n root.querySelectorAll<HTMLLIElement>('li[data-name]').forEach((li) => {\n const name = li.dataset.name!;\n li.querySelector('a')!.onclick = (e) => {\n if (e.metaKey || e.ctrlKey || e.shiftKey) return; // let the browser open a new tab\n e.preventDefault();\n void run(() => session.open(name))();\n };\n\n li.querySelector<HTMLButtonElement>('.add')!.onclick = run(() => {\n const names = new Set((session.active?.name ?? '').split(',').filter(Boolean));\n names.add(name);\n\n return session.open([...names].join(','));\n });\n });\n const filter = find('input[type=search]', HTMLInputElement);\n filter.oninput = () => {\n const q = filter.value.toLowerCase().trim();\n root.querySelectorAll<HTMLLIElement>('li[data-search]').forEach((li) => {\n li.hidden = !li.dataset.search!.includes(q);\n });\n };\n\n const editors = new Map<string, { textarea: HTMLTextAreaElement; dirty: boolean; }>();\n\n const renderLive = () => {\n const keys = session.keys();\n\n for (const [key, ed] of editors) {\n if (!keys.includes(key)) {\n ed.textarea.closest('details')!.remove();\n editors.delete(key);\n }\n }\n\n for (const key of keys) {\n let ed = editors.get(key);\n\n if (!ed) {\n const details = document.createElement('details');\n details.innerHTML = `\n <summary>Live state: <code>${esc(key)}</code></summary>\n <textarea spellcheck=\"false\" aria-label=\"Live state ${esc(key)}\"></textarea>\n <div class=\"row\"><button data-act=\"live-apply\">Apply</button><button data-act=\"live-revert\">Revert</button></div>`;\n find('.live', HTMLElement).append(details);\n const entry = { textarea: details.querySelector('textarea')!, dirty: false };\n entry.textarea.oninput = () => (entry.dirty = true);\n details.ontoggle = () => renderLive();\n details.querySelector<HTMLButtonElement>('[data-act=live-apply]')!.onclick = run(async () => {\n await session.applyState(key, JSON.parse(entry.textarea.value));\n entry.dirty = false;\n renderLive();\n });\n details.querySelector<HTMLButtonElement>('[data-act=live-revert]')!.onclick = () => {\n entry.dirty = false;\n renderLive();\n };\n\n editors.set(key, ed = entry);\n }\n\n // Follow the app while open (state can be large), never overwriting what someone is typing.\n if (!ed.dirty && ed.textarea.closest('details')!.open) {\n ed.textarea.value = JSON.stringify(session.read(key) ?? null, null, 2);\n }\n }\n };\n\n const saveName = find('input[name=save-name]', HTMLInputElement);\n const saveDescription = find('input[name=save-description]', HTMLInputElement);\n const saveReady = find('input[name=save-ready]', HTMLInputElement);\n const saveBase = find('select[name=save-base]', HTMLSelectElement);\n const readyCheck = find('.ready-check', HTMLParagraphElement);\n const saveProject = find('[data-act=save-project]', HTMLButtonElement);\n const saveStatus = find('.save-status', HTMLParagraphElement);\n const candidates = find('.candidates', HTMLElement);\n const saved = find('.saved', HTMLInputElement);\n const saveDetails = find('.save', HTMLDetailsElement);\n const markReadyBtn = find('[data-act=mark-ready]', HTMLButtonElement);\n\n const fillBaseOptions = () => {\n const preferred = catalog.get('default')?.name\n ?? [session.active?.extends ?? []].flat()[0]\n ?? '';\n\n const previous = saveBase.value;\n\n saveBase.innerHTML = `<option value=\"\">(none: save everything)</option>`\n + catalog.list.map((s) => `<option value=\"${esc(s.name)}\">${esc(s.name)}</option>`).join('');\n\n if (previous && [...saveBase.options].some((o) => o.value === previous)) {\n saveBase.value = previous;\n } else {\n saveBase.value = preferred;\n }\n };\n\n fillBaseOptions();\n\n const renderCandidates = () => {\n const picks = suggestReadySelectors(document.body, { limit: 3 });\n candidates.innerHTML = picks.length\n ? `<p class=\"hint\">Suggestions</p><div class=\"row\">${\n picks.map((s) => `<button type=\"button\" data-candidate=\"${esc(s)}\">${esc(s)}</button>`).join('')\n }</div>`\n : '';\n candidates.querySelectorAll<HTMLButtonElement>('[data-candidate]').forEach((btn) => {\n btn.onclick = () => {\n saveReady.value = btn.dataset.candidate ?? '';\n updateReadyCheck();\n };\n });\n };\n\n saveDetails.ontoggle = () => {\n if (saveDetails.open) {\n fillBaseOptions();\n // Offer, don't fill: the top suggestion is often chrome present in every state.\n renderCandidates();\n updateReadyCheck();\n void probeSaveEndpoint();\n }\n };\n\n const stopPickMode = () => {\n this.stopPick?.();\n this.stopPick = undefined;\n markReadyBtn.setAttribute('aria-pressed', 'false');\n markReadyBtn.textContent = 'Mark ready';\n };\n\n const pickBox = find('.pick-box', HTMLDivElement);\n const pickLabel = find('.pick-box span', HTMLSpanElement);\n\n const outline = (el: Element | undefined, label: string) => {\n if (!el) {\n pickBox.hidden = true;\n\n return;\n }\n\n const r = el.getBoundingClientRect();\n Object.assign(pickBox.style, {\n top: `${r.top}px`,\n left: `${r.left}px`,\n width: `${r.width}px`,\n height: `${r.height}px`,\n });\n pickBox.dataset.below = String(r.top < 28);\n pickLabel.textContent = label;\n pickBox.hidden = false;\n };\n\n // Elements under the pointer, outside the panel, innermost first.\n const appTarget = (e: Event) => {\n const path = e.composedPath();\n\n if (path.includes(this)) return undefined;\n\n return path.find((n): n is Element =>\n n instanceof Element && n !== document.documentElement && n !== document.body\n );\n };\n\n const READY_MESSAGES = {\n empty:\n \"No ready selector: the link still works, but shoot and tests won't wait for this state to render.\",\n none: 'No match on this page: tests would time out waiting for it.',\n unchecked: \"Can't check this kind of selector here; Playwright still can.\",\n };\n\n let readyMatch: Element | undefined;\n\n const updateReadyCheck = () => {\n const value = saveReady.value.trim();\n const check = value ? checkReadySelector(value) : undefined;\n readyMatch = check?.status === 'match' ? check.first : undefined;\n\n let tone = 'muted';\n let message: string;\n\n if (!check) message = READY_MESSAGES.empty;\n else if (check.status === 'invalid') [tone, message] = ['bad', check.error];\n else if (check.status === 'none') [tone, message] = ['bad', READY_MESSAGES.none];\n else if (check.status === 'unchecked') message = READY_MESSAGES.unchecked;\n else if (!check.visible) {\n message = 'Matches, but the first match is hidden: tests wait until it shows.';\n } else if (check.count > 1) {\n message = `Matches ${check.count} elements; tests wait for the first (outlined).`;\n } else [tone, message] = ['ok', '✓ Matches 1 element on this page.'];\n\n readyCheck.dataset.tone = tone;\n readyCheck.textContent = message;\n\n // While the field has focus, show which element tests will wait on.\n if (this.shadowRoot?.activeElement === saveReady && !this.stopPick) outline(readyMatch, 'ready');\n };\n\n saveReady.oninput = updateReadyCheck;\n saveReady.onfocus = updateReadyCheck;\n\n saveReady.onblur = () => {\n if (!this.stopPick) outline(undefined, '');\n };\n\n updateReadyCheck();\n\n const startPickMode = () => {\n stopPickMode();\n markReadyBtn.setAttribute('aria-pressed', 'true');\n markReadyBtn.textContent = 'Click the UI… (Esc cancels)';\n document.documentElement.style.cursor = 'crosshair';\n\n const onMove = (e: MouseEvent) => {\n const target = appTarget(e);\n const hit = target && closestReadySelector(target);\n\n if (hit) outline(hit.element, hit.selector);\n else outline(target, 'No selector here: try text, a heading or a button');\n };\n\n // Swallow presses so the app doesn't react (open menus, follow links) while picking.\n const block = (e: Event) => {\n if (!appTarget(e)) return;\n e.preventDefault();\n e.stopPropagation();\n };\n\n const onClick = (e: MouseEvent) => {\n const target = appTarget(e);\n\n if (!target) return;\n block(e);\n const hit = closestReadySelector(target);\n\n // Stay in pick mode so the user can try another element; the outline says why.\n if (!hit) return;\n\n saveReady.value = hit.selector;\n updateReadyCheck();\n stopPickMode();\n saveReady.focus();\n };\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n // Esc only cancels picking; without this the drawer's Esc handler also closes the panel.\n e.stopPropagation();\n stopPickMode();\n };\n\n const pressEvents = ['pointerdown', 'mousedown', 'pointerup', 'mouseup'] as const;\n\n document.addEventListener('mousemove', onMove, true);\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKey, true);\n\n for (const type of pressEvents) document.addEventListener(type, block, true);\n\n this.stopPick = () => {\n document.removeEventListener('mousemove', onMove, true);\n document.removeEventListener('click', onClick, true);\n document.removeEventListener('keydown', onKey, true);\n\n for (const type of pressEvents) document.removeEventListener(type, block, true);\n\n document.documentElement.style.removeProperty('cursor');\n pickBox.hidden = true;\n };\n };\n\n markReadyBtn.onclick = () => {\n if (markReadyBtn.getAttribute('aria-pressed') === 'true') stopPickMode();\n else startPickMode();\n };\n\n find('[data-act=clear-ready]', HTMLButtonElement).onclick = () => {\n saveReady.value = '';\n updateReadyCheck();\n };\n\n const capture = () => {\n const baseName = saveBase.value;\n const base = baseName ? catalog.get(baseName) : undefined;\n\n return captureScenario(session, {\n name: saveName.value.trim(),\n description: saveDescription.value.trim(),\n ready: saveReady.value.trim() || undefined,\n base,\n schemaPath: this.schemaPath,\n });\n };\n\n let probe: Promise<void> | undefined;\n // The Next.js route answers with a loopback save server's URL; Vite saves on its own origin.\n let saveUrl = SAVE_ENDPOINT;\n\n // The Vite plugin or the Next.js route answers with JSON; anything else (a build, an app\n // without either) returns HTML or a 404, and the button stays hidden.\n const probeSaveEndpoint = () =>\n probe ??= fetch(SAVE_ENDPOINT, { headers: { accept: 'application/json' } })\n .then(async (res) => {\n const isJson = res.ok && res.headers.get('content-type')?.includes('json');\n const probed = isJson ? SaveProbeSchema.safeParse(await res.json()) : undefined;\n\n if (!probed?.success) return;\n saveUrl = probed.data.url ?? SAVE_ENDPOINT;\n saveProject.textContent = `Save to ${probed.data.dir}`;\n saveProject.hidden = false;\n })\n .catch(() => {});\n\n const saveToProject = async (overwrite: boolean): Promise<void> => {\n const { forCatalog } = capture();\n\n const res = await fetch(saveUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ scenario: forCatalog, overwrite }),\n });\n\n const body = SaveResultSchema.catch({}).parse(await res.json().catch(() => ({})));\n\n if (res.status === 409 && !overwrite) {\n if (confirm(`${body.path} already exists. Replace it?`)) return saveToProject(true);\n\n return;\n }\n\n if (!res.ok) throw new Error(body.error ?? `Save failed (HTTP ${res.status})`);\n\n const message = `Saved ${body.path}. Commit it with your change.`;\n saveStatus.textContent = message;\n\n try {\n sessionStorage.setItem(FLASH_KEY, JSON.stringify({ message, at: Date.now() }));\n } catch {\n // Storage blocked: the message won't survive a reload.\n }\n };\n\n saveProject.onclick = run(async () => {\n saveStatus.textContent = '';\n await session.settleRecording();\n await saveToProject(false);\n });\n\n try {\n const flash = FlashSchema.safeParse(JSON.parse(sessionStorage.getItem(FLASH_KEY) ?? 'null')).data;\n sessionStorage.removeItem(FLASH_KEY);\n\n // Only right after a save; a stale one would describe some other session.\n if (flash && Date.now() - flash.at < 10_000) {\n saveStatus.textContent = flash.message;\n saveDetails.open = true;\n }\n } catch {\n // Storage blocked or corrupt: nothing to show.\n }\n\n find('[data-act=save-link]', HTMLButtonElement).onclick = run(async () => {\n await session.settleRecording();\n const { flat } = capture();\n const link = scenarioHref(flat, { inline: true, strict: session.strict });\n saved.hidden = false;\n saved.value = link;\n saved.select();\n await navigator.clipboard?.writeText(link).catch(() => {});\n updateReadyCheck();\n });\n find('[data-act=save-json]', HTMLButtonElement).onclick = run(async () => {\n await session.settleRecording();\n const { forCatalog } = capture();\n const a = document.createElement('a');\n a.href = URL.createObjectURL(\n new Blob([`${JSON.stringify(forCatalog, null, 2)}\\n`], { type: 'application/json' }),\n );\n a.download = `${forCatalog.name}.json`;\n a.click();\n URL.revokeObjectURL(a.href);\n updateReadyCheck();\n });\n\n const editor = find('.edit textarea', HTMLTextAreaElement);\n\n if (readInline(location.hash)) find('.edit', HTMLDetailsElement).open = true;\n find('[data-act=apply]', HTMLButtonElement).onclick = run(() =>\n session.openInline(parseScenario(JSON.parse(editor.value)))\n );\n\n find('[data-act=copy]', HTMLButtonElement).onclick = run(async () => {\n try {\n await navigator.clipboard.writeText(location.href);\n } catch {\n prompt('Copy this link', location.href);\n }\n });\n // An absolute same-origin URL: a path starting with \"//\" must not read as another host.\n const reset = new URL(location.href);\n reset.search = '';\n reset.hash = '';\n reset.searchParams.set(PARAM, '');\n find('.reset', HTMLAnchorElement).href = reset.href;\n\n const setOpen = (open: boolean) => {\n drawer.hidden = !open;\n toggle.setAttribute('aria-expanded', String(open));\n\n try {\n sessionStorage.setItem(OPEN_KEY, open ? '1' : '');\n } catch {\n // sessionStorage throws when storage is blocked; the panel just forgets\n }\n };\n\n let stored: string | null = null;\n\n try {\n stored = sessionStorage.getItem(OPEN_KEY);\n } catch {\n // sessionStorage throws when storage is blocked; the panel just forgets\n }\n\n setOpen(!!stored);\n toggle.onclick = () => setOpen(!!drawer.hidden);\n find('.close', HTMLButtonElement).onclick = () => {\n setOpen(false);\n toggle.focus();\n };\n\n drawer.onkeydown = (e) => {\n if (e.key === 'Escape') {\n setOpen(false);\n toggle.focus();\n }\n };\n\n let shownScenario: string | undefined;\n\n const render = () => {\n const { active, strict, unhandled, recording } = session;\n const mode = recording ? ` (recording ${session.recorded().length})` : strict ? ' (strict)' : '';\n toggle.innerHTML = `Scenario: <b>${esc(active?.name ?? 'none')}</b>${mode}`;\n const names = (active?.name ?? '').split(',');\n root.querySelectorAll<HTMLLIElement>('li[data-name]').forEach((li) => {\n const a = li.querySelector('a')!;\n\n if (names.includes(li.dataset.name!)) a.setAttribute('aria-current', 'true');\n else a.removeAttribute('aria-current');\n });\n // While recording, unmocked requests are the point: they're what gets saved.\n find('.unmocked', HTMLElement).innerHTML = unhandled.length && !recording\n ? `<p>${unhandled.length} request(s) not covered by this scenario${\n strict ? ' (answered 501)' : ''\n }:</p>\n <ul>${unhandled.map((u) => `<li><code>${esc(u)}</code></li>`).join('')}</ul>`\n : '';\n\n // Set as a property, not markup, so page text selectors never match the panel's JSON.\n if (active?.name !== shownScenario) {\n shownScenario = active?.name;\n editor.value = JSON.stringify(active ?? { name: 'custom', extends: catalog.list[0]?.name }, null, 2);\n fillBaseOptions();\n }\n\n renderLive();\n\n // The app re-rendered: the ready selector may match now, or no longer.\n if (saveDetails.open) updateReadyCheck();\n };\n\n this.render = render;\n render();\n this.unsubscribe = session.onChange(render);\n }\n}\n\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\n\nconst STYLES = `\n :host {\n --bg: #ffffff; --fg: #1b1b1f; --muted: #5d5d66; --line: #dcdce2;\n --accent: #4747eb; --accent-fg: #ffffff; --hover: #f2f2f7; --danger: #b42318;\n all: initial; position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;\n font: 14px/1.4 system-ui, sans-serif; color: var(--fg);\n }\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme=\"light\"])) {\n --bg: #1c1c21; --fg: #ececf1; --muted: #a3a3ad; --line: #34343c;\n --accent: #8c8cff; --accent-fg: #101014; --hover: #26262d; --danger: #ff8a80;\n }\n }\n :host([data-theme=\"dark\"]) {\n --bg: #1c1c21; --fg: #ececf1; --muted: #a3a3ad; --line: #34343c;\n --accent: #8c8cff; --accent-fg: #101014; --hover: #26262d; --danger: #ff8a80;\n }\n :host([data-v=\"top\"]) { bottom: auto; top: 16px; }\n :host([data-h=\"left\"]) { right: auto; left: 16px; }\n :host([data-v=\"top\"]) #drawer { bottom: auto; top: 48px; }\n :host([data-h=\"left\"]) #drawer { right: auto; left: 0; }\n :host([data-h=\"left\"]) .toggle { margin-left: 0; }\n * { box-sizing: border-box; font: inherit; color: inherit; }\n button, a { cursor: pointer; }\n :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n .toggle {\n display: block; margin-left: auto; padding: 8px 14px; border: 0; border-radius: 999px;\n background: var(--accent); color: var(--accent-fg); box-shadow: 0 4px 16px #0003;\n }\n .toggle b, strong { font-weight: 700; }\n #drawer {\n position: absolute; right: 0; bottom: 48px; width: min(400px, calc(100vw - 32px));\n max-height: min(680px, calc(100vh - 96px)); overflow: auto; padding: 12px;\n background: var(--bg); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 12px 40px #0004;\n }\n #drawer[hidden] { display: none; }\n header, footer, .row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n .row { justify-content: flex-start; margin-top: 8px; flex-wrap: wrap; }\n h2 { margin: 0; font-size: 16px; font-weight: 700; }\n .close { border: 0; background: none; font-size: 22px; line-height: 1; padding: 4px 8px; border-radius: 6px; }\n input, textarea, select {\n width: 100%; margin: 6px 0; padding: 8px 10px; border: 1px solid var(--line);\n border-radius: 8px; background: var(--bg);\n }\n label { display: block; margin-top: 6px; color: var(--muted); font-size: 12px; }\n label input, label select { color: var(--fg); font-size: 14px; }\n ul { list-style: none; margin: 0; padding: 0; }\n li[hidden] { display: none; }\n .list li { display: flex; align-items: stretch; gap: 4px; }\n .list a {\n flex: 1; min-width: 0; overflow-wrap: anywhere; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 8px;\n text-decoration: none; border-left: 3px solid transparent;\n }\n .list a:hover { background: var(--hover); }\n .list a[aria-current] { border-left-color: var(--accent); background: var(--hover); }\n .list span { color: var(--muted); }\n .list small { color: var(--muted); font-size: 12px; }\n .add { width: 32px; border: 0; border-radius: 8px; background: none; color: var(--muted); font-size: 18px; }\n .add:hover { background: var(--hover); color: var(--fg); }\n details { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }\n summary { cursor: pointer; font-weight: 600; }\n code { font: 12px ui-monospace, monospace; }\n textarea { height: 200px; font: 12px/1.45 ui-monospace, monospace; resize: vertical; }\n .hint { margin: 6px 0 0; color: var(--muted); font-size: 12px; }\n .error { color: var(--danger); white-space: pre-wrap; margin: 8px 0; font-size: 12px; }\n .error:empty, .unmocked:empty, .candidates:empty { display: none; }\n .ready-check, .save-status { color: var(--muted); font-size: 12px; margin: 2px 0 0; }\n .ready-check[data-tone=ok] { color: var(--accent); }\n .ready-check[data-tone=bad] { color: var(--danger); }\n .save-status:empty { display: none; }\n details button[hidden] { display: none; }\n .candidates { margin-top: 6px; }\n .pick-box {\n position: fixed; pointer-events: none; box-sizing: border-box;\n border: 2px solid var(--accent); border-radius: 4px; background: #4747eb14;\n }\n .pick-box[hidden] { display: none; }\n .pick-box span {\n position: absolute; left: -2px; bottom: 100%; margin-bottom: 4px; max-width: 360px;\n padding: 2px 6px; border-radius: 4px; background: var(--accent); color: var(--accent-fg);\n font: 12px/1.4 ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n }\n .pick-box[data-below=true] span { bottom: auto; top: 100%; margin: 4px 0 0; }\n .candidates button { font-size: 12px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }\n .unmocked { margin-top: 10px; padding: 8px 10px; border-radius: 8px; background: var(--hover); color: var(--danger); font-size: 12px; }\n .unmocked p { margin: 0 0 4px; }\n .unmocked ul { padding-left: 16px; list-style: disc; }\n .unmocked code { overflow-wrap: anywhere; }\n em { font-style: normal; color: var(--danger); font-weight: 400; }\n details button, footer button {\n padding: 6px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--hover);\n }\n button[aria-pressed=\"true\"] { border-color: var(--accent); color: var(--accent); }\n footer { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }\n footer a { color: var(--accent); }\n`;\n"],"mappings":"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","names":["href"]}
|
|
1
|
+
{"version":3,"sources":["../src/panel.ts"],"sourcesContent":["import { z } from 'zod';\nimport { captureScenario } from './capture.js';\nimport { isLoopbackSaveUrl, PARAM, readInline, SAVE_ENDPOINT, scenarioHref } from './link.js';\nimport { checkReadySelector, closestReadySelector, suggestReadySelectors } from './ready.js';\nimport type { ScenarioSession } from './runtime.js';\nimport { parseScenario, type Scenario } from './schema.js';\n\nconst OPEN_KEY = 'scenario-panel:open';\n\n// \"Saved …\" survives the reload Vite does when a new file lands in the scenarios glob.\nconst FLASH_KEY = 'scenario-panel:flash';\n\n// The panel posts the captured scenario to `url`, so only a loopback save server is accepted.\nconst SaveProbeSchema = z.object({ dir: z.string(), url: z.string().refine(isLoopbackSaveUrl).optional() });\n\nconst SaveResultSchema = z.object({ path: z.string().optional(), error: z.string().optional() });\n\nconst FlashSchema = z.object({ message: z.string(), at: z.number() });\n\nexport interface PanelOptions {\n /** Corner to dock in, e.g. to avoid a chat widget. Default \"bottom-right\". */\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';\n /** Default: the maximum, so the panel stays above app overlays. */\n zIndex?: number;\n /** \"auto\" follows the OS colour scheme. Default \"auto\". */\n theme?: 'auto' | 'light' | 'dark';\n /**\n * Relative `$schema` path stamped on Download JSON, e.g. `../scenario.schema.json`.\n * Copy link stays schema-free.\n */\n schemaPath?: string;\n}\n\n/**\n * Add the floating scenario panel to the page. Framework-agnostic.\n * Remove it with `panel.remove()`; it unsubscribes from the session.\n */\nexport function mountPanel(session: ScenarioSession, options: PanelOptions = {}) {\n if (!customElements.get('scenario-panel')) {\n customElements.define('scenario-panel', ScenarioPanel);\n }\n\n const el = new ScenarioPanel();\n el.session = session;\n el.schemaPath = options.schemaPath;\n const [v, h] = (options.position ?? 'bottom-right').split('-');\n el.dataset.v = v;\n el.dataset.h = h;\n\n if (options.zIndex !== undefined) el.style.zIndex = String(options.zIndex);\n\n if (options.theme && options.theme !== 'auto') el.dataset.theme = options.theme;\n document.body.append(el);\n\n return el;\n}\n\n// Next.js loads client modules on the server too, where HTMLElement is undefined. Only\n// mountPanel creates a panel, and it runs in a browser, so on the server any base will do.\nconst Base: typeof HTMLElement = globalThis.HTMLElement ?? Object;\n\nclass ScenarioPanel extends Base {\n session!: ScenarioSession;\n schemaPath?: string;\n private render?: () => void;\n private unsubscribe?: () => void;\n private stopPick?: () => void;\n\n disconnectedCallback() {\n this.unsubscribe?.();\n this.unsubscribe = undefined;\n this.stopPick?.();\n this.stopPick = undefined;\n }\n\n connectedCallback() {\n // Re-attached (e.g. moved in the DOM): resubscribe and redraw.\n if (this.render) {\n this.unsubscribe = this.session.onChange(this.render);\n this.render();\n\n return;\n }\n\n const session = this.session;\n const { catalog } = session;\n const root = this.attachShadow({ mode: 'open' });\n\n root.innerHTML = `\n <style>${STYLES}</style>\n <button class=\"toggle\" aria-expanded=\"false\" aria-controls=\"drawer\"></button>\n <section id=\"drawer\" aria-label=\"Scenarios\" hidden>\n <header>\n <h2>Scenarios</h2>\n <button class=\"close\" aria-label=\"Close scenarios panel\">×</button>\n </header>\n <p class=\"error\" role=\"alert\"></p>\n <input type=\"search\" placeholder=\"Filter by name, description or tag\" aria-label=\"Filter scenarios\">\n <ul class=\"list\"></ul>\n <div class=\"unmocked\" role=\"status\"></div>\n <section class=\"live\" aria-label=\"Live state\"></section>\n <details class=\"save\">\n <summary>Save as scenario</summary>\n <p class=\"hint\">Saves the current URL, network responses and live state. <b>Copy link</b> shares it as is; <b>Download JSON</b> writes a file for your scenarios folder that keeps only what differs from the base.</p>\n <label>Name <input name=\"save-name\" placeholder=\"e.g. checkout-card-declined\" aria-label=\"Scenario name\"></label>\n <label>Description <input name=\"save-description\" aria-label=\"Scenario description\"></label>\n <label>Ready: what's on screen once this state has rendered <input name=\"save-ready\" placeholder='Use Mark ready, or type text=\"…\"' aria-label=\"Ready selector\"></label>\n <p class=\"ready-check\" role=\"status\"></p>\n <div class=\"row\">\n <button type=\"button\" data-act=\"mark-ready\">Mark ready</button>\n <button type=\"button\" data-act=\"clear-ready\">Clear</button>\n </div>\n <div class=\"candidates\" role=\"group\" aria-label=\"Suggested ready selectors\"></div>\n <div class=\"pick-box\" hidden><span></span></div>\n <label>Extends\n <select name=\"save-base\" aria-label=\"Base scenario to extend\"></select>\n </label>\n <div class=\"row\">\n <button data-act=\"save-project\" hidden>Save to project</button>\n <button data-act=\"save-link\">Copy link</button>\n <button data-act=\"save-json\">Download JSON</button>\n </div>\n <p class=\"save-status\" role=\"status\"></p>\n <input class=\"saved\" readonly aria-label=\"Saved scenario link\" hidden>\n </details>\n <details class=\"edit\">\n <summary>Edit current scenario</summary>\n <textarea spellcheck=\"false\" aria-label=\"Scenario JSON\"></textarea>\n <button data-act=\"apply\">Apply edited scenario</button>\n </details>\n <footer>\n <button data-act=\"copy\">Copy link</button>\n <a class=\"reset\">Reset</a>\n </footer>\n </section>`;\n\n const find = <T extends Element>(selector: string, type: new() => T): T => {\n const el = root.querySelector(selector);\n\n if (!(el instanceof type)) throw new Error(`executable-stories-showcase panel: missing ${selector}`);\n\n return el;\n };\n\n const toggle = find('.toggle', HTMLButtonElement);\n const drawer = find('#drawer', HTMLElement);\n const error = find('.error', HTMLParagraphElement);\n\n const run = (fn: () => void | Promise<void>) => async () => {\n error.textContent = '';\n\n try {\n await fn();\n } catch (err) {\n error.textContent = err instanceof Error ? err.message : String(err);\n }\n };\n\n const href = (s: Scenario) => {\n try {\n return { href: scenarioHref(catalog.resolve(s), { strict: session.strict, record: session.recording }), invalid: false };\n } catch {\n return { href: `?${PARAM}=${encodeURIComponent(s.name)}`, invalid: true };\n }\n };\n\n find('.list', HTMLUListElement).innerHTML = catalog.list\n .map((s) => ({ s, ...href(s) }))\n .map(\n ({ s, href, invalid }) => `\n <li data-name=\"${esc(s.name)}\" data-search=\"${\n esc([s.name, s.description, ...(s.tags ?? [])].join(' ').toLowerCase())\n }\">\n <a href=\"${esc(href)}\">\n <strong>${esc(s.name)}${invalid ? ' <em>(invalid, opens with error)</em>' : ''}</strong>\n ${s.description ? `<span>${esc(s.description)}</span>` : ''}\n ${s.tags?.length ? `<small>${s.tags.map(esc).join(' · ')}</small>` : ''}\n </a>\n <button class=\"add\" aria-label=\"Combine ${\n esc(s.name)\n } with the current scenario\" title=\"Combine with the current scenario\">+</button>\n </li>`,\n )\n .join('');\n root.querySelectorAll<HTMLLIElement>('li[data-name]').forEach((li) => {\n const name = li.dataset.name!;\n li.querySelector('a')!.onclick = (e) => {\n if (e.metaKey || e.ctrlKey || e.shiftKey) return; // let the browser open a new tab\n e.preventDefault();\n void run(() => session.open(name))();\n };\n\n li.querySelector<HTMLButtonElement>('.add')!.onclick = run(() => {\n const names = new Set((session.active?.name ?? '').split(',').filter(Boolean));\n names.add(name);\n\n return session.open([...names].join(','));\n });\n });\n const filter = find('input[type=search]', HTMLInputElement);\n filter.oninput = () => {\n const q = filter.value.toLowerCase().trim();\n root.querySelectorAll<HTMLLIElement>('li[data-search]').forEach((li) => {\n li.hidden = !li.dataset.search!.includes(q);\n });\n };\n\n const editors = new Map<string, { textarea: HTMLTextAreaElement; dirty: boolean; }>();\n\n const renderLive = () => {\n const keys = session.keys();\n\n for (const [key, ed] of editors) {\n if (!keys.includes(key)) {\n ed.textarea.closest('details')!.remove();\n editors.delete(key);\n }\n }\n\n for (const key of keys) {\n let ed = editors.get(key);\n\n if (!ed) {\n const details = document.createElement('details');\n details.innerHTML = `\n <summary>Live state: <code>${esc(key)}</code></summary>\n <textarea spellcheck=\"false\" aria-label=\"Live state ${esc(key)}\"></textarea>\n <div class=\"row\"><button data-act=\"live-apply\">Apply</button><button data-act=\"live-revert\">Revert</button></div>`;\n find('.live', HTMLElement).append(details);\n const entry = { textarea: details.querySelector('textarea')!, dirty: false };\n entry.textarea.oninput = () => (entry.dirty = true);\n details.ontoggle = () => renderLive();\n details.querySelector<HTMLButtonElement>('[data-act=live-apply]')!.onclick = run(async () => {\n await session.applyState(key, JSON.parse(entry.textarea.value));\n entry.dirty = false;\n renderLive();\n });\n details.querySelector<HTMLButtonElement>('[data-act=live-revert]')!.onclick = () => {\n entry.dirty = false;\n renderLive();\n };\n\n editors.set(key, ed = entry);\n }\n\n // Follow the app while open (state can be large), never overwriting what someone is typing.\n if (!ed.dirty && ed.textarea.closest('details')!.open) {\n ed.textarea.value = JSON.stringify(session.read(key) ?? null, null, 2);\n }\n }\n };\n\n const saveName = find('input[name=save-name]', HTMLInputElement);\n const saveDescription = find('input[name=save-description]', HTMLInputElement);\n const saveReady = find('input[name=save-ready]', HTMLInputElement);\n const saveBase = find('select[name=save-base]', HTMLSelectElement);\n const readyCheck = find('.ready-check', HTMLParagraphElement);\n const saveProject = find('[data-act=save-project]', HTMLButtonElement);\n const saveStatus = find('.save-status', HTMLParagraphElement);\n const candidates = find('.candidates', HTMLElement);\n const saved = find('.saved', HTMLInputElement);\n const saveDetails = find('.save', HTMLDetailsElement);\n const markReadyBtn = find('[data-act=mark-ready]', HTMLButtonElement);\n\n const fillBaseOptions = () => {\n const preferred = catalog.get('default')?.name\n ?? [session.active?.extends ?? []].flat()[0]\n ?? '';\n\n const previous = saveBase.value;\n\n saveBase.innerHTML = `<option value=\"\">(none: save everything)</option>`\n + catalog.list.map((s) => `<option value=\"${esc(s.name)}\">${esc(s.name)}</option>`).join('');\n\n if (previous && [...saveBase.options].some((o) => o.value === previous)) {\n saveBase.value = previous;\n } else {\n saveBase.value = preferred;\n }\n };\n\n fillBaseOptions();\n\n const renderCandidates = () => {\n const picks = suggestReadySelectors(document.body, { limit: 3 });\n candidates.innerHTML = picks.length\n ? `<p class=\"hint\">Suggestions</p><div class=\"row\">${\n picks.map((s) => `<button type=\"button\" data-candidate=\"${esc(s)}\">${esc(s)}</button>`).join('')\n }</div>`\n : '';\n candidates.querySelectorAll<HTMLButtonElement>('[data-candidate]').forEach((btn) => {\n btn.onclick = () => {\n saveReady.value = btn.dataset.candidate ?? '';\n updateReadyCheck();\n };\n });\n };\n\n saveDetails.ontoggle = () => {\n if (saveDetails.open) {\n fillBaseOptions();\n // Offer, don't fill: the top suggestion is often chrome present in every state.\n renderCandidates();\n updateReadyCheck();\n void probeSaveEndpoint();\n }\n };\n\n const stopPickMode = () => {\n this.stopPick?.();\n this.stopPick = undefined;\n markReadyBtn.setAttribute('aria-pressed', 'false');\n markReadyBtn.textContent = 'Mark ready';\n };\n\n const pickBox = find('.pick-box', HTMLDivElement);\n const pickLabel = find('.pick-box span', HTMLSpanElement);\n\n const outline = (el: Element | undefined, label: string) => {\n if (!el) {\n pickBox.hidden = true;\n\n return;\n }\n\n const r = el.getBoundingClientRect();\n Object.assign(pickBox.style, {\n top: `${r.top}px`,\n left: `${r.left}px`,\n width: `${r.width}px`,\n height: `${r.height}px`,\n });\n pickBox.dataset.below = String(r.top < 28);\n pickLabel.textContent = label;\n pickBox.hidden = false;\n };\n\n // Elements under the pointer, outside the panel, innermost first.\n const appTarget = (e: Event) => {\n const path = e.composedPath();\n\n if (path.includes(this)) return undefined;\n\n return path.find((n): n is Element =>\n n instanceof Element && n !== document.documentElement && n !== document.body\n );\n };\n\n const READY_MESSAGES = {\n empty:\n \"No ready selector: the link still works, but shoot and tests won't wait for this state to render.\",\n none: 'No match on this page: tests would time out waiting for it.',\n unchecked: \"Can't check this kind of selector here; Playwright still can.\",\n };\n\n let readyMatch: Element | undefined;\n\n const updateReadyCheck = () => {\n const value = saveReady.value.trim();\n const check = value ? checkReadySelector(value) : undefined;\n readyMatch = check?.status === 'match' ? check.first : undefined;\n\n let tone = 'muted';\n let message: string;\n\n if (!check) message = READY_MESSAGES.empty;\n else if (check.status === 'invalid') [tone, message] = ['bad', check.error];\n else if (check.status === 'none') [tone, message] = ['bad', READY_MESSAGES.none];\n else if (check.status === 'unchecked') message = READY_MESSAGES.unchecked;\n else if (!check.visible) {\n message = 'Matches, but the first match is hidden: tests wait until it shows.';\n } else if (check.count > 1) {\n message = `Matches ${check.count} elements; tests wait for the first (outlined).`;\n } else [tone, message] = ['ok', '✓ Matches 1 element on this page.'];\n\n readyCheck.dataset.tone = tone;\n readyCheck.textContent = message;\n\n // While the field has focus, show which element tests will wait on.\n if (this.shadowRoot?.activeElement === saveReady && !this.stopPick) outline(readyMatch, 'ready');\n };\n\n saveReady.oninput = updateReadyCheck;\n saveReady.onfocus = updateReadyCheck;\n\n saveReady.onblur = () => {\n if (!this.stopPick) outline(undefined, '');\n };\n\n updateReadyCheck();\n\n const startPickMode = () => {\n stopPickMode();\n markReadyBtn.setAttribute('aria-pressed', 'true');\n markReadyBtn.textContent = 'Click the UI… (Esc cancels)';\n document.documentElement.style.cursor = 'crosshair';\n\n const onMove = (e: MouseEvent) => {\n const target = appTarget(e);\n const hit = target && closestReadySelector(target);\n\n if (hit) outline(hit.element, hit.selector);\n else outline(target, 'No selector here: try text, a heading or a button');\n };\n\n // Swallow presses so the app doesn't react (open menus, follow links) while picking.\n const block = (e: Event) => {\n if (!appTarget(e)) return;\n e.preventDefault();\n e.stopPropagation();\n };\n\n const onClick = (e: MouseEvent) => {\n const target = appTarget(e);\n\n if (!target) return;\n block(e);\n const hit = closestReadySelector(target);\n\n // Stay in pick mode so the user can try another element; the outline says why.\n if (!hit) return;\n\n saveReady.value = hit.selector;\n updateReadyCheck();\n stopPickMode();\n saveReady.focus();\n };\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n // Esc only cancels picking; without this the drawer's Esc handler also closes the panel.\n e.stopPropagation();\n stopPickMode();\n };\n\n const pressEvents = ['pointerdown', 'mousedown', 'pointerup', 'mouseup'] as const;\n\n document.addEventListener('mousemove', onMove, true);\n document.addEventListener('click', onClick, true);\n document.addEventListener('keydown', onKey, true);\n\n for (const type of pressEvents) document.addEventListener(type, block, true);\n\n this.stopPick = () => {\n document.removeEventListener('mousemove', onMove, true);\n document.removeEventListener('click', onClick, true);\n document.removeEventListener('keydown', onKey, true);\n\n for (const type of pressEvents) document.removeEventListener(type, block, true);\n\n document.documentElement.style.removeProperty('cursor');\n pickBox.hidden = true;\n };\n };\n\n markReadyBtn.onclick = () => {\n if (markReadyBtn.getAttribute('aria-pressed') === 'true') stopPickMode();\n else startPickMode();\n };\n\n find('[data-act=clear-ready]', HTMLButtonElement).onclick = () => {\n saveReady.value = '';\n updateReadyCheck();\n };\n\n const capture = () => {\n const baseName = saveBase.value;\n const base = baseName ? catalog.get(baseName) : undefined;\n\n return captureScenario(session, {\n name: saveName.value.trim(),\n description: saveDescription.value.trim(),\n ready: saveReady.value.trim() || undefined,\n base,\n schemaPath: this.schemaPath,\n });\n };\n\n let probe: Promise<void> | undefined;\n // The Next.js route answers with a loopback save server's URL; Vite saves on its own origin.\n let saveUrl = SAVE_ENDPOINT;\n\n // The Vite plugin or the Next.js route answers with JSON; anything else (a build, an app\n // without either) returns HTML or a 404, and the button stays hidden.\n const probeSaveEndpoint = () =>\n probe ??= fetch(SAVE_ENDPOINT, { headers: { accept: 'application/json' } })\n .then(async (res) => {\n const isJson = res.ok && res.headers.get('content-type')?.includes('json');\n const probed = isJson ? SaveProbeSchema.safeParse(await res.json()) : undefined;\n\n if (!probed?.success) return;\n saveUrl = probed.data.url ?? SAVE_ENDPOINT;\n saveProject.textContent = `Save to ${probed.data.dir}`;\n saveProject.hidden = false;\n })\n .catch(() => {});\n\n const saveToProject = async (overwrite: boolean): Promise<void> => {\n const { forCatalog } = capture();\n\n const res = await fetch(saveUrl, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ scenario: forCatalog, overwrite }),\n });\n\n const body = SaveResultSchema.catch({}).parse(await res.json().catch(() => ({})));\n\n if (res.status === 409 && !overwrite) {\n if (confirm(`${body.path} already exists. Replace it?`)) return saveToProject(true);\n\n return;\n }\n\n if (!res.ok) throw new Error(body.error ?? `Save failed (HTTP ${res.status})`);\n\n const message = `Saved ${body.path}. Commit it with your change.`;\n saveStatus.textContent = message;\n\n try {\n sessionStorage.setItem(FLASH_KEY, JSON.stringify({ message, at: Date.now() }));\n } catch {\n // Storage blocked: the message won't survive a reload.\n }\n };\n\n saveProject.onclick = run(async () => {\n saveStatus.textContent = '';\n await session.settleRecording();\n await saveToProject(false);\n });\n\n try {\n const flash = FlashSchema.safeParse(JSON.parse(sessionStorage.getItem(FLASH_KEY) ?? 'null')).data;\n sessionStorage.removeItem(FLASH_KEY);\n\n // Only right after a save; a stale one would describe some other session.\n if (flash && Date.now() - flash.at < 10_000) {\n saveStatus.textContent = flash.message;\n saveDetails.open = true;\n }\n } catch {\n // Storage blocked or corrupt: nothing to show.\n }\n\n find('[data-act=save-link]', HTMLButtonElement).onclick = run(async () => {\n await session.settleRecording();\n const { flat } = capture();\n const link = scenarioHref(flat, { inline: true, strict: session.strict });\n saved.hidden = false;\n saved.value = link;\n saved.select();\n await navigator.clipboard?.writeText(link).catch(() => {});\n updateReadyCheck();\n });\n find('[data-act=save-json]', HTMLButtonElement).onclick = run(async () => {\n await session.settleRecording();\n const { forCatalog } = capture();\n const a = document.createElement('a');\n a.href = URL.createObjectURL(\n new Blob([`${JSON.stringify(forCatalog, null, 2)}\\n`], { type: 'application/json' }),\n );\n a.download = `${forCatalog.name}.json`;\n a.click();\n URL.revokeObjectURL(a.href);\n updateReadyCheck();\n });\n\n const editor = find('.edit textarea', HTMLTextAreaElement);\n\n if (readInline(location.hash)) find('.edit', HTMLDetailsElement).open = true;\n find('[data-act=apply]', HTMLButtonElement).onclick = run(() =>\n session.openInline(parseScenario(JSON.parse(editor.value)))\n );\n\n find('[data-act=copy]', HTMLButtonElement).onclick = run(async () => {\n try {\n await navigator.clipboard.writeText(location.href);\n } catch {\n prompt('Copy this link', location.href);\n }\n });\n // An absolute same-origin URL: a path starting with \"//\" must not read as another host.\n const reset = new URL(location.href);\n reset.search = '';\n reset.hash = '';\n reset.searchParams.set(PARAM, '');\n find('.reset', HTMLAnchorElement).href = reset.href;\n\n const setOpen = (open: boolean) => {\n drawer.hidden = !open;\n toggle.setAttribute('aria-expanded', String(open));\n\n try {\n sessionStorage.setItem(OPEN_KEY, open ? '1' : '');\n } catch {\n // sessionStorage throws when storage is blocked; the panel just forgets\n }\n };\n\n let stored: string | null = null;\n\n try {\n stored = sessionStorage.getItem(OPEN_KEY);\n } catch {\n // sessionStorage throws when storage is blocked; the panel just forgets\n }\n\n setOpen(!!stored);\n toggle.onclick = () => setOpen(!!drawer.hidden);\n find('.close', HTMLButtonElement).onclick = () => {\n setOpen(false);\n toggle.focus();\n };\n\n drawer.onkeydown = (e) => {\n if (e.key === 'Escape') {\n setOpen(false);\n toggle.focus();\n }\n };\n\n let shownScenario: string | undefined;\n\n const render = () => {\n const { active, strict, unhandled, recording } = session;\n const mode = recording ? ` (recording ${session.recorded().length})` : strict ? ' (strict)' : '';\n toggle.innerHTML = `Scenario: <b>${esc(active?.name ?? 'none')}</b>${mode}`;\n const names = (active?.name ?? '').split(',');\n root.querySelectorAll<HTMLLIElement>('li[data-name]').forEach((li) => {\n const a = li.querySelector('a')!;\n\n if (names.includes(li.dataset.name!)) a.setAttribute('aria-current', 'true');\n else a.removeAttribute('aria-current');\n });\n // While recording, unmocked requests are the point: they're what gets saved.\n find('.unmocked', HTMLElement).innerHTML = unhandled.length && !recording\n ? `<p>${unhandled.length} request(s) not covered by this scenario${\n strict ? ' (answered 501)' : ''\n }:</p>\n <ul>${unhandled.map((u) => `<li><code>${esc(u)}</code></li>`).join('')}</ul>`\n : '';\n\n // Set as a property, not markup, so page text selectors never match the panel's JSON.\n if (active?.name !== shownScenario) {\n shownScenario = active?.name;\n editor.value = JSON.stringify(active ?? { name: 'custom', extends: catalog.list[0]?.name }, null, 2);\n fillBaseOptions();\n }\n\n renderLive();\n\n // The app re-rendered: the ready selector may match now, or no longer.\n if (saveDetails.open) updateReadyCheck();\n };\n\n this.render = render;\n render();\n this.unsubscribe = session.onChange(render);\n }\n}\n\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => `&#${c.charCodeAt(0)};`);\n\nconst STYLES = `\n :host {\n --bg: #ffffff; --fg: #1b1b1f; --muted: #5d5d66; --line: #dcdce2;\n --accent: #4747eb; --accent-fg: #ffffff; --hover: #f2f2f7; --danger: #b42318;\n all: initial; position: fixed; right: 16px; bottom: 16px; z-index: 2147483647;\n font: 14px/1.4 system-ui, sans-serif; color: var(--fg);\n }\n @media (prefers-color-scheme: dark) {\n :host(:not([data-theme=\"light\"])) {\n --bg: #1c1c21; --fg: #ececf1; --muted: #a3a3ad; --line: #34343c;\n --accent: #8c8cff; --accent-fg: #101014; --hover: #26262d; --danger: #ff8a80;\n }\n }\n :host([data-theme=\"dark\"]) {\n --bg: #1c1c21; --fg: #ececf1; --muted: #a3a3ad; --line: #34343c;\n --accent: #8c8cff; --accent-fg: #101014; --hover: #26262d; --danger: #ff8a80;\n }\n :host([data-v=\"top\"]) { bottom: auto; top: 16px; }\n :host([data-h=\"left\"]) { right: auto; left: 16px; }\n :host([data-v=\"top\"]) #drawer { bottom: auto; top: 48px; }\n :host([data-h=\"left\"]) #drawer { right: auto; left: 0; }\n :host([data-h=\"left\"]) .toggle { margin-left: 0; }\n * { box-sizing: border-box; font: inherit; color: inherit; }\n button, a { cursor: pointer; }\n :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n .toggle {\n display: block; margin-left: auto; padding: 8px 14px; border: 0; border-radius: 999px;\n background: var(--accent); color: var(--accent-fg); box-shadow: 0 4px 16px #0003;\n }\n .toggle b, strong { font-weight: 700; }\n #drawer {\n position: absolute; right: 0; bottom: 48px; width: min(400px, calc(100vw - 32px));\n max-height: min(680px, calc(100vh - 96px)); overflow: auto; padding: 12px;\n background: var(--bg); border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 12px 40px #0004;\n }\n #drawer[hidden] { display: none; }\n header, footer, .row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }\n .row { justify-content: flex-start; margin-top: 8px; flex-wrap: wrap; }\n h2 { margin: 0; font-size: 16px; font-weight: 700; }\n .close { border: 0; background: none; font-size: 22px; line-height: 1; padding: 4px 8px; border-radius: 6px; }\n input, textarea, select {\n width: 100%; margin: 6px 0; padding: 8px 10px; border: 1px solid var(--line);\n border-radius: 8px; background: var(--bg);\n }\n label { display: block; margin-top: 6px; color: var(--muted); font-size: 12px; }\n label input, label select { color: var(--fg); font-size: 14px; }\n ul { list-style: none; margin: 0; padding: 0; }\n li[hidden] { display: none; }\n .list li { display: flex; align-items: stretch; gap: 4px; }\n .list a {\n flex: 1; min-width: 0; overflow-wrap: anywhere; display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; border-radius: 8px;\n text-decoration: none; border-left: 3px solid transparent;\n }\n .list a:hover { background: var(--hover); }\n .list a[aria-current] { border-left-color: var(--accent); background: var(--hover); }\n .list span { color: var(--muted); }\n .list small { color: var(--muted); font-size: 12px; }\n .add { width: 32px; border: 0; border-radius: 8px; background: none; color: var(--muted); font-size: 18px; }\n .add:hover { background: var(--hover); color: var(--fg); }\n details { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }\n summary { cursor: pointer; font-weight: 600; }\n code { font: 12px ui-monospace, monospace; }\n textarea { height: 200px; font: 12px/1.45 ui-monospace, monospace; resize: vertical; }\n .hint { margin: 6px 0 0; color: var(--muted); font-size: 12px; }\n .error { color: var(--danger); white-space: pre-wrap; margin: 8px 0; font-size: 12px; }\n .error:empty, .unmocked:empty, .candidates:empty { display: none; }\n .ready-check, .save-status { color: var(--muted); font-size: 12px; margin: 2px 0 0; }\n .ready-check[data-tone=ok] { color: var(--accent); }\n .ready-check[data-tone=bad] { color: var(--danger); }\n .save-status:empty { display: none; }\n details button[hidden] { display: none; }\n .candidates { margin-top: 6px; }\n .pick-box {\n position: fixed; pointer-events: none; box-sizing: border-box;\n border: 2px solid var(--accent); border-radius: 4px; background: #4747eb14;\n }\n .pick-box[hidden] { display: none; }\n .pick-box span {\n position: absolute; left: -2px; bottom: 100%; margin-bottom: 4px; max-width: 360px;\n padding: 2px 6px; border-radius: 4px; background: var(--accent); color: var(--accent-fg);\n font: 12px/1.4 ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n }\n .pick-box[data-below=true] span { bottom: auto; top: 100%; margin: 4px 0 0; }\n .candidates button { font-size: 12px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }\n .unmocked { margin-top: 10px; padding: 8px 10px; border-radius: 8px; background: var(--hover); color: var(--danger); font-size: 12px; }\n .unmocked p { margin: 0 0 4px; }\n .unmocked ul { padding-left: 16px; list-style: disc; }\n .unmocked code { overflow-wrap: anywhere; }\n em { font-style: normal; color: var(--danger); font-weight: 400; }\n details button, footer button {\n padding: 6px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--hover);\n }\n button[aria-pressed=\"true\"] { border-color: var(--accent); color: var(--accent); }\n footer { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }\n footer a { color: var(--accent); }\n`;\n"],"mappings":"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","names":["href"]}
|
package/dist/playwright.js
CHANGED
package/dist/redact.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { Json } from './schema.js';
|
|
2
|
-
/**
|
|
3
|
-
* Remove secrets from a recorded response body: strings under secret-like keys become
|
|
4
|
-
* "[redacted]" and numbers become 0, and signed JWTs anywhere lose their signature but keep
|
|
5
|
-
* their claims.
|
|
6
|
-
* Returns the paths it changed, e.g. `user.accessToken`.
|
|
7
|
-
*/
|
|
8
2
|
export interface Redacted {
|
|
9
3
|
value: Json;
|
|
10
4
|
/** Paths that changed, e.g. `user.accessToken`. */
|
|
11
5
|
redacted: string[];
|
|
12
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Remove secrets from recorded responses and captured state: strings under secret-like keys
|
|
9
|
+
* become "[redacted]" and numbers become 0, and signed JWTs anywhere lose their signature but
|
|
10
|
+
* keep their claims.
|
|
11
|
+
*/
|
|
13
12
|
export declare function redactSecrets(value: Json): Redacted;
|
|
14
13
|
/**
|
|
15
14
|
* Drops URL params whose value redaction would change (`?api_key=…`, `?token=…`), and OAuth's
|
package/dist/vite.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "executable-stories-showcase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Put a running app into any state from a URL: shareable JSON scenarios for network, URL and app state, with a dev panel, Playwright helpers and adapters.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/network.ts"],"sourcesContent":["import { delay, http, HttpResponse } from 'msw';\nimport type { NetworkEntry } from './schema.js';\n\nconst verbs = {\n GET: http.get,\n POST: http.post,\n PUT: http.put,\n PATCH: http.patch,\n DELETE: http.delete,\n HEAD: http.head,\n OPTIONS: http.options,\n} satisfies Record<NetworkEntry['method'], typeof http.get>;\n\n// The worker can't see a request's destination, so code is recognised by its path.\n// ponytail: a script URL with no extension (e.g. `/gtag/js`) can still be answered by a fixture;\n// refusing links off localhost (`allowLinks`) is the upgrade if that matters.\nconst isCode = (href: string) => {\n const { pathname } = new URL(href);\n\n return /\\.(m?[jt]sx?|css|s[ac]ss|less|html?|wasm)$/i.test(pathname)\n || pathname.startsWith('/@') || pathname.includes('/node_modules/');\n};\n\n/** Turn scenario network entries into MSW handlers. First match wins. */\nexport function toHandlers(entries: NetworkEntry[] = []) {\n return entries.map((entry) => {\n const responses = entry.sequence ?? [entry.response!];\n let calls = 0;\n\n return verbs[entry.method](entry.path, async ({ request }): Promise<Response | undefined> => {\n // Fixtures answer the app's requests, never a page load or code: a scenario from a shared\n // link could otherwise serve its own HTML or scripts (a lazy chunk) on the app's origin.\n if (request.mode === 'navigate' || isCode(request.url)) return undefined;\n\n if (entry.query) {\n const params = new URL(request.url).searchParams;\n const matches = Object.entries(entry.query).every(([k, v]) => params.get(k) === v);\n\n if (!matches) return undefined; // fall through to the next handler\n }\n\n const r = responses[Math.min(calls++, responses.length - 1)];\n\n if (r.delay !== undefined) await delay(r.delay);\n const init = { status: r.status, headers: r.headers };\n\n if (r.text !== undefined) return HttpResponse.text(r.text, init);\n\n if (r.body === undefined) return new HttpResponse(null, init);\n\n return HttpResponse.json(r.body, init);\n });\n });\n}\n"],"mappings":";AAAA,SAAS,OAAO,MAAM,oBAAoB;AAG1C,IAAM,QAAQ;AAAA,EACZ,KAAK,KAAK;AAAA,EACV,MAAM,KAAK;AAAA,EACX,KAAK,KAAK;AAAA,EACV,OAAO,KAAK;AAAA,EACZ,QAAQ,KAAK;AAAA,EACb,MAAM,KAAK;AAAA,EACX,SAAS,KAAK;AAChB;AAKA,IAAM,SAAS,CAAC,SAAiB;AAC/B,QAAM,EAAE,SAAS,IAAI,IAAI,IAAI,IAAI;AAEjC,SAAO,8CAA8C,KAAK,QAAQ,KAC7D,SAAS,WAAW,IAAI,KAAK,SAAS,SAAS,gBAAgB;AACtE;AAGO,SAAS,WAAW,UAA0B,CAAC,GAAG;AACvD,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,UAAM,YAAY,MAAM,YAAY,CAAC,MAAM,QAAS;AACpD,QAAI,QAAQ;AAEZ,WAAO,MAAM,MAAM,MAAM,EAAE,MAAM,MAAM,OAAO,EAAE,QAAQ,MAAqC;AAG3F,UAAI,QAAQ,SAAS,cAAc,OAAO,QAAQ,GAAG,EAAG,QAAO;AAE/D,UAAI,MAAM,OAAO;AACf,cAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE;AACpC,cAAM,UAAU,OAAO,QAAQ,MAAM,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,IAAI,CAAC,MAAM,CAAC;AAEjF,YAAI,CAAC,QAAS,QAAO;AAAA,MACvB;AAEA,YAAM,IAAI,UAAU,KAAK,IAAI,SAAS,UAAU,SAAS,CAAC,CAAC;AAE3D,UAAI,EAAE,UAAU,OAAW,OAAM,MAAM,EAAE,KAAK;AAC9C,YAAM,OAAO,EAAE,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ;AAEpD,UAAI,EAAE,SAAS,OAAW,QAAO,aAAa,KAAK,EAAE,MAAM,IAAI;AAE/D,UAAI,EAAE,SAAS,OAAW,QAAO,IAAI,aAAa,MAAM,IAAI;AAE5D,aAAO,aAAa,KAAK,EAAE,MAAM,IAAI;AAAA,IACvC,CAAC;AAAA,EACH,CAAC;AACH;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/link.ts"],"sourcesContent":["import type { Scenario } from './schema.js';\n\ndeclare global {\n /** Requests the active scenario didn't cover, for tests and agents (see `unhandledRequests`). */\n var executableStoriesShowcaseUnhandled: string[] | undefined;\n}\n\nexport const PARAM = 'scenario';\n\n/** Fail unmatched requests instead of letting them reach the network. */\nexport const STRICT_PARAM = 'scenario-strict';\n\n/** Record real responses so they can be saved as a scenario. */\nexport const RECORD_PARAM = 'scenario-record';\n\n/** Dev-server route the `executable-stories-showcase/vite` plugin serves for the panel's \"Save to project\". */\nexport const SAVE_ENDPOINT = '/__executable-stories-showcase/save';\n\nexport const LOOPBACK = new Set(['127.0.0.1', 'localhost', '[::1]']);\n\n/**\n * Whether a save URL the app's save route hands the panel is a loopback save server, such as\n * the one the Next.js route starts on 127.0.0.1. The panel posts captured state there, so a\n * URL on any other host is refused.\n */\nexport const isLoopbackSaveUrl = (value: string): boolean => {\n try {\n const url = new URL(value);\n\n return url.protocol === 'http:' && LOOPBACK.has(url.hostname) && url.pathname === SAVE_ENDPOINT;\n } catch {\n return false;\n }\n};\n\n/** Inline scenario JSON, kept in the URL hash so it never reaches a server. */\nexport const DATA_KEY = 'scenario-data';\n\n/**\n * A shareable URL that opens the app in a resolved scenario.\n * `inline` embeds the flattened scenario, so the link works without the catalog.\n */\nexport function scenarioHref(\n resolved: Scenario,\n { base = location.href, inline = false, strict = false } = {},\n): string {\n const url = new URL(base);\n url.search = '';\n url.hash = '';\n\n if (resolved.path) url.pathname = resolved.path;\n\n for (const [k, v] of Object.entries(resolved.url ?? {})) {\n if (v !== null) url.searchParams.set(k, v);\n }\n\n if (inline) {\n const { $schema: _schema, extends: _extends, ...snapshot } = resolved;\n url.hash = new URLSearchParams({ [DATA_KEY]: JSON.stringify(snapshot) }).toString();\n } else {\n url.searchParams.set(PARAM, resolved.name);\n }\n\n if (strict) url.searchParams.set(STRICT_PARAM, '');\n\n return url.toString();\n}\n\n/** Inline scenario JSON from a URL hash, if present. */\nexport const readInline = (hash: string) => new URLSearchParams(hash.slice(1)).get(DATA_KEY);\n"],"mappings":";AAOO,IAAM,QAAQ;AAGd,IAAM,eAAe;AAGrB,IAAM,eAAe;AAGrB,IAAM,gBAAgB;AAEtB,IAAM,WAAW,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAO5D,IAAM,oBAAoB,CAAC,UAA2B;AAC3D,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AAEzB,WAAO,IAAI,aAAa,WAAW,SAAS,IAAI,IAAI,QAAQ,KAAK,IAAI,aAAa;AAAA,EACpF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,IAAM,WAAW;AAMjB,SAAS,aACd,UACA,EAAE,OAAO,SAAS,MAAM,SAAS,OAAO,SAAS,MAAM,IAAI,CAAC,GACpD;AACR,QAAM,MAAM,IAAI,IAAI,IAAI;AACxB,MAAI,SAAS;AACb,MAAI,OAAO;AAEX,MAAI,SAAS,KAAM,KAAI,WAAW,SAAS;AAE3C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,SAAS,OAAO,CAAC,CAAC,GAAG;AACvD,QAAI,MAAM,KAAM,KAAI,aAAa,IAAI,GAAG,CAAC;AAAA,EAC3C;AAEA,MAAI,QAAQ;AACV,UAAM,EAAE,SAAS,SAAS,SAAS,UAAU,GAAG,SAAS,IAAI;AAC7D,QAAI,OAAO,IAAI,gBAAgB,EAAE,CAAC,QAAQ,GAAG,KAAK,UAAU,QAAQ,EAAE,CAAC,EAAE,SAAS;AAAA,EACpF,OAAO;AACL,QAAI,aAAa,IAAI,OAAO,SAAS,IAAI;AAAA,EAC3C;AAEA,MAAI,OAAQ,KAAI,aAAa,IAAI,cAAc,EAAE;AAEjD,SAAO,IAAI,SAAS;AACtB;AAGO,IAAM,aAAa,CAAC,SAAiB,IAAI,gBAAgB,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,QAAQ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/redact.ts","../src/jwt.ts","../src/capture.ts"],"sourcesContent":["import { z } from 'zod';\nimport { unsignedJwt } from './jwt.js';\nimport type { Json, JsonObject } from './schema.js';\n\n/** Keys whose values are secrets whatever they hold. */\nconst SECRET_KEY =\n /pass(word|phrase)?$|secret|api[-_]?key|private[-_]?key|access[-_]?key|credential|^otp$|card[-_]?number|^cvc$|^cvv$/i;\n\n/** Keys holding bearer values: a JWT keeps its claims, anything else is replaced. */\nconst TOKEN_KEY = /token|session[-_]?id|^sid$|authorization|cookie/i;\n\n/** Numeric bearer values: session ids. */\nconst NUMERIC_ID_KEY = /session[-_]?id$|^sid$/i;\n\nconst JWT = /^[\\w-]+\\.[\\w-]+\\.[\\w-]*$/;\n\n/**\n * Values that are credentials whatever key holds them: an auth header, and the key formats of\n * Stripe, GitHub, AWS and Slack.\n */\nconst SECRET_VALUE = /^(Bearer|Basic) \\S|^[sr]k_(live|test)_\\w|^gh[pousr]_\\w{20}|^github_pat_\\w|^AKIA[0-9A-Z]{16}$|^xox[abprs]-/;\n\n/** URL params that carry a one-time credential, such as an OAuth authorization `code`. */\nconst SECRET_PARAM = /^(code|state|nonce)$/i;\n\nconst PLACEHOLDER = '[redacted]';\n\nconst decode = (part: string): JsonObject | undefined => {\n try {\n const bytes = Uint8Array.from(atob(part.replace(/-/g, '+').replace(/_/g, '/')), (c) => c.charCodeAt(0));\n const value: Json = JSON.parse(new TextDecoder().decode(bytes));\n\n return value instanceof Object && !Array.isArray(value) ? value : undefined;\n } catch {\n return undefined;\n }\n};\n\n/** A JWT re-issued unsigned with the same claims, so the app still decodes it but no server accepts it. */\nconst unsign = (token: string) => {\n if (!JWT.test(token)) return undefined;\n const [header = '', payload = ''] = token.split('.');\n\n if (!decode(header)) return undefined;\n const claims = decode(payload);\n\n return claims && unsignedJwt(claims);\n};\n\n/**\n * Remove secrets from a recorded response body: strings under secret-like keys become\n * \"[redacted]\" and numbers become 0, and signed JWTs anywhere lose their signature but keep\n * their claims.\n * Returns the paths it changed, e.g. `user.accessToken`.\n */\nexport interface Redacted {\n value: Json;\n /** Paths that changed, e.g. `user.accessToken`. */\n redacted: string[];\n}\n\nexport function redactSecrets(value: Json): Redacted {\n const redacted: string[] = [];\n\n const walk = (node: Json, path: string, key: string): Json => {\n const text = z.string().safeParse(node);\n // A JWT keeps its claims, whatever key holds it: the app may decode it.\n const jwt = text.success ? unsign(text.data) : undefined;\n\n if (jwt !== undefined) {\n if (jwt !== text.data) redacted.push(path);\n\n return jwt;\n }\n\n // Checked before the value's type: a card number or one-time code is often a number.\n // Token-like keys only for strings: `tokens: 3` is usually a count, not a credential.\n const secret = SECRET_KEY.test(key) || TOKEN_KEY.test(key);\n const number = z.number().safeParse(node);\n\n if ((SECRET_KEY.test(key) || NUMERIC_ID_KEY.test(key)) && number.success) {\n redacted.push(path);\n\n return 0; // still a number, so the app's parsing and formatting keep working\n }\n\n if (text.success && ((secret && text.data) || SECRET_VALUE.test(text.data))) {\n redacted.push(path);\n\n return PLACEHOLDER;\n }\n\n if (text.success || number.success) return node;\n\n if (Array.isArray(node)) return node.map((item, i) => walk(item, `${path}[${i}]`, key));\n\n if (node instanceof Object) {\n return Object.fromEntries(\n Object.entries(node).map(([k, v]) => [k, walk(v, path ? `${path}.${k}` : k, k)]),\n );\n }\n\n return node;\n };\n\n return { value: walk(value, '', ''), redacted };\n}\n\n/**\n * Drops URL params whose value redaction would change (`?api_key=…`, `?token=…`), and OAuth's\n * one-time `code`, `state` and `nonce`. Saved params are matched or replayed as written, so `\"[redacted]\"`\n * would be wrong; without the param, a recorded request matches whatever value the app sends.\n */\nexport function withoutSecretParams(params: Record<string, string>, where: string): Record<string, string> {\n const redacted = z.record(z.string(), z.unknown()).parse(redactSecrets(params).value);\n const dropped = Object.keys(params).filter((key) => SECRET_PARAM.test(key) || redacted[key] !== params[key]);\n\n if (dropped.length) console.info(`[executable-stories-showcase] left ${dropped.join(', ')} out of ${where}`);\n\n return Object.fromEntries(Object.entries(params).filter(([key]) => !dropped.includes(key)));\n}\n","import type { JsonObject } from './schema.js';\n\n/** Base64url of UTF-8, so claims in any script (\"José\", \"李雷\", \"Zoë\") survive decoding. */\nconst encode = (value: JsonObject) =>\n btoa(Array.from(new TextEncoder().encode(JSON.stringify(value)), (b) => String.fromCharCode(b)).join(''))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\n/**\n * An unsigned JWT (`alg: \"none\"`) carrying `claims`, for mocked responses in \"signed-in\" scenarios\n * when the app only decodes the token it gets back. Anything that verifies signatures rejects it,\n * which keeps it out of real backends. Set `exp` in the future if the app checks expiry.\n */\nexport const unsignedJwt = (claims: JsonObject): string =>\n `${encode({ alg: 'none', typ: 'JWT' })}.${encode(claims)}.`;\n","import { diffScenario } from './catalog.js';\nimport { PARAM, RECORD_PARAM, STRICT_PARAM } from './link.js';\nimport { redactSecrets, withoutSecretParams } from './redact.js';\nimport type { ScenarioSession } from './runtime.js';\nimport { parseScenario, type Scenario, type ScenarioInput } from './schema.js';\n\nconst SCENARIO_PARAMS = new Set([PARAM, STRICT_PARAM, RECORD_PARAM]);\n\nexport interface CaptureOptions {\n name: string;\n description?: string;\n /** Playwright selector that marks this UI state ready. */\n ready?: string;\n /**\n * Catalog scenario to `extends`. Diff uses its resolved (flattened) form.\n * Omit for a full self-contained snapshot.\n */\n base?: Scenario;\n /** Relative `$schema` path stamped on the catalog file only, e.g. `../scenario.schema.json`. */\n schemaPath?: string;\n}\n\nexport interface CapturedScenario {\n /** Flattened snapshot for inline share links. */\n flat: Scenario;\n /** File to commit: diffs against `base` when set, otherwise the flat snapshot as input. */\n forCatalog: ScenarioInput;\n}\n\n/**\n * Snapshot what the app shows now: current route and URL params, the active network\n * fixtures (plus recorded responses), and every connected adapter's live state.\n */\nexport function captureScenario(session: ScenarioSession, options: CaptureOptions): CapturedScenario {\n const params: Record<string, string> = {};\n new URLSearchParams(location.search).forEach((v, k) => {\n if (!SCENARIO_PARAMS.has(k)) params[k] = v;\n });\n // A page URL can carry a token or a magic-link code; it is saved like recorded traffic.\n const url = withoutSecretParams(params, 'the captured URL');\n\n const state = Object.fromEntries(\n session.keys().flatMap((key) => {\n const live = session.read(key);\n if (live === undefined) return [];\n\n // A store or cache can hold a session token; captured scenarios get committed and shared,\n // so they get the same redaction as recorded responses.\n const { value, redacted } = redactSecrets(live);\n\n if (redacted.length) {\n console.info(`[executable-stories-showcase] redacted ${redacted.join(', ')} in state[\"${key}\"]`);\n }\n\n return [[key, value] as const];\n }),\n );\n\n // Recorded responses go after the scenario's own: they answered requests it didn't mock.\n const network = [...(session.resolved?.network ?? []), ...session.recorded()];\n\n const draft = parseScenario({\n name: options.name,\n description: options.description || undefined,\n path: location.pathname,\n url,\n network: network.length ? network : undefined,\n handlers: session.resolved?.handlers,\n viewport: session.resolved?.viewport,\n state,\n ready: options.ready || undefined,\n });\n\n const flat = session.catalog.resolve(draft);\n const flatBase = options.base ? session.catalog.resolve(options.base) : undefined;\n const forCatalog = diffScenario(flat, flatBase);\n\n if (options.schemaPath) forCatalog.$schema = options.schemaPath;\n\n return { flat, forCatalog };\n}\n"],"mappings":"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","names":[]}
|
package/dist/network-4UFPO74C.js
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|