@canaryai/cli 0.1.14 → 0.2.1
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/{chunk-2T64Z2NI.js → chunk-7R4YFGP6.js} +53 -2
- package/dist/chunk-7R4YFGP6.js.map +1 -0
- package/dist/chunk-DXJNFJ3A.js +64 -0
- package/dist/chunk-DXJNFJ3A.js.map +1 -0
- package/dist/{chunk-V7U52ISX.js → chunk-HOYYXZPV.js} +136 -131
- package/dist/chunk-HOYYXZPV.js.map +1 -0
- package/dist/{chunk-ROTCL5WO.js → chunk-TO66FC4R.js} +688 -479
- package/dist/chunk-TO66FC4R.js.map +1 -0
- package/dist/chunk-UEOXNF5X.js +371 -0
- package/dist/chunk-UEOXNF5X.js.map +1 -0
- package/dist/{feature-flag-ESPSOSKG.js → feature-flag-ZDLDYRSF.js} +15 -92
- package/dist/feature-flag-ZDLDYRSF.js.map +1 -0
- package/dist/index.js +17 -65
- package/dist/index.js.map +1 -1
- package/dist/{knobs-HKONHY55.js → knobs-3MKMOXIV.js} +19 -104
- package/dist/knobs-3MKMOXIV.js.map +1 -0
- package/dist/{local-browser-SYPTG6IQ.js → local-browser-GG5GUXDS.js} +3 -3
- package/dist/{mcp-TMD2R5Z6.js → mcp-AD67OLQM.js} +4 -4
- package/dist/{psql-6IFVXM3A.js → psql-IVAPNYZV.js} +2 -2
- package/dist/{redis-HZC32IEO.js → redis-LWY7L6AS.js} +2 -2
- package/dist/{release-WOD3DAX4.js → release-KQFCTAXA.js} +5 -35
- package/dist/release-KQFCTAXA.js.map +1 -0
- package/dist/runner/preload.js +7 -323
- package/dist/runner/preload.js.map +1 -1
- package/dist/test.js +5 -340
- package/dist/test.js.map +1 -1
- package/package.json +2 -1
- package/dist/chunk-2T64Z2NI.js.map +0 -1
- package/dist/chunk-L26U3BST.js +0 -770
- package/dist/chunk-L26U3BST.js.map +0 -1
- package/dist/chunk-ROTCL5WO.js.map +0 -1
- package/dist/chunk-V7U52ISX.js.map +0 -1
- package/dist/feature-flag-ESPSOSKG.js.map +0 -1
- package/dist/knobs-HKONHY55.js.map +0 -1
- package/dist/release-WOD3DAX4.js.map +0 -1
- /package/dist/{local-browser-SYPTG6IQ.js.map → local-browser-GG5GUXDS.js.map} +0 -0
- /package/dist/{mcp-TMD2R5Z6.js.map → mcp-AD67OLQM.js.map} +0 -0
- /package/dist/{psql-6IFVXM3A.js.map → psql-IVAPNYZV.js.map} +0 -0
- /package/dist/{redis-HZC32IEO.js.map → redis-LWY7L6AS.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/knobs.ts"],"sourcesContent":["/**\n * CLI Knobs Management\n *\n * Allows superadmins to manage knobs (global config) via the CLI.\n */\n\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport {\n type LifecycleStage,\n toLifecycleLabel,\n parseLifecycleStage,\n apiRequest,\n fetchList,\n} from \"./cli-helpers.js\";\n\ntype SerializedKnob = {\n key: string;\n description: string | null;\n valueType: \"boolean\" | \"string\" | \"number\" | \"json\";\n value: unknown;\n lifecycleStage: LifecycleStage;\n finalValue: unknown;\n updatedAt: string | null;\n updatedBy: string | null;\n createdAt: string | null;\n};\n\ntype KnobApiResponse = {\n ok: boolean;\n error?: string;\n knob?: SerializedKnob;\n};\n\nfunction formatValue(valueType: SerializedKnob[\"valueType\"], value: unknown): string {\n if (valueType === \"json\") return JSON.stringify(value);\n return String(value);\n}\n\nfunction formatFinalValue(knob: SerializedKnob): string {\n if (knob.lifecycleStage === \"active\") return \"(none)\";\n return formatValue(knob.valueType, knob.finalValue);\n}\n\nfunction parseTypedValue(raw: string, valueType: SerializedKnob[\"valueType\"]): unknown {\n switch (valueType) {\n case \"boolean\":\n if (raw !== \"true\" && raw !== \"false\") {\n console.error('Error: Boolean value must be \"true\" or \"false\".');\n process.exit(1);\n }\n return raw === \"true\";\n case \"string\":\n return raw;\n case \"number\": {\n const num = parseFloat(raw);\n if (Number.isNaN(num)) {\n console.error(`Error: Invalid number: ${raw}`);\n process.exit(1);\n }\n return num;\n }\n case \"json\":\n try {\n return JSON.parse(raw);\n } catch {\n console.error(`Error: Invalid JSON: ${raw}`);\n process.exit(1);\n }\n }\n}\n\nfunction fetchKnobs(apiUrl: string, token: string): Promise<SerializedKnob[]> {\n return fetchList<SerializedKnob>(apiUrl, token, \"/superadmin/knobs\", \"knobs\");\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const knobs = await fetchKnobs(apiUrl, token);\n\n if (jsonOutput) {\n console.log(JSON.stringify(knobs, null, 2));\n return;\n }\n\n if (knobs.length === 0) {\n console.log(\"No knobs found.\");\n return;\n }\n\n for (const knob of knobs) {\n const desc = knob.description ? ` ${knob.description}` : \"\";\n const lifecycle = `lifecycle=${toLifecycleLabel(knob.lifecycleStage)}`;\n const finalValue =\n knob.lifecycleStage === \"active\" ? \"\" : ` final=${formatFinalValue(knob)}`;\n console.log(\n ` ${knob.key} [${knob.valueType}] = ${formatValue(knob.valueType, knob.value)} (${lifecycle}${finalValue})${desc}`\n );\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs get <key>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n const knobs = await fetchKnobs(apiUrl, token);\n const knob = knobs.find((k) => k.key === key);\n\n if (!knob) {\n console.error(`Knob not found: ${key}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify(knob, null, 2));\n return;\n }\n\n console.log(` Key: ${knob.key}`);\n console.log(` Type: ${knob.valueType}`);\n console.log(` Value: ${formatValue(knob.valueType, knob.value)}`);\n console.log(` Lifecycle: ${toLifecycleLabel(knob.lifecycleStage)}`);\n console.log(` Final value: ${formatFinalValue(knob)}`);\n console.log(` Description: ${knob.description ?? \"(none)\"}`);\n console.log(` Updated: ${knob.updatedAt ?? \"(never)\"}`);\n}\n\nasync function handleSet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs set <key> <value> --type <boolean|string|number|json> [--description <text>]\");\n process.exit(1);\n }\n\n const rawValue = argv[1];\n if (rawValue === undefined || rawValue.startsWith(\"--\")) {\n console.error(\"Error: Missing knob value.\");\n console.error(\"Usage: canary knobs set <key> <value> --type <boolean|string|number|json> [--description <text>]\");\n process.exit(1);\n }\n\n const valueType = getArgValue(argv, \"--type\") as SerializedKnob[\"valueType\"] | undefined;\n if (!valueType || ![\"boolean\", \"string\", \"number\", \"json\"].includes(valueType)) {\n console.error(\"Error: --type is required and must be one of: boolean, string, number, json\");\n process.exit(1);\n }\n\n const value = parseTypedValue(rawValue, valueType);\n const description = getArgValue(argv, \"--description\") ?? undefined;\n\n const result = await apiRequest<KnobApiResponse>(apiUrl, token, \"POST\", \"/superadmin/knobs\", {\n key,\n valueType,\n value,\n description,\n });\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Set knob: ${key} = ${formatValue(valueType, result.knob?.value)}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs delete <key>\");\n process.exit(1);\n }\n\n const result = await apiRequest<KnobApiResponse>(\n apiUrl, token, \"DELETE\", `/superadmin/knobs/${encodeURIComponent(key)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Deleted knob: ${key}`);\n}\n\nasync function handleToggle(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs toggle <key>\");\n process.exit(1);\n }\n\n const result = await apiRequest<KnobApiResponse>(\n apiUrl, token, \"POST\", `/superadmin/knobs/${encodeURIComponent(key)}/toggle`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n const knob = result.knob;\n console.log(`Toggled knob: ${key} -> ${formatValue(knob?.valueType ?? \"boolean\", knob?.value)}`);\n}\n\nasync function handleLifecycle(argv: string[], apiUrl: string, token: string): Promise<void> {\n const key = argv[0];\n if (!key || key.startsWith(\"--\")) {\n console.error(\"Error: Missing knob key.\");\n console.error(\"Usage: canary knobs lifecycle <key> --stage <active|deprecated|ready_for_cleanup> [--final-value <value>]\");\n process.exit(1);\n }\n\n const stage = parseLifecycleStage(argv);\n const rawFinalValue = getArgValue(argv, \"--final-value\");\n const clearFinalValue = hasFlag(argv, \"--clear-final-value\");\n\n if (rawFinalValue !== undefined && clearFinalValue) {\n console.error(\"Error: use either --final-value or --clear-final-value, not both.\");\n process.exit(1);\n }\n\n const knobs = await fetchKnobs(apiUrl, token);\n const knob = knobs.find((k) => k.key === key);\n\n if (!knob) {\n console.error(`Knob not found: ${key}`);\n process.exit(1);\n }\n\n let finalValue: unknown = undefined;\n\n if (stage === \"active\") {\n if (rawFinalValue !== undefined) {\n console.error(\"Error: active stage does not accept --final-value. Use --stage deprecated|ready_for_cleanup.\");\n process.exit(1);\n }\n finalValue = clearFinalValue ? undefined : undefined;\n } else {\n if (clearFinalValue) {\n console.error(\"Error: --clear-final-value can only be used with --stage active.\");\n process.exit(1);\n }\n if (rawFinalValue === undefined) {\n console.error(\"Error: --final-value is required when stage is deprecated or ready_for_cleanup.\");\n process.exit(1);\n }\n finalValue = parseTypedValue(rawFinalValue, knob.valueType);\n }\n\n const result = await apiRequest<KnobApiResponse>(\n apiUrl, token, \"POST\", `/superadmin/knobs/${encodeURIComponent(key)}/lifecycle`,\n { stage, finalValue }\n );\n\n if (!result.ok || !result.knob) {\n console.error(`Error: ${result.error ?? \"Failed to update lifecycle\"}`);\n process.exit(1);\n }\n\n console.log(\n `Updated lifecycle for ${key}: stage=${toLifecycleLabel(result.knob.lifecycleStage)}, final=${formatFinalValue(result.knob)}`\n );\n}\n\nfunction printKnobsHelp(): void {\n console.log(\n [\n \"Usage: canary knobs <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list List all knobs\",\n \" get <key> Get a knob value\",\n \" set <key> <value> --type <type> [--description <text>]\",\n \" Set a knob value\",\n \" delete <key> Delete a knob\",\n \" toggle <key> Toggle a boolean knob\",\n \" lifecycle <key> --stage <stage> [--final-value <value>]\",\n \" Mark knob lifecycle + final value\",\n \"\",\n \"Types: boolean, string, number, json\",\n \"Stages: active, deprecated, ready_for_cleanup\",\n \"\",\n \"Options:\",\n \" --final-value <value> Final value for deprecated/ready_for_cleanup\",\n \" --clear-final-value Clear final value (only valid with --stage active)\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --json Output as JSON (list/get only)\",\n \" --api-url <url> API URL override (takes precedence over --env)\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runKnobs(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printKnobsHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n case \"set\":\n await handleSet(rest, apiUrl, token);\n break;\n case \"delete\":\n await handleDelete(rest, apiUrl, token);\n break;\n case \"toggle\":\n await handleToggle(rest, apiUrl, token);\n break;\n case \"lifecycle\":\n await handleLifecycle(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printKnobsHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LocalBrowserHost
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UEOXNF5X.js";
|
|
4
4
|
import {
|
|
5
5
|
readStoredToken
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7R4YFGP6.js";
|
|
7
7
|
import "./chunk-DGUM43GV.js";
|
|
8
8
|
|
|
9
9
|
// src/local-browser/index.ts
|
|
@@ -137,4 +137,4 @@ async function runLocalBrowser(args) {
|
|
|
137
137
|
export {
|
|
138
138
|
runLocalBrowser
|
|
139
139
|
};
|
|
140
|
-
//# sourceMappingURL=local-browser-
|
|
140
|
+
//# sourceMappingURL=local-browser-GG5GUXDS.js.map
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
connectTunnel,
|
|
3
3
|
createLocalRun,
|
|
4
4
|
createTunnel
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HOYYXZPV.js";
|
|
6
6
|
import {
|
|
7
7
|
LocalBrowserHost
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-UEOXNF5X.js";
|
|
9
9
|
import {
|
|
10
10
|
readStoredToken
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-7R4YFGP6.js";
|
|
12
12
|
import "./chunk-DGUM43GV.js";
|
|
13
13
|
|
|
14
14
|
// src/mcp.ts
|
|
@@ -381,4 +381,4 @@ function formatReport(input) {
|
|
|
381
381
|
export {
|
|
382
382
|
runMcp
|
|
383
383
|
};
|
|
384
|
-
//# sourceMappingURL=mcp-
|
|
384
|
+
//# sourceMappingURL=mcp-AD67OLQM.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readStoredApiUrl,
|
|
3
3
|
readStoredToken
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7R4YFGP6.js";
|
|
5
5
|
import "./chunk-DGUM43GV.js";
|
|
6
6
|
|
|
7
7
|
// src/psql.ts
|
|
@@ -121,4 +121,4 @@ Time: ${json.durationMs}ms`);
|
|
|
121
121
|
export {
|
|
122
122
|
runPsql
|
|
123
123
|
};
|
|
124
|
-
//# sourceMappingURL=psql-
|
|
124
|
+
//# sourceMappingURL=psql-IVAPNYZV.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readStoredApiUrl,
|
|
3
3
|
readStoredToken
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7R4YFGP6.js";
|
|
5
5
|
import "./chunk-DGUM43GV.js";
|
|
6
6
|
|
|
7
7
|
// src/redis.ts
|
|
@@ -127,4 +127,4 @@ Time: ${json_response.durationMs}ms`);
|
|
|
127
127
|
export {
|
|
128
128
|
runRedis
|
|
129
129
|
};
|
|
130
|
-
//# sourceMappingURL=redis-
|
|
130
|
+
//# sourceMappingURL=redis-LWY7L6AS.js.map
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
getArgValue,
|
|
3
|
+
hasFlag,
|
|
4
|
+
resolveConfig
|
|
5
|
+
} from "./chunk-7R4YFGP6.js";
|
|
5
6
|
import "./chunk-DGUM43GV.js";
|
|
6
7
|
|
|
7
8
|
// src/release.ts
|
|
8
9
|
import process from "process";
|
|
9
|
-
var ENV_URLS = {
|
|
10
|
-
prod: "https://api.trycanary.ai",
|
|
11
|
-
production: "https://api.trycanary.ai",
|
|
12
|
-
dev: "https://api.dev.trycanary.ai",
|
|
13
|
-
local: "http://localhost:3000"
|
|
14
|
-
};
|
|
15
10
|
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
16
11
|
"completed",
|
|
17
12
|
"completed_with_errors",
|
|
@@ -19,31 +14,6 @@ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
|
19
14
|
"canceled",
|
|
20
15
|
"timeout"
|
|
21
16
|
]);
|
|
22
|
-
function getArgValue(argv, key) {
|
|
23
|
-
const index = argv.indexOf(key);
|
|
24
|
-
if (index === -1 || index >= argv.length - 1) return void 0;
|
|
25
|
-
return argv[index + 1];
|
|
26
|
-
}
|
|
27
|
-
function hasFlag(argv, ...flags) {
|
|
28
|
-
return flags.some((flag) => argv.includes(flag));
|
|
29
|
-
}
|
|
30
|
-
async function resolveConfig(argv) {
|
|
31
|
-
const storedApiUrl = await readStoredApiUrl();
|
|
32
|
-
const env = getArgValue(argv, "--env");
|
|
33
|
-
if (env && !ENV_URLS[env]) {
|
|
34
|
-
console.error(`Unknown environment: ${env}`);
|
|
35
|
-
console.error("Valid environments: prod, dev, local");
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
const apiUrl = getArgValue(argv, "--api-url") ?? (env ? ENV_URLS[env] : void 0) ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
|
|
39
|
-
const token = getArgValue(argv, "--token") ?? process.env.CANARY_API_TOKEN ?? await readStoredToken();
|
|
40
|
-
if (!token) {
|
|
41
|
-
console.error("Error: No API token found.");
|
|
42
|
-
console.error("Set CANARY_API_TOKEN or run: canary login");
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
return { apiUrl, token };
|
|
46
|
-
}
|
|
47
17
|
async function handleTrigger(argv, apiUrl, token) {
|
|
48
18
|
const propertyId = getArgValue(argv, "--property-id");
|
|
49
19
|
if (!propertyId) {
|
|
@@ -246,4 +216,4 @@ async function runRelease(argv) {
|
|
|
246
216
|
export {
|
|
247
217
|
runRelease
|
|
248
218
|
};
|
|
249
|
-
//# sourceMappingURL=release-
|
|
219
|
+
//# sourceMappingURL=release-KQFCTAXA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/release.ts"],"sourcesContent":["/**\n * CLI Release QA Management\n *\n * Trigger, poll, and check status of Release QA runs from CI/CD pipelines.\n * Used by the scheduled-release GitHub Actions workflow to gate deployments.\n */\n\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\n\ntype TriggerResponse = {\n ok: boolean;\n releaseRunId?: string;\n jobId?: string;\n error?: string;\n};\n\ntype RunStatusResponse = {\n ok: boolean;\n run?: {\n id: string;\n status: string;\n triggerSource: string;\n cutoffReason: string | null;\n fromSha: string | null;\n toSha: string | null;\n commitsAnalyzed: number;\n testersSpawned: number;\n testersCompleted: number;\n testersFailed: number;\n issuesFound: number;\n regressionTestsTotal: number;\n regressionTestsPassed: number;\n regressionTestsFailed: number;\n startedAt: string | null;\n finishedAt: string | null;\n createdAt: string;\n };\n error?: string;\n};\n\n/** Terminal statuses that stop polling */\nconst TERMINAL_STATUSES = new Set([\n \"completed\",\n \"completed_with_errors\",\n \"failed\",\n \"canceled\",\n \"timeout\",\n]);\n\nasync function handleTrigger(argv: string[], apiUrl: string, token: string): Promise<void> {\n const propertyId = getArgValue(argv, \"--property-id\");\n if (!propertyId) {\n console.error(\"Error: Missing --property-id <uuid>.\");\n console.error(\"Usage: canary release trigger --property-id <uuid> [--credential-ids <uuid,...>]\");\n process.exit(1);\n }\n\n const credentialIdsRaw = getArgValue(argv, \"--credential-ids\");\n const body: Record<string, unknown> = { propertyId };\n if (credentialIdsRaw) {\n body.credentialIds = credentialIdsRaw.split(\",\").map((id) => id.trim());\n }\n\n const res = await fetch(`${apiUrl}/api/v1/release-qa/trigger`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Check your API token.\");\n process.exit(1);\n }\n\n const json = (await res.json()) as TriggerResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n console.log(`Release QA run triggered`);\n console.log(` Run ID: ${json.releaseRunId}`);\n console.log(` Job ID: ${json.jobId}`);\n}\n\nasync function fetchRunStatus(\n apiUrl: string,\n token: string,\n runId: string\n): Promise<RunStatusResponse> {\n const res = await fetch(`${apiUrl}/api/v1/release-qa/runs/${encodeURIComponent(runId)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Check your API token.\");\n process.exit(1);\n }\n\n return (await res.json()) as RunStatusResponse;\n}\n\nasync function handleStatus(argv: string[], apiUrl: string, token: string): Promise<void> {\n const runId = argv[0];\n if (!runId || runId.startsWith(\"--\")) {\n console.error(\"Error: Missing run ID.\");\n console.error(\"Usage: canary release status <run-id>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n const json = await fetchRunStatus(apiUrl, token, runId);\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n const run = json.run!;\n\n if (jsonOutput) {\n console.log(JSON.stringify(run, null, 2));\n return;\n }\n\n console.log(`Release QA Run: ${run.id}`);\n console.log(` Status: ${run.status}`);\n console.log(` Trigger: ${run.triggerSource}`);\n console.log(` Commits analyzed: ${run.commitsAnalyzed}`);\n console.log(` Testers: ${run.testersCompleted}/${run.testersSpawned} completed, ${run.testersFailed} failed`);\n console.log(` Issues found: ${run.issuesFound}`);\n console.log(` Regression tests: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed, ${run.regressionTestsFailed} failed`);\n if (run.startedAt) console.log(` Started: ${run.startedAt}`);\n if (run.finishedAt) console.log(` Finished: ${run.finishedAt}`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function handleRun(argv: string[], apiUrl: string, token: string): Promise<void> {\n const propertyId = getArgValue(argv, \"--property-id\");\n if (!propertyId) {\n console.error(\"Error: Missing --property-id <uuid>.\");\n console.error(\n \"Usage: canary release run --property-id <uuid> [--timeout 3600] [--poll-interval 30]\"\n );\n process.exit(1);\n }\n\n const timeoutSec = parseInt(getArgValue(argv, \"--timeout\") ?? \"3600\", 10);\n const pollIntervalSec = parseInt(getArgValue(argv, \"--poll-interval\") ?? \"30\", 10);\n\n const credentialIdsRaw = getArgValue(argv, \"--credential-ids\");\n const body: Record<string, unknown> = { propertyId };\n if (credentialIdsRaw) {\n body.credentialIds = credentialIdsRaw.split(\",\").map((id) => id.trim());\n }\n\n // Trigger\n console.log(\"Triggering Release QA run...\");\n const triggerRes = await fetch(`${apiUrl}/api/v1/release-qa/trigger`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (triggerRes.status === 401) {\n console.error(\"Error: Unauthorized. Check your API token.\");\n process.exit(1);\n }\n\n const triggerJson = (await triggerRes.json()) as TriggerResponse;\n\n if (!triggerJson.ok) {\n console.error(`Error triggering run: ${triggerJson.error}`);\n process.exit(1);\n }\n\n const runId = triggerJson.releaseRunId!;\n console.log(`Run started: ${runId}`);\n\n // Poll\n const deadline = Date.now() + timeoutSec * 1000;\n let lastStatus = \"\";\n\n while (Date.now() < deadline) {\n await sleep(pollIntervalSec * 1000);\n\n let statusJson: RunStatusResponse;\n try {\n statusJson = await fetchRunStatus(apiUrl, token, runId);\n } catch (err) {\n console.error(`Warning: Failed to fetch status, retrying... (${err})`);\n continue;\n }\n\n if (!statusJson.ok) {\n console.error(`Error: ${statusJson.error}`);\n process.exit(1);\n }\n\n const run = statusJson.run!;\n const statusLine = `[${run.status}] testers: ${run.testersCompleted}/${run.testersSpawned}, regressions: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed`;\n\n if (run.status !== lastStatus) {\n console.log(statusLine);\n lastStatus = run.status;\n } else {\n // Print progress on same status change in metrics\n console.log(statusLine);\n }\n\n if (TERMINAL_STATUSES.has(run.status)) {\n console.log(\"\");\n console.log(`Run finished: ${run.status}`);\n console.log(` Testers: ${run.testersCompleted}/${run.testersSpawned} completed, ${run.testersFailed} failed`);\n console.log(` Regressions: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed, ${run.regressionTestsFailed} failed`);\n console.log(` Issues found: ${run.issuesFound}`);\n\n if (run.status === \"completed\") {\n console.log(\"\\nRelease QA PASSED\");\n process.exit(0);\n } else {\n console.error(`\\nRelease QA FAILED (${run.status})`);\n process.exit(1);\n }\n }\n }\n\n console.error(`\\nTimeout: Release QA did not complete within ${timeoutSec}s`);\n process.exit(1);\n}\n\nfunction printReleaseHelp(): void {\n console.log(\n [\n \"Usage: canary release <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" trigger --property-id <uuid> [--credential-ids <uuid,...>]\",\n \" Trigger a Release QA run\",\n \" status <run-id> [--json] Check run status\",\n \" run --property-id <uuid> [options] Trigger and poll until complete\",\n \"\",\n \"Run options:\",\n \" --timeout <seconds> Max wait time (default: 3600)\",\n \" --poll-interval <secs> Poll frequency (default: 30)\",\n \" --credential-ids <ids> Comma-separated credential UUIDs\",\n \"\",\n \"Options:\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --api-url <url> API URL override\",\n \" --token <key> API token override (or set CANARY_API_TOKEN)\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runRelease(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printReleaseHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"trigger\":\n await handleTrigger(rest, apiUrl, token);\n break;\n case \"status\":\n await handleStatus(rest, apiUrl, token);\n break;\n case \"run\":\n await handleRun(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printReleaseHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/runner/preload.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
alreadyPatched,
|
|
3
|
-
classifyFailure,
|
|
4
|
-
executeHealActions,
|
|
5
3
|
getEventLog,
|
|
6
4
|
loadCanaryConfig,
|
|
7
5
|
markPatched,
|
|
8
6
|
recordHealingEvent,
|
|
9
|
-
setEventLogPath
|
|
10
|
-
|
|
7
|
+
setEventLogPath,
|
|
8
|
+
wrapExpect,
|
|
9
|
+
wrapPage
|
|
10
|
+
} from "../chunk-TO66FC4R.js";
|
|
11
11
|
import {
|
|
12
12
|
__require
|
|
13
13
|
} from "../chunk-DGUM43GV.js";
|
|
@@ -17,57 +17,6 @@ import Module from "module";
|
|
|
17
17
|
import { createRequire } from "module";
|
|
18
18
|
var DEBUG = false;
|
|
19
19
|
var requireFn = typeof __require !== "undefined" ? __require : createRequire(import.meta.url);
|
|
20
|
-
var LOCATOR_ACTIONS = /* @__PURE__ */ new Set([
|
|
21
|
-
"click",
|
|
22
|
-
"dblclick",
|
|
23
|
-
"fill",
|
|
24
|
-
"check",
|
|
25
|
-
"uncheck",
|
|
26
|
-
"hover",
|
|
27
|
-
"press",
|
|
28
|
-
"type",
|
|
29
|
-
"selectOption",
|
|
30
|
-
"tap"
|
|
31
|
-
]);
|
|
32
|
-
var PAGE_ACTIONS = /* @__PURE__ */ new Set([
|
|
33
|
-
"goto",
|
|
34
|
-
"click",
|
|
35
|
-
"dblclick",
|
|
36
|
-
"fill",
|
|
37
|
-
"check",
|
|
38
|
-
"uncheck",
|
|
39
|
-
"hover",
|
|
40
|
-
"press",
|
|
41
|
-
"type",
|
|
42
|
-
"selectOption",
|
|
43
|
-
"tap",
|
|
44
|
-
"waitForSelector"
|
|
45
|
-
]);
|
|
46
|
-
var LOCATOR_FACTORIES = /* @__PURE__ */ new Set([
|
|
47
|
-
"locator",
|
|
48
|
-
"getByRole",
|
|
49
|
-
"getByText",
|
|
50
|
-
"getByLabel",
|
|
51
|
-
"getByPlaceholder",
|
|
52
|
-
"getByAltText",
|
|
53
|
-
"getByTitle",
|
|
54
|
-
"getByTestId",
|
|
55
|
-
"frameLocator"
|
|
56
|
-
]);
|
|
57
|
-
var LOCATOR_CHAIN_METHODS = /* @__PURE__ */ new Set([
|
|
58
|
-
"locator",
|
|
59
|
-
"first",
|
|
60
|
-
"last",
|
|
61
|
-
"nth",
|
|
62
|
-
"filter",
|
|
63
|
-
"getByRole",
|
|
64
|
-
"getByText",
|
|
65
|
-
"getByLabel",
|
|
66
|
-
"getByPlaceholder",
|
|
67
|
-
"getByAltText",
|
|
68
|
-
"getByTitle",
|
|
69
|
-
"getByTestId"
|
|
70
|
-
]);
|
|
71
20
|
function installInstrumentation() {
|
|
72
21
|
if (alreadyPatched()) return;
|
|
73
22
|
const config = loadCanaryConfig();
|
|
@@ -117,6 +66,7 @@ function hookPlaywrightModuleLoad() {
|
|
|
117
66
|
}
|
|
118
67
|
function createPatchedPlaywright(real) {
|
|
119
68
|
if (real.__canaryPatched) return real;
|
|
69
|
+
const options = { debug: DEBUG };
|
|
120
70
|
const extended = real.test.extend({
|
|
121
71
|
page: async ({ page }, use) => {
|
|
122
72
|
recordHealingEvent({
|
|
@@ -124,14 +74,14 @@ function createPatchedPlaywright(real) {
|
|
|
124
74
|
action: "fixture_initialized",
|
|
125
75
|
healed: false
|
|
126
76
|
});
|
|
127
|
-
const wrappedPage = wrapPage(page);
|
|
77
|
+
const wrappedPage = wrapPage(page, options);
|
|
128
78
|
await use(wrappedPage);
|
|
129
79
|
}
|
|
130
80
|
});
|
|
131
81
|
const patched = {
|
|
132
82
|
...real,
|
|
133
83
|
test: extended,
|
|
134
|
-
expect: wrapExpect(real.expect)
|
|
84
|
+
expect: wrapExpect(real.expect, options)
|
|
135
85
|
};
|
|
136
86
|
patched.__canaryPatched = true;
|
|
137
87
|
if (DEBUG) {
|
|
@@ -152,272 +102,6 @@ function ensurePlaywrightVersion(expected) {
|
|
|
152
102
|
return true;
|
|
153
103
|
}
|
|
154
104
|
}
|
|
155
|
-
function wrapExpect(expectImpl) {
|
|
156
|
-
const wrapMatchers = (expectation, mode) => {
|
|
157
|
-
return new Proxy(expectation ?? {}, {
|
|
158
|
-
get(target, prop, receiver) {
|
|
159
|
-
const value = Reflect.get(target, prop, receiver);
|
|
160
|
-
if (typeof prop === "string" && typeof value === "function") {
|
|
161
|
-
return (...args) => runMatcherWithHealing({ matcher: value, matcherName: prop, args, expectTarget: target, mode });
|
|
162
|
-
}
|
|
163
|
-
return value;
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
};
|
|
167
|
-
const proxy = new Proxy(expectImpl, {
|
|
168
|
-
apply(target, thisArg, argArray) {
|
|
169
|
-
const expectation = target.apply(thisArg, argArray);
|
|
170
|
-
return wrapMatchers(expectation, "hard");
|
|
171
|
-
},
|
|
172
|
-
get(target, prop, receiver) {
|
|
173
|
-
const value = Reflect.get(target, prop, receiver);
|
|
174
|
-
if (prop === "soft" && typeof value === "function") {
|
|
175
|
-
return (...args) => wrapMatchers(value.apply(target, args), "soft");
|
|
176
|
-
}
|
|
177
|
-
return value;
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
return proxy;
|
|
181
|
-
}
|
|
182
|
-
function wrapPage(page) {
|
|
183
|
-
return new Proxy(page, {
|
|
184
|
-
get(target, prop, receiver) {
|
|
185
|
-
const value = Reflect.get(target, prop, receiver);
|
|
186
|
-
if (typeof prop === "string") {
|
|
187
|
-
if (LOCATOR_FACTORIES.has(prop) && typeof value === "function") {
|
|
188
|
-
return (...args) => {
|
|
189
|
-
const locator = value.apply(target, args);
|
|
190
|
-
return wrapLocator(locator);
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
if (PAGE_ACTIONS.has(prop) && typeof value === "function") {
|
|
194
|
-
return async (...args) => {
|
|
195
|
-
return attemptWithHealing({
|
|
196
|
-
kind: "page",
|
|
197
|
-
action: prop,
|
|
198
|
-
target: safeTargetString(target),
|
|
199
|
-
locator: void 0,
|
|
200
|
-
page: target,
|
|
201
|
-
invoke: () => value.apply(target, args)
|
|
202
|
-
});
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return value;
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
function wrapLocator(locator) {
|
|
211
|
-
if (!locator || typeof locator !== "object") return locator;
|
|
212
|
-
return new Proxy(locator, {
|
|
213
|
-
get(target, prop, receiver) {
|
|
214
|
-
const value = Reflect.get(target, prop, receiver);
|
|
215
|
-
if (typeof prop === "string" && LOCATOR_ACTIONS.has(prop) && typeof value === "function") {
|
|
216
|
-
return async (...args) => {
|
|
217
|
-
return attemptWithHealing({
|
|
218
|
-
kind: "locator",
|
|
219
|
-
action: prop,
|
|
220
|
-
target: safeTargetString(target),
|
|
221
|
-
locator: target,
|
|
222
|
-
page: void 0,
|
|
223
|
-
invoke: () => value.apply(target, args)
|
|
224
|
-
});
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
if (typeof prop === "string" && LOCATOR_CHAIN_METHODS.has(prop) && typeof value === "function") {
|
|
228
|
-
return (...args) => wrapLocator(value.apply(target, args));
|
|
229
|
-
}
|
|
230
|
-
return value;
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
async function attemptWithHealing(ctx) {
|
|
235
|
-
try {
|
|
236
|
-
return await Promise.resolve(ctx.invoke());
|
|
237
|
-
} catch (error) {
|
|
238
|
-
const failure = buildFailureContext(ctx.kind, ctx.action, ctx.target, error);
|
|
239
|
-
recordHealingEvent({ ...failure, healed: false });
|
|
240
|
-
const decision = classifyFailure(failure);
|
|
241
|
-
if (DEBUG) {
|
|
242
|
-
console.log(`[canary][debug] failure intercepted: kind=${ctx.kind} action=${ctx.action} reason=${decision.healable ? decision.reason ?? "healable" : decision.reason}`);
|
|
243
|
-
}
|
|
244
|
-
if (!decision.healable) {
|
|
245
|
-
throw error;
|
|
246
|
-
}
|
|
247
|
-
const testContext = getTestContext();
|
|
248
|
-
const outcome = await executeHealActions(decision, failure, {
|
|
249
|
-
kind: ctx.kind,
|
|
250
|
-
action: ctx.action,
|
|
251
|
-
target: ctx.target,
|
|
252
|
-
locator: isLocatorLike(ctx.locator) ? ctx.locator : void 0,
|
|
253
|
-
page: isPageLike(ctx.page) ? ctx.page : void 0,
|
|
254
|
-
testContext: {
|
|
255
|
-
testFile: testContext?.testFile,
|
|
256
|
-
testTitle: testContext?.testTitle,
|
|
257
|
-
testSource: loadTestSource(testContext?.testFile)
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
const summaryBase = {
|
|
261
|
-
...failure,
|
|
262
|
-
healed: outcome.healed,
|
|
263
|
-
strategy: "agentic",
|
|
264
|
-
reason: outcome.reason ?? decision.reason,
|
|
265
|
-
actions: actionsToEventItems(outcome.actionsRun),
|
|
266
|
-
durationMs: outcome.durationMs,
|
|
267
|
-
mode: outcome.mode,
|
|
268
|
-
decision: decision.reason,
|
|
269
|
-
modelId: outcome.modelId,
|
|
270
|
-
summary: outcome.summary,
|
|
271
|
-
testFile: testContext?.testFile,
|
|
272
|
-
testTitle: testContext?.testTitle
|
|
273
|
-
};
|
|
274
|
-
if (outcome.healed && !outcome.shouldRetryOriginal) {
|
|
275
|
-
recordHealingEvent({ ...summaryBase, healed: true });
|
|
276
|
-
return void 0;
|
|
277
|
-
}
|
|
278
|
-
if (outcome.shouldRetryOriginal) {
|
|
279
|
-
try {
|
|
280
|
-
const retried = await Promise.resolve(ctx.invoke());
|
|
281
|
-
if (DEBUG) {
|
|
282
|
-
console.log(`[canary][debug] retry_original succeeded for action=${ctx.action}`);
|
|
283
|
-
}
|
|
284
|
-
recordHealingEvent({ ...summaryBase, healed: true });
|
|
285
|
-
return retried;
|
|
286
|
-
} catch (retryError) {
|
|
287
|
-
const retryInfo = errorInfo(retryError);
|
|
288
|
-
recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });
|
|
289
|
-
if (DEBUG) {
|
|
290
|
-
console.log(`[canary][debug] retry_original failed for action=${ctx.action}: ${retryInfo.message ?? retryError}`);
|
|
291
|
-
}
|
|
292
|
-
throw retryError;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
recordHealingEvent(summaryBase);
|
|
296
|
-
throw error;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
async function runMatcherWithHealing(params) {
|
|
300
|
-
const { matcher, matcherName, args, expectTarget, mode } = params;
|
|
301
|
-
const invoke = () => matcher.apply(expectTarget, args);
|
|
302
|
-
if (mode === "soft") {
|
|
303
|
-
return Promise.resolve(invoke());
|
|
304
|
-
}
|
|
305
|
-
try {
|
|
306
|
-
return await Promise.resolve(invoke());
|
|
307
|
-
} catch (error) {
|
|
308
|
-
const target = stringifyTarget(args?.[0]);
|
|
309
|
-
const failure = buildFailureContext("expect", matcherName, target, error);
|
|
310
|
-
recordHealingEvent({ ...failure, healed: false });
|
|
311
|
-
const decision = classifyFailure(failure);
|
|
312
|
-
if (!decision.healable) {
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
const outcome = await executeHealActions(decision, failure, {
|
|
316
|
-
kind: "expect",
|
|
317
|
-
action: matcherName,
|
|
318
|
-
target,
|
|
319
|
-
locator: isLocatorLike(args?.[0]) ? args[0] : void 0
|
|
320
|
-
});
|
|
321
|
-
const testContext = getTestContext();
|
|
322
|
-
const summaryBase = {
|
|
323
|
-
...failure,
|
|
324
|
-
healed: false,
|
|
325
|
-
strategy: "agentic",
|
|
326
|
-
reason: outcome.reason ?? decision.reason,
|
|
327
|
-
actions: actionsToEventItems(outcome.actionsRun),
|
|
328
|
-
durationMs: outcome.durationMs,
|
|
329
|
-
mode: outcome.mode,
|
|
330
|
-
decision: decision.reason,
|
|
331
|
-
modelId: outcome.modelId,
|
|
332
|
-
summary: outcome.summary,
|
|
333
|
-
testFile: testContext?.testFile,
|
|
334
|
-
testTitle: testContext?.testTitle
|
|
335
|
-
};
|
|
336
|
-
if (outcome.shouldRetryOriginal) {
|
|
337
|
-
try {
|
|
338
|
-
const retried = await Promise.resolve(invoke());
|
|
339
|
-
recordHealingEvent({ ...summaryBase, healed: true });
|
|
340
|
-
return retried;
|
|
341
|
-
} catch (retryError) {
|
|
342
|
-
const retryInfo = errorInfo(retryError);
|
|
343
|
-
recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });
|
|
344
|
-
throw retryError;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
recordHealingEvent(summaryBase);
|
|
348
|
-
throw error;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function buildFailureContext(kind, action, target, error) {
|
|
352
|
-
const info = errorInfo(error);
|
|
353
|
-
return {
|
|
354
|
-
kind,
|
|
355
|
-
action,
|
|
356
|
-
target,
|
|
357
|
-
errorMessage: info.message,
|
|
358
|
-
errorName: info.name,
|
|
359
|
-
stack: info.stack
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
function actionsToEventItems(actions) {
|
|
363
|
-
return actions.map((action) => ({ type: action }));
|
|
364
|
-
}
|
|
365
|
-
function errorInfo(error) {
|
|
366
|
-
if (error instanceof Error) {
|
|
367
|
-
return { message: error.message, name: error.name, stack: error.stack };
|
|
368
|
-
}
|
|
369
|
-
return { message: typeof error === "string" ? error : JSON.stringify(error) };
|
|
370
|
-
}
|
|
371
|
-
function isLocatorLike(candidate) {
|
|
372
|
-
return Boolean(candidate && typeof candidate === "object" && "scrollIntoViewIfNeeded" in candidate);
|
|
373
|
-
}
|
|
374
|
-
function isPageLike(candidate) {
|
|
375
|
-
return Boolean(candidate && typeof candidate === "object" && "waitForTimeout" in candidate);
|
|
376
|
-
}
|
|
377
|
-
function safeTargetString(target) {
|
|
378
|
-
try {
|
|
379
|
-
if (typeof target === "object" && target !== null && "toString" in target) {
|
|
380
|
-
const s = String(target.toString());
|
|
381
|
-
if (s && s !== "[object Object]") return s;
|
|
382
|
-
}
|
|
383
|
-
} catch {
|
|
384
|
-
}
|
|
385
|
-
return void 0;
|
|
386
|
-
}
|
|
387
|
-
function loadTestSource(filePath) {
|
|
388
|
-
if (!filePath) return void 0;
|
|
389
|
-
try {
|
|
390
|
-
return requireFn("fs").readFileSync(filePath, "utf-8");
|
|
391
|
-
} catch {
|
|
392
|
-
return void 0;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
function stringifyTarget(candidate) {
|
|
396
|
-
if (!candidate) return void 0;
|
|
397
|
-
if (typeof candidate === "string") return candidate;
|
|
398
|
-
if (typeof candidate === "object") {
|
|
399
|
-
if ("selector" in candidate && typeof candidate.selector === "string") {
|
|
400
|
-
return String(candidate.selector);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return safeTargetString(candidate);
|
|
404
|
-
}
|
|
405
|
-
function getTestContext() {
|
|
406
|
-
try {
|
|
407
|
-
const playwright = requireFn("@playwright/test");
|
|
408
|
-
if (playwright?.test?.info) {
|
|
409
|
-
const info = playwright.test.info();
|
|
410
|
-
if (info) {
|
|
411
|
-
return {
|
|
412
|
-
testFile: info.file,
|
|
413
|
-
testTitle: info.title
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
} catch {
|
|
418
|
-
}
|
|
419
|
-
return void 0;
|
|
420
|
-
}
|
|
421
105
|
|
|
422
106
|
// src/runner/preload.ts
|
|
423
107
|
getEventLog();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/runner/instrumentation.ts","../../src/runner/preload.ts"],"sourcesContent":["import Module from \"node:module\";\nimport { createRequire } from \"node:module\";\nimport { pathToFileURL } from \"node:url\";\nimport { loadCanaryConfig } from \"./config\";\nimport { classifyFailure, executeHealActions, type FailureContext } from \"./healer\";\nimport { alreadyPatched, markPatched, recordHealingEvent, setEventLogPath } from \"./state\";\n\nlet DEBUG = false;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst requireFn: any = typeof require !== \"undefined\" ? require : createRequire(import.meta.url);\n\nconst LOCATOR_ACTIONS = new Set([\n \"click\",\n \"dblclick\",\n \"fill\",\n \"check\",\n \"uncheck\",\n \"hover\",\n \"press\",\n \"type\",\n \"selectOption\",\n \"tap\",\n]);\n\nconst PAGE_ACTIONS = new Set([\n \"goto\",\n \"click\",\n \"dblclick\",\n \"fill\",\n \"check\",\n \"uncheck\",\n \"hover\",\n \"press\",\n \"type\",\n \"selectOption\",\n \"tap\",\n \"waitForSelector\",\n]);\n\nconst LOCATOR_FACTORIES = new Set([\n \"locator\",\n \"getByRole\",\n \"getByText\",\n \"getByLabel\",\n \"getByPlaceholder\",\n \"getByAltText\",\n \"getByTitle\",\n \"getByTestId\",\n \"frameLocator\",\n]);\n\nconst LOCATOR_CHAIN_METHODS = new Set([\n \"locator\",\n \"first\",\n \"last\",\n \"nth\",\n \"filter\",\n \"getByRole\",\n \"getByText\",\n \"getByLabel\",\n \"getByPlaceholder\",\n \"getByAltText\",\n \"getByTitle\",\n \"getByTestId\",\n]);\n\nexport function installInstrumentation(): void {\n if (alreadyPatched()) return;\n\n const config = loadCanaryConfig();\n DEBUG = !!config.debug;\n if (!config.enabled) {\n markPatched();\n return;\n }\n\n setEventLogPath(config.eventLogPath);\n if (!ensurePlaywrightVersion(config.allowedPlaywrightVersion)) {\n recordHealingEvent({\n kind: \"unknown\",\n action: \"playwright_version_mismatch\",\n errorMessage: \"Playwright version does not match CANARY_PLAYWRIGHT_VERSION\",\n healed: false,\n });\n markPatched();\n return;\n }\n hookPlaywrightModuleLoad();\n\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] instrumentation installed (eventLog=${config.eventLogPath})`);\n }\n\n recordHealingEvent({\n kind: \"unknown\",\n action: \"instrumentation_ready\",\n healed: false,\n });\n markPatched();\n}\n\nfunction hookPlaywrightModuleLoad(): void {\n // Prevent double-hooking.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mod = Module as any;\n const originalLoad: typeof mod._load = mod._canaryOriginalLoad ?? mod._load;\n if (!mod._canaryOriginalLoad) {\n mod._canaryOriginalLoad = originalLoad;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mod._load = function patchedLoad(request: string, parent: any, isMain: boolean) {\n if (request === \"@playwright/test\" || request === \"playwright/test\") {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] patching playwright module (request=${request})`);\n }\n const real = originalLoad.call(this, request, parent, isMain);\n return createPatchedPlaywright(real);\n }\n return originalLoad.call(this, request, parent, isMain);\n };\n}\n\ntype PlaywrightExports = {\n test: { extend: typeof import(\"@playwright/test\").test.extend };\n expect: typeof import(\"@playwright/test\").expect;\n [key: string]: unknown;\n};\n\nfunction createPatchedPlaywright(real: PlaywrightExports): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const extended = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: extended,\n expect: wrapExpect(real.expect),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright patched`);\n }\n return patched;\n}\n\nfunction ensurePlaywrightVersion(expected?: string): boolean {\n if (!expected) return true;\n try {\n const pkg = requireFn(\"playwright/package.json\") as { version?: string };\n if (pkg.version && pkg.version.startsWith(expected)) {\n return true;\n }\n // eslint-disable-next-line no-console\n console.warn(`[canary] Playwright version mismatch. Expected ${expected}, found ${pkg.version ?? \"unknown\"}`);\n return false;\n } catch {\n return true;\n }\n}\n\nfunction wrapExpect(expectImpl: PlaywrightExports[\"expect\"]): PlaywrightExports[\"expect\"] {\n const wrapMatchers = (expectation: unknown, mode: \"hard\" | \"soft\") => {\n return new Proxy(expectation ?? {}, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof prop === \"string\" && typeof value === \"function\") {\n return (...args: unknown[]) => runMatcherWithHealing({ matcher: value as (...args: unknown[]) => unknown, matcherName: prop, args, expectTarget: target, mode });\n }\n return value;\n },\n });\n };\n\n const proxy = new Proxy(expectImpl, {\n apply(target, thisArg, argArray) {\n const expectation = (target as (...args: unknown[]) => unknown).apply(thisArg, argArray as unknown[]);\n return wrapMatchers(expectation, \"hard\");\n },\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (prop === \"soft\" && typeof value === \"function\") {\n return (...args: unknown[]) => wrapMatchers((value as (...args: unknown[]) => unknown).apply(target, args), \"soft\");\n }\n return value;\n },\n });\n\n return proxy as PlaywrightExports[\"expect\"];\n}\n\nfunction wrapPage<PageType extends object>(page: PageType): PageType {\n return new Proxy(page, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop === \"string\") {\n if (LOCATOR_FACTORIES.has(prop) && typeof value === \"function\") {\n return (...args: unknown[]) => {\n const locator = (value as (...args: unknown[]) => unknown).apply(target, args);\n return wrapLocator(locator);\n };\n }\n\n if (PAGE_ACTIONS.has(prop) && typeof value === \"function\") {\n return async (...args: unknown[]) => {\n return attemptWithHealing({\n kind: \"page\",\n action: prop,\n target: safeTargetString(target),\n locator: undefined,\n page: target as unknown,\n invoke: () => (value as (...args: unknown[]) => unknown).apply(target, args),\n });\n };\n }\n }\n\n return value;\n },\n });\n}\n\nfunction wrapLocator(locator: unknown): unknown {\n if (!locator || typeof locator !== \"object\") return locator;\n\n return new Proxy(locator, {\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof prop === \"string\" && LOCATOR_ACTIONS.has(prop) && typeof value === \"function\") {\n return async (...args: unknown[]) => {\n return attemptWithHealing({\n kind: \"locator\",\n action: prop,\n target: safeTargetString(target),\n locator: target as unknown,\n page: undefined,\n invoke: () => (value as (...args: unknown[]) => unknown).apply(target, args),\n });\n };\n }\n\n if (typeof prop === \"string\" && LOCATOR_CHAIN_METHODS.has(prop) && typeof value === \"function\") {\n return (...args: unknown[]) => wrapLocator((value as (...args: unknown[]) => unknown).apply(target, args));\n }\n\n return value;\n },\n });\n}\n\ntype AttemptContext = {\n kind: FailureContext[\"kind\"];\n action: string;\n target?: string;\n locator?: unknown;\n page?: unknown;\n invoke: () => unknown;\n};\n\nasync function attemptWithHealing<T>(ctx: AttemptContext): Promise<T> {\n try {\n return await Promise.resolve(ctx.invoke()) as T;\n } catch (error) {\n const failure = buildFailureContext(ctx.kind, ctx.action, ctx.target, error);\n recordHealingEvent({ ...failure, healed: false });\n const decision = classifyFailure(failure);\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] failure intercepted: kind=${ctx.kind} action=${ctx.action} reason=${decision.healable ? decision.reason ?? \"healable\" : decision.reason}`);\n }\n if (!decision.healable) {\n throw error;\n }\n\n // Get test context for correlation before passing to heal actions\n const testContext = getTestContext();\n\n const outcome = await executeHealActions(decision, failure, {\n kind: ctx.kind,\n action: ctx.action,\n target: ctx.target,\n locator: isLocatorLike(ctx.locator) ? ctx.locator : undefined,\n page: isPageLike(ctx.page) ? ctx.page : undefined,\n testContext: {\n testFile: testContext?.testFile,\n testTitle: testContext?.testTitle,\n testSource: loadTestSource(testContext?.testFile),\n },\n });\n\n const summaryBase = {\n ...failure,\n healed: outcome.healed,\n strategy: 'agentic',\n reason: outcome.reason ?? decision.reason,\n actions: actionsToEventItems(outcome.actionsRun),\n durationMs: outcome.durationMs,\n mode: outcome.mode,\n decision: decision.reason,\n modelId: outcome.modelId,\n summary: outcome.summary,\n testFile: testContext?.testFile,\n testTitle: testContext?.testTitle,\n } as const;\n\n if (outcome.healed && !outcome.shouldRetryOriginal) {\n recordHealingEvent({ ...summaryBase, healed: true });\n return undefined as T;\n }\n\n if (outcome.shouldRetryOriginal) {\n try {\n const retried = await Promise.resolve(ctx.invoke()) as T;\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] retry_original succeeded for action=${ctx.action}`);\n }\n recordHealingEvent({ ...summaryBase, healed: true });\n return retried;\n } catch (retryError) {\n const retryInfo = errorInfo(retryError);\n recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] retry_original failed for action=${ctx.action}: ${retryInfo.message ?? retryError}`);\n }\n throw retryError;\n }\n }\n\n recordHealingEvent(summaryBase);\n throw error;\n }\n}\n\nasync function runMatcherWithHealing(params: {\n matcher: (...args: unknown[]) => unknown;\n matcherName: string;\n args: unknown[];\n expectTarget: unknown;\n mode: \"hard\" | \"soft\";\n}): Promise<unknown> {\n const { matcher, matcherName, args, expectTarget, mode } = params;\n\n const invoke = () => matcher.apply(expectTarget, args);\n\n // Skip healing for soft assertions to avoid altering semantics.\n if (mode === \"soft\") {\n return Promise.resolve(invoke());\n }\n\n try {\n return await Promise.resolve(invoke());\n } catch (error) {\n const target = stringifyTarget(args?.[0]);\n const failure = buildFailureContext(\"expect\", matcherName, target, error);\n recordHealingEvent({ ...failure, healed: false });\n const decision = classifyFailure(failure);\n if (!decision.healable) {\n throw error;\n }\n\n const outcome = await executeHealActions(decision, failure, {\n kind: \"expect\",\n action: matcherName,\n target,\n locator: isLocatorLike(args?.[0]) ? args[0] : undefined,\n });\n\n // Try to get test context for correlation\n const testContext = getTestContext();\n\n const summaryBase = {\n ...failure,\n healed: false,\n strategy: 'agentic',\n reason: outcome.reason ?? decision.reason,\n actions: actionsToEventItems(outcome.actionsRun),\n durationMs: outcome.durationMs,\n mode: outcome.mode,\n decision: decision.reason,\n modelId: outcome.modelId,\n summary: outcome.summary,\n testFile: testContext?.testFile,\n testTitle: testContext?.testTitle,\n } as const;\n\n if (outcome.shouldRetryOriginal) {\n try {\n const retried = await Promise.resolve(invoke());\n recordHealingEvent({ ...summaryBase, healed: true });\n return retried;\n } catch (retryError) {\n const retryInfo = errorInfo(retryError);\n recordHealingEvent({ ...summaryBase, healed: false, errorMessage: retryInfo.message });\n throw retryError;\n }\n }\n\n recordHealingEvent(summaryBase);\n throw error;\n }\n}\n\nfunction buildFailureContext(\n kind: FailureContext[\"kind\"],\n action: string,\n target: string | undefined,\n error: unknown\n): FailureContext {\n const info = errorInfo(error);\n return {\n kind,\n action,\n target,\n errorMessage: info.message,\n errorName: info.name,\n stack: info.stack,\n };\n}\n\nfunction actionsToEventItems(actions: string[]): Array<{ type: string; detail?: string }> {\n return actions.map((action) => ({ type: action }));\n}\n\nfunction errorInfo(error: unknown): { message?: string; name?: string; stack?: string } {\n if (error instanceof Error) {\n return { message: error.message, name: error.name, stack: error.stack };\n }\n return { message: typeof error === \"string\" ? error : JSON.stringify(error) };\n}\n\nfunction isLocatorLike(candidate: unknown): candidate is { scrollIntoViewIfNeeded: () => Promise<unknown> } {\n return Boolean(candidate && typeof candidate === \"object\" && \"scrollIntoViewIfNeeded\" in (candidate as Record<string, unknown>));\n}\n\nfunction isPageLike(candidate: unknown): candidate is { waitForTimeout: (ms: number) => Promise<unknown> } {\n return Boolean(candidate && typeof candidate === \"object\" && \"waitForTimeout\" in (candidate as Record<string, unknown>));\n}\n\nfunction safeTargetString(target: unknown): string | undefined {\n try {\n if (typeof target === \"object\" && target !== null && \"toString\" in target) {\n const s = String((target as { toString: () => string }).toString());\n if (s && s !== \"[object Object]\") return s;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nfunction loadTestSource(filePath?: string): string | undefined {\n if (!filePath) return undefined;\n try {\n return requireFn(\"fs\").readFileSync(filePath, \"utf-8\") as string;\n } catch {\n return undefined;\n }\n}\n\nfunction stringifyTarget(candidate: unknown): string | undefined {\n if (!candidate) return undefined;\n if (typeof candidate === \"string\") return candidate;\n if (typeof candidate === \"object\") {\n if (\"selector\" in (candidate as Record<string, unknown>) && typeof (candidate as Record<string, unknown>).selector === \"string\") {\n return String((candidate as Record<string, unknown>).selector);\n }\n }\n return safeTargetString(candidate);\n}\n\n/**\n * Try to get test context from Playwright's test.info().\n * This is used to correlate healing events with tests in the report.\n */\nfunction getTestContext(): { testFile?: string; testTitle?: string } | undefined {\n try {\n // Try to dynamically require @playwright/test and call test.info()\n const playwright = requireFn(\"@playwright/test\") as { test?: { info?: () => { file?: string; title?: string } } };\n if (playwright?.test?.info) {\n const info = playwright.test.info();\n if (info) {\n return {\n testFile: info.file,\n testTitle: info.title,\n };\n }\n }\n } catch {\n // Not in a test context or @playwright/test not available\n }\n return undefined;\n}\n","import { installInstrumentation } from \"./instrumentation\";\nimport { getEventLog } from \"./state\";\n\n// Ensure globals are initialized and patch Playwright classes in each worker process.\ngetEventLog();\ninstallInstrumentation();\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/runner/instrumentation.ts","../../src/runner/preload.ts"],"sourcesContent":["/**\n * Module._load hook instrumentation for Playwright healing.\n *\n * Intercepts `@playwright/test` imports and patches them with healing-aware proxies.\n * Delegates all proxy/healing logic to `healing-helpers.ts`.\n */\n\nimport Module from \"node:module\";\nimport { createRequire } from \"node:module\";\nimport { loadCanaryConfig } from \"./config\";\nimport { alreadyPatched, markPatched, recordHealingEvent, setEventLogPath } from \"./state\";\nimport {\n wrapExpect,\n wrapPage,\n type PlaywrightExports,\n} from \"./healing-helpers\";\n\nlet DEBUG = false;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst requireFn: any = typeof require !== \"undefined\" ? require : createRequire(import.meta.url);\n\nexport function installInstrumentation(): void {\n if (alreadyPatched()) return;\n\n const config = loadCanaryConfig();\n DEBUG = !!config.debug;\n if (!config.enabled) {\n markPatched();\n return;\n }\n\n setEventLogPath(config.eventLogPath);\n if (!ensurePlaywrightVersion(config.allowedPlaywrightVersion)) {\n recordHealingEvent({\n kind: \"unknown\",\n action: \"playwright_version_mismatch\",\n errorMessage: \"Playwright version does not match CANARY_PLAYWRIGHT_VERSION\",\n healed: false,\n });\n markPatched();\n return;\n }\n hookPlaywrightModuleLoad();\n\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] instrumentation installed (eventLog=${config.eventLogPath})`);\n }\n\n recordHealingEvent({\n kind: \"unknown\",\n action: \"instrumentation_ready\",\n healed: false,\n });\n markPatched();\n}\n\nfunction hookPlaywrightModuleLoad(): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mod = Module as any;\n const originalLoad: typeof mod._load = mod._canaryOriginalLoad ?? mod._load;\n if (!mod._canaryOriginalLoad) {\n mod._canaryOriginalLoad = originalLoad;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mod._load = function patchedLoad(request: string, parent: any, isMain: boolean) {\n if (request === \"@playwright/test\" || request === \"playwright/test\") {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] patching playwright module (request=${request})`);\n }\n const real = originalLoad.call(this, request, parent, isMain);\n return createPatchedPlaywright(real);\n }\n return originalLoad.call(this, request, parent, isMain);\n };\n}\n\nfunction createPatchedPlaywright(real: PlaywrightExports): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const options = { debug: DEBUG };\n\n const extended = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: extended,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright patched`);\n }\n return patched;\n}\n\nfunction ensurePlaywrightVersion(expected?: string): boolean {\n if (!expected) return true;\n try {\n const pkg = requireFn(\"playwright/package.json\") as { version?: string };\n if (pkg.version && pkg.version.startsWith(expected)) {\n return true;\n }\n // eslint-disable-next-line no-console\n console.warn(`[canary] Playwright version mismatch. Expected ${expected}, found ${pkg.version ?? \"unknown\"}`);\n return false;\n } catch {\n return true;\n }\n}\n","import { installInstrumentation } from \"./instrumentation\";\nimport { getEventLog } from \"./state\";\n\n// Ensure globals are initialized and patch Playwright classes in each worker process.\ngetEventLog();\ninstallInstrumentation();\n"],"mappings":";;;;;;;;;;;;;;;AAOA,OAAO,YAAY;AACnB,SAAS,qBAAqB;AAS9B,IAAI,QAAQ;AAGZ,IAAM,YAAiB,OAAO,cAAY,cAAc,YAAU,cAAc,YAAY,GAAG;AAExF,SAAS,yBAA+B;AAC7C,MAAI,eAAe,EAAG;AAEtB,QAAM,SAAS,iBAAiB;AAChC,UAAQ,CAAC,CAAC,OAAO;AACjB,MAAI,CAAC,OAAO,SAAS;AACnB,gBAAY;AACZ;AAAA,EACF;AAEA,kBAAgB,OAAO,YAAY;AACnC,MAAI,CAAC,wBAAwB,OAAO,wBAAwB,GAAG;AAC7D,uBAAmB;AAAA,MACjB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC;AACD,gBAAY;AACZ;AAAA,EACF;AACA,2BAAyB;AAEzB,MAAI,OAAO;AAET,YAAQ,IAAI,uDAAuD,OAAO,YAAY,GAAG;AAAA,EAC3F;AAEA,qBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AACD,cAAY;AACd;AAEA,SAAS,2BAAiC;AAExC,QAAM,MAAM;AACZ,QAAM,eAAiC,IAAI,uBAAuB,IAAI;AACtE,MAAI,CAAC,IAAI,qBAAqB;AAC5B,QAAI,sBAAsB;AAAA,EAC5B;AAGA,MAAI,QAAQ,SAAS,YAAY,SAAiB,QAAa,QAAiB;AAC9E,QAAI,YAAY,sBAAsB,YAAY,mBAAmB;AACnE,UAAI,OAAO;AAET,gBAAQ,IAAI,uDAAuD,OAAO,GAAG;AAAA,MAC/E;AACA,YAAM,OAAO,aAAa,KAAK,MAAM,SAAS,QAAQ,MAAM;AAC5D,aAAO,wBAAwB,IAAI;AAAA,IACrC;AACA,WAAO,aAAa,KAAK,MAAM,SAAS,QAAQ,MAAM;AAAA,EACxD;AACF;AAEA,SAAS,wBAAwB,MAA4C;AAC3E,MAAK,KAAuC,gBAAiB,QAAO;AAEpE,QAAM,UAAU,EAAE,OAAO,MAAM;AAE/B,QAAM,WAAW,KAAK,KAAK,OAAO;AAAA,IAChC,MAAM,OAAO,EAAE,KAAK,GAAG,QAAQ;AAC7B,yBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,cAAc,SAAS,MAAM,OAAO;AAC1C,YAAM,IAAI,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,UAA6B;AAAA,IACjC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAAA,EACzC;AAEA,EAAC,QAA0C,kBAAkB;AAC7D,MAAI,OAAO;AAET,YAAQ,IAAI,oCAAoC;AAAA,EAClD;AACA,SAAO;AACT;AAEA,SAAS,wBAAwB,UAA4B;AAC3D,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,MAAM,UAAU,yBAAyB;AAC/C,QAAI,IAAI,WAAW,IAAI,QAAQ,WAAW,QAAQ,GAAG;AACnD,aAAO;AAAA,IACT;AAEA,YAAQ,KAAK,kDAAkD,QAAQ,WAAW,IAAI,WAAW,SAAS,EAAE;AAC5G,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxHA,YAAY;AACZ,uBAAuB;","names":[]}
|