@mlola/browser-evals 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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -0
  3. package/dist/bin.d.ts +7 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +59 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/cli-args.d.ts +7 -0
  8. package/dist/cli-args.d.ts.map +1 -0
  9. package/dist/cli-args.js +28 -0
  10. package/dist/cli-args.js.map +1 -0
  11. package/dist/fixtures/server.d.ts +22 -0
  12. package/dist/fixtures/server.d.ts.map +1 -0
  13. package/dist/fixtures/server.js +149 -0
  14. package/dist/fixtures/server.js.map +1 -0
  15. package/dist/fixtures/site.d.ts +51 -0
  16. package/dist/fixtures/site.d.ts.map +1 -0
  17. package/dist/fixtures/site.js +312 -0
  18. package/dist/fixtures/site.js.map +1 -0
  19. package/dist/harness.d.ts +85 -0
  20. package/dist/harness.d.ts.map +1 -0
  21. package/dist/harness.js +193 -0
  22. package/dist/harness.js.map +1 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/report.d.ts +33 -0
  28. package/dist/report.d.ts.map +1 -0
  29. package/dist/report.js +121 -0
  30. package/dist/report.js.map +1 -0
  31. package/dist/suites/core.d.ts +10 -0
  32. package/dist/suites/core.d.ts.map +1 -0
  33. package/dist/suites/core.js +144 -0
  34. package/dist/suites/core.js.map +1 -0
  35. package/dist/suites/matrix.d.ts +9 -0
  36. package/dist/suites/matrix.d.ts.map +1 -0
  37. package/dist/suites/matrix.js +72 -0
  38. package/dist/suites/matrix.js.map +1 -0
  39. package/dist/suites/regression.d.ts +12 -0
  40. package/dist/suites/regression.d.ts.map +1 -0
  41. package/dist/suites/regression.js +39 -0
  42. package/dist/suites/regression.js.map +1 -0
  43. package/dist/suites/security.d.ts +9 -0
  44. package/dist/suites/security.d.ts.map +1 -0
  45. package/dist/suites/security.js +91 -0
  46. package/dist/suites/security.js.map +1 -0
  47. package/dist/suites/stress.d.ts +6 -0
  48. package/dist/suites/stress.d.ts.map +1 -0
  49. package/dist/suites/stress.js +64 -0
  50. package/dist/suites/stress.js.map +1 -0
  51. package/package.json +55 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MLola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # @mlola/browser-evals
2
+
3
+ The deterministic eval corpus of the
4
+ [MLola Browser Runtime](https://mlola.com/browser-runtime): a local fixture site
5
+ plus core, matrix, security, regression and stress suites that run against real
6
+ Chrome.
7
+
8
+ ```bash
9
+ npx mlola browser eval --suite core
10
+ ```
11
+
12
+ ```ts
13
+ import { runSuites, startFixtureServer } from "@mlola/browser-evals";
14
+ ```
package/dist/bin.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Evals CLI: `tsx packages/evals/src/bin.ts run --suite core`
4
+ * (`mlola browser eval` shells into this).
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;GAGG"}
package/dist/bin.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Evals CLI: `tsx packages/evals/src/bin.ts run --suite core`
4
+ * (`mlola browser eval` shells into this).
5
+ */
6
+ import { parseArgs } from "./cli-args.js";
7
+ import { formatReport, runSuites } from "./report.js";
8
+ import { startFixtureServer, startFixtureServerOn } from "./fixtures/server.js";
9
+ import { casesForSuite } from "./report.js";
10
+ const main = async () => {
11
+ const argv = process.argv.slice(2);
12
+ const { flags, positional } = parseArgs(argv);
13
+ const command = positional[0] ?? "run";
14
+ if (command === "serve") {
15
+ const port = typeof flags["port"] === "string" ? Number(flags["port"]) : undefined;
16
+ const fixture = port === undefined ? await startFixtureServer() : await startFixtureServerOn(port);
17
+ process.stdout.write(`fixture site: ${fixture.url}\n`);
18
+ process.stdout.write("routes: / /billing /portal /autocomplete /dialog /rerender /decoys /injection /big /slow /canvas /danger\n");
19
+ await new Promise(() => undefined);
20
+ return 0;
21
+ }
22
+ if (command === "list") {
23
+ const suite = flags["suite"] ?? "all";
24
+ for (const testCase of casesForSuite(suite)) {
25
+ process.stdout.write(`${testCase.suite.padEnd(10)} ${testCase.name}${testCase.blockedReason ? " (blocked)" : ""}\n`);
26
+ }
27
+ return 0;
28
+ }
29
+ const suite = flags["suite"] ?? "core";
30
+ const provider = flags["provider"] ?? "scripted";
31
+ const filter = typeof flags["filter"] === "string" ? flags["filter"] : undefined;
32
+ const headless = flags["headed"] !== true;
33
+ const json = flags["json"] === true;
34
+ const report = await runSuites({
35
+ suite,
36
+ provider,
37
+ ...(filter ? { filter } : {}),
38
+ headless,
39
+ onProgress: json ? undefined : (line) => process.stdout.write(` ${line}\n`),
40
+ });
41
+ if (json) {
42
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
43
+ }
44
+ else {
45
+ process.stdout.write(`${formatReport(report)}\n`);
46
+ if (report.resultsPath)
47
+ process.stdout.write(`results: ${report.resultsPath}\n`);
48
+ }
49
+ return report.failed === 0 ? 0 : 1;
50
+ };
51
+ main()
52
+ .then((code) => {
53
+ process.exitCode = code;
54
+ })
55
+ .catch((error) => {
56
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
57
+ process.exitCode = 1;
58
+ });
59
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** Argument parsing for the evals CLI. */
2
+ export interface ParsedArgs {
3
+ flags: Record<string, string | boolean>;
4
+ positional: string[];
5
+ }
6
+ export declare const parseArgs: (argv: string[]) => ParsedArgs;
7
+ //# sourceMappingURL=cli-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAE1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAG,UAwB1C,CAAC"}
@@ -0,0 +1,28 @@
1
+ /** Argument parsing for the evals CLI. */
2
+ export const parseArgs = (argv) => {
3
+ const flags = {};
4
+ const positional = [];
5
+ for (let i = 0; i < argv.length; i += 1) {
6
+ const arg = argv[i];
7
+ if (arg.startsWith("--")) {
8
+ const eq = arg.indexOf("=");
9
+ if (eq > 0) {
10
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
11
+ continue;
12
+ }
13
+ const key = arg.slice(2);
14
+ const next = argv[i + 1];
15
+ if (next !== undefined && !next.startsWith("-")) {
16
+ flags[key] = next;
17
+ i += 1;
18
+ }
19
+ else {
20
+ flags[key] = true;
21
+ }
22
+ continue;
23
+ }
24
+ positional.push(arg);
25
+ }
26
+ return { flags, positional };
27
+ };
28
+ //# sourceMappingURL=cli-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAO1C,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAc,EAAc,EAAE;IACtD,MAAM,KAAK,GAAqC,EAAE,CAAC;IACnD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;gBACX,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBAC5C,SAAS;YACX,CAAC;YACD,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChD,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;gBAClB,CAAC,IAAI,CAAC,CAAC;YACT,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YACpB,CAAC;YACD,SAAS;QACX,CAAC;QACD,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC/B,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Fixture HTTP server (PRD §50).
3
+ *
4
+ * One localhost server, deterministic responses, no external traffic. Each
5
+ * eval run starts it on an ephemeral port and shuts it down afterwards.
6
+ */
7
+ import { type Server } from "node:http";
8
+ export interface FixtureServer {
9
+ url: string;
10
+ port: number;
11
+ close(): Promise<void>;
12
+ requests: {
13
+ method: string;
14
+ url: string;
15
+ at: number;
16
+ }[];
17
+ }
18
+ export declare const startFixtureServer: () => Promise<FixtureServer>;
19
+ /** Starts the fixture site on a fixed port (0 = ephemeral). */
20
+ export declare const startFixtureServerOn: (port: number) => Promise<FixtureServer>;
21
+ export type { Server };
22
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/fixtures/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AAwBjG,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzD;AAID,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,aAAa,CAA4B,CAAC;AAE9F,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,aAAa,CAiH9E,CAAC;AAiBF,YAAY,EAAE,MAAM,EAAE,CAAC"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Fixture HTTP server (PRD §50).
3
+ *
4
+ * One localhost server, deterministic responses, no external traffic. Each
5
+ * eval run starts it on an ephemeral port and shuts it down afterwards.
6
+ */
7
+ import { createServer } from "node:http";
8
+ import { randomBytes } from "node:crypto";
9
+ import { autocompletePage, bigPage, billingPage, canvasPage, claimPage, claimSubmittedPage, dangerDeletedPage, dangerPage, decoyPage, dialogPage, fixtureFiles, homePage, injectionPage, portalLoginPage, portalPage, rerenderPage, searchPage, slowPage, variationFromQuery, } from "./site.js";
10
+ const COOKIE_NAME = "portal_session";
11
+ export const startFixtureServer = async () => startFixtureServerOn(0);
12
+ /** Starts the fixture site on a fixed port (0 = ephemeral). */
13
+ export const startFixtureServerOn = async (port) => {
14
+ const files = fixtureFiles();
15
+ const requests = [];
16
+ const sessionToken = randomBytes(8).toString("hex");
17
+ const server = createServer((req, res) => {
18
+ void handle(req, res);
19
+ });
20
+ const handle = async (req, res) => {
21
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
22
+ const path = url.pathname;
23
+ const variation = variationFromQuery(url.searchParams);
24
+ requests.push({ method: req.method ?? "GET", url: url.pathname + url.search, at: Date.now() });
25
+ const html = (body) => {
26
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
27
+ res.end(body);
28
+ };
29
+ const redirect = (to) => {
30
+ res.writeHead(303, { location: to });
31
+ res.end();
32
+ };
33
+ const cookies = parseCookies(req.headers.cookie ?? "");
34
+ const signedIn = cookies[COOKIE_NAME] === sessionToken;
35
+ // ── static pages
36
+ if (path === "/" || path === "/index.html")
37
+ return html(homePage(variation));
38
+ if (path === "/billing")
39
+ return html(billingPage(variation));
40
+ if (path === "/search")
41
+ return html(searchPage(url.searchParams.get("q") ?? "", variation));
42
+ if (path === "/autocomplete")
43
+ return html(autocompletePage(variation));
44
+ if (path === "/dialog")
45
+ return html(dialogPage(variation));
46
+ if (path === "/rerender")
47
+ return html(rerenderPage(variation));
48
+ if (path === "/decoys")
49
+ return html(decoyPage(variation));
50
+ if (path === "/injection")
51
+ return html(injectionPage(variation));
52
+ if (path === "/big")
53
+ return html(bigPage(variation));
54
+ if (path === "/slow")
55
+ return html(slowPage(variation));
56
+ if (path === "/canvas")
57
+ return html(canvasPage());
58
+ if (path === "/danger")
59
+ return html(dangerPage(variation));
60
+ if (path === "/danger/deleted")
61
+ return html(dangerDeletedPage(variation));
62
+ // ── reimbursement portal
63
+ if (path === "/portal/login" || path === "/portal") {
64
+ if (signedIn && path === "/portal")
65
+ return html(portalPage(variation));
66
+ return html(portalLoginPage(variation));
67
+ }
68
+ if (path === "/portal/demo-signin") {
69
+ res.writeHead(303, {
70
+ location: "/portal",
71
+ "set-cookie": `${COOKIE_NAME}=${sessionToken}; Path=/; HttpOnly`,
72
+ });
73
+ res.end();
74
+ return;
75
+ }
76
+ if (path === "/portal/logout") {
77
+ res.writeHead(303, { location: "/", "set-cookie": `${COOKIE_NAME}=; Path=/; Max-Age=0` });
78
+ res.end();
79
+ return;
80
+ }
81
+ if (path === "/portal/claim") {
82
+ if (!signedIn)
83
+ return redirect("/portal/login");
84
+ return html(claimPage(variation));
85
+ }
86
+ if (path === "/portal/claim/submit" && req.method === "POST") {
87
+ const body = await readBody(req);
88
+ if (!signedIn)
89
+ return redirect("/portal/login");
90
+ if (!body.includes("invoice-2026-08.pdf") && !body.includes('filename="')) {
91
+ res.writeHead(400, { "content-type": "text/plain" });
92
+ res.end("no file attached");
93
+ return;
94
+ }
95
+ return html(claimSubmittedPage(variation));
96
+ }
97
+ // ── files (downloads)
98
+ const fileMatch = /^\/files\/([^/]+)$/.exec(path);
99
+ if (fileMatch) {
100
+ const name = decodeURIComponent(fileMatch[1]);
101
+ const file = files[name];
102
+ if (!file) {
103
+ res.writeHead(404, { "content-type": "text/plain" });
104
+ res.end("not found");
105
+ return;
106
+ }
107
+ // Deterministic latency for the matrix suite.
108
+ const delay = Number(url.searchParams.get("delayMs") ?? 0);
109
+ if (delay > 0)
110
+ await new Promise((resolve) => setTimeout(resolve, delay));
111
+ res.writeHead(200, {
112
+ "content-type": file.contentType,
113
+ "content-length": String(file.body.length),
114
+ "content-disposition": `attachment; filename="${file.name}"`,
115
+ });
116
+ res.end(file.body);
117
+ return;
118
+ }
119
+ res.writeHead(404, { "content-type": "text/html; charset=utf-8" });
120
+ res.end(`<!doctype html><title>Not found</title><h1>404</h1>`);
121
+ };
122
+ await new Promise((resolve) => server.listen(port, "127.0.0.1", resolve));
123
+ const address = server.address();
124
+ const boundPort = address && typeof address === "object" ? address.port : port;
125
+ return {
126
+ url: `http://127.0.0.1:${boundPort}`,
127
+ port: boundPort,
128
+ requests,
129
+ close: () => new Promise((resolve) => {
130
+ server.close(() => resolve());
131
+ }),
132
+ };
133
+ };
134
+ const parseCookies = (header) => {
135
+ const out = {};
136
+ for (const part of header.split(";")) {
137
+ const [key, ...rest] = part.trim().split("=");
138
+ if (key)
139
+ out[key] = rest.join("=");
140
+ }
141
+ return out;
142
+ };
143
+ const readBody = async (req) => {
144
+ const chunks = [];
145
+ for await (const chunk of req)
146
+ chunks.push(chunk);
147
+ return Buffer.concat(chunks).toString("latin1");
148
+ };
149
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/fixtures/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Fixture site for evals (PRD §50).
3
+ *
4
+ * A small, fully local, deterministic multi-page app that exercises the
5
+ * mechanics the runtime promises: search, autocomplete, downloads, uploads,
6
+ * multi-page flows, dialogs, forms, decoys, re-renders and prompt-injection
7
+ * traps. Nothing here touches the network or a real service.
8
+ */
9
+ export interface FixtureVariation {
10
+ /** Deterministic seed for DOM variation (matrix suite). */
11
+ seed?: number;
12
+ /** Element order shuffling. */
13
+ shuffle?: boolean;
14
+ /** Delay before interactive content is hydrated (ms). */
15
+ hydrateMs?: number;
16
+ /** Delay before autocomplete options appear (ms). */
17
+ autocompleteMs?: number;
18
+ /** Add decoy controls with similar labels. */
19
+ decoys?: number;
20
+ /** Insert a covering overlay that must be dismissed. */
21
+ overlay?: boolean;
22
+ /** Rename ids to random-looking values. */
23
+ randomIds?: boolean;
24
+ }
25
+ export declare const homePage: (variation?: FixtureVariation) => string;
26
+ export declare const dangerPage: (variation?: FixtureVariation) => string;
27
+ export declare const dangerDeletedPage: (variation?: FixtureVariation) => string;
28
+ export declare const billingPage: (variation?: FixtureVariation) => string;
29
+ export declare const portalLoginPage: (variation?: FixtureVariation) => string;
30
+ export declare const portalPage: (variation?: FixtureVariation) => string;
31
+ export declare const claimPage: (variation?: FixtureVariation) => string;
32
+ export declare const claimSubmittedPage: (variation?: FixtureVariation) => string;
33
+ export declare const searchPage: (query: string, variation?: FixtureVariation) => string;
34
+ export declare const autocompletePage: (variation?: FixtureVariation) => string;
35
+ export declare const dialogPage: (variation?: FixtureVariation) => string;
36
+ export declare const rerenderPage: (variation?: FixtureVariation) => string;
37
+ export declare const decoyPage: (variation?: FixtureVariation) => string;
38
+ export declare const injectionPage: (variation?: FixtureVariation) => string;
39
+ export declare const bigPage: (variation?: FixtureVariation) => string;
40
+ export declare const slowPage: (variation?: FixtureVariation) => string;
41
+ export declare const canvasPage: () => string;
42
+ export declare const pdfBytes: (title: string, body?: string) => Buffer;
43
+ export declare const csvBytes: (rows: string[][]) => Buffer;
44
+ export interface FixtureFile {
45
+ name: string;
46
+ contentType: string;
47
+ body: Buffer;
48
+ }
49
+ export declare const fixtureFiles: () => Record<string, FixtureFile>;
50
+ export declare const variationFromQuery: (params: URLSearchParams) => FixtureVariation;
51
+ //# sourceMappingURL=site.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../src/fixtures/site.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAwED,eAAO,MAAM,QAAQ,GAAI,YAAW,gBAAqB,KAAG,MAmBzD,CAAC;AAEJ,eAAO,MAAM,UAAU,GAAI,YAAW,gBAAqB,KAAG,MAa3D,CAAC;AAEJ,eAAO,MAAM,iBAAiB,GAAI,YAAW,gBAAqB,KAAG,MACU,CAAC;AAEhF,eAAO,MAAM,WAAW,GAAI,YAAW,gBAAqB,KAAG,MAgC9D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,YAAW,gBAAqB,KAAG,MAYhE,CAAC;AAEJ,eAAO,MAAM,UAAU,GAAI,YAAW,gBAAqB,KAAG,MAY3D,CAAC;AAEJ,eAAO,MAAM,SAAS,GAAI,YAAW,gBAAqB,KAAG,MAoB1D,CAAC;AAEJ,eAAO,MAAM,kBAAkB,GAAI,YAAW,gBAAqB,KAAG,MAOnE,CAAC;AAEJ,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,EAAE,YAAW,gBAAqB,KAAG,MAS1E,CAAC;AAEJ,eAAO,MAAM,gBAAgB,GAAI,YAAW,gBAAqB,KAAG,MAsBjE,CAAC;AAEJ,eAAO,MAAM,UAAU,GAAI,YAAW,gBAAqB,KAAG,MAU3D,CAAC;AAEJ,eAAO,MAAM,YAAY,GAAI,YAAW,gBAAqB,KAAG,MAiB7D,CAAC;AAEJ,eAAO,MAAM,SAAS,GAAI,YAAW,gBAAqB,KAAG,MAgB5D,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,YAAW,gBAAqB,KAAG,MAe9D,CAAC;AAEJ,eAAO,MAAM,OAAO,GAAI,YAAW,gBAAqB,KAAG,MAiB1D,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,YAAW,gBAAqB,KAAG,MAYzD,CAAC;AAEJ,eAAO,MAAM,UAAU,QAAO,MAS3B,CAAC;AAIJ,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,EAAE,aAAqC,KAAG,MAqB/E,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,EAAE,EAAE,KAAG,MACqE,CAAC;AAElH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,YAAY,QAAO,MAAM,CAAC,MAAM,EAAE,WAAW,CAQxD,CAAC;AAEH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,eAAe,KAAG,gBAU5D,CAAC"}