@cementic/cementic-test 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/{chunk-XM2TYPU7.js → chunk-3TBS4PBV.js} +136 -14
- package/dist/chunk-3TBS4PBV.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/{gen-EST5UZNZ.js → gen-YF22KYYU.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-XM2TYPU7.js.map +0 -1
- /package/dist/{gen-EST5UZNZ.js.map → gen-YF22KYYU.js.map} +0 -0
package/README.md
CHANGED
|
@@ -365,6 +365,12 @@ And start automating.
|
|
|
365
365
|
|
|
366
366
|
## Changelog
|
|
367
367
|
|
|
368
|
+
### v0.2.10
|
|
369
|
+
|
|
370
|
+
- fixed `ct gen` so selector-hinted fields like `#username` and `#password` infer matching `CT_VAR_*` constants instead of leaving `.fill('value')` placeholders behind
|
|
371
|
+
- fixed generated `toHaveValue(...)` assertions so they reuse the same `CT_VAR_*` binding when the related fill step was variablized
|
|
372
|
+
- added regression coverage for selector-driven auth generation to keep fill calls and value assertions aligned
|
|
373
|
+
|
|
368
374
|
### v0.2.9
|
|
369
375
|
|
|
370
376
|
- added `CT_VAR_*` extraction in `ct gen` for input and select test data so generated specs use env-backed constants instead of hardcoded values
|
|
@@ -70,6 +70,53 @@ function fieldNameToEnvSuffix(value) {
|
|
|
70
70
|
if (/^(?:FIELD|INPUT|BOX|AREA|DROPDOWN|COMBOBOX|SELECT|MENU)$/.test(suffix)) return void 0;
|
|
71
71
|
return suffix;
|
|
72
72
|
}
|
|
73
|
+
function normalizeSelectorTarget(value) {
|
|
74
|
+
if (!value) return void 0;
|
|
75
|
+
const trimmed = value.trim();
|
|
76
|
+
if (!trimmed) return void 0;
|
|
77
|
+
return trimmed.replace(/^page\./, "").replace(/\s+/g, " ");
|
|
78
|
+
}
|
|
79
|
+
function parseQuotedLiteral(value) {
|
|
80
|
+
if (!value) return void 0;
|
|
81
|
+
const trimmed = value.trim();
|
|
82
|
+
if (trimmed.length < 2) return void 0;
|
|
83
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(trimmed);
|
|
86
|
+
} catch {
|
|
87
|
+
return trimmed.slice(1, -1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
91
|
+
return trimmed.slice(1, -1).replace(/\\\\/g, "\\").replace(/\\'/g, "'");
|
|
92
|
+
}
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
function selectorTargetToEnvSuffix(value) {
|
|
96
|
+
const selector = normalizeSelectorTarget(value);
|
|
97
|
+
if (!selector) return void 0;
|
|
98
|
+
const quotedArg = (pattern, group = 2) => {
|
|
99
|
+
const match = selector.match(pattern);
|
|
100
|
+
return match?.[group]?.trim();
|
|
101
|
+
};
|
|
102
|
+
const locatorArg = quotedArg(/^(?:locator)\((['"])(.*?)\1\)/i);
|
|
103
|
+
if (locatorArg) {
|
|
104
|
+
const idMatch = locatorArg.match(/#([A-Za-z][\w-]*)/);
|
|
105
|
+
if (idMatch) return sanitizeEnvSegment(idMatch[1]);
|
|
106
|
+
const attrMatch = locatorArg.match(/\[(?:name|id|data-testid|aria-label)=['"]?([^'"\]]+)['"]?\]/i);
|
|
107
|
+
if (attrMatch) return sanitizeEnvSegment(attrMatch[1]);
|
|
108
|
+
const classMatch = locatorArg.match(/\.([A-Za-z][\w-]*)/);
|
|
109
|
+
if (classMatch) return sanitizeEnvSegment(classMatch[1]);
|
|
110
|
+
return sanitizeEnvSegment(locatorArg.replace(/^[#.]+/, ""));
|
|
111
|
+
}
|
|
112
|
+
const labelArg = quotedArg(/^(?:getByLabel|getByPlaceholder|getByTestId|getByText)\((['"])(.*?)\1/i) ?? quotedArg(/^getByRole\((['"])(?:textbox|searchbox|combobox|spinbutton)\1,\s*\{\s*name:\s*(['"])(.*?)\2/i, 3);
|
|
113
|
+
if (labelArg) return fieldNameToEnvSuffix(labelArg) ?? sanitizeEnvSegment(labelArg);
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
function isGenericFallbackValue(value) {
|
|
117
|
+
if (!value) return true;
|
|
118
|
+
return /^(?:value|text|input|option|selection|selected value|default)$/i.test(value.trim());
|
|
119
|
+
}
|
|
73
120
|
function parseStepBinding(step) {
|
|
74
121
|
const s = step.trim();
|
|
75
122
|
const fillMatch = s.match(/\b(?:fill|input|write)\s+(?:in\s+)?(.+?)\s+with\s+(['"])(.*?)\2/i);
|
|
@@ -81,6 +128,14 @@ function parseStepBinding(step) {
|
|
|
81
128
|
value: fillMatch[3].trim()
|
|
82
129
|
};
|
|
83
130
|
}
|
|
131
|
+
const fillWithoutValueMatch = s.match(/\b(?:fill|input|write)\s+(?:in\s+)?(.+?)$/i);
|
|
132
|
+
if (fillWithoutValueMatch) {
|
|
133
|
+
return {
|
|
134
|
+
kind: "fill",
|
|
135
|
+
rawField: fillWithoutValueMatch[1].trim(),
|
|
136
|
+
fieldLabel: cleanFieldLabel(fillWithoutValueMatch[1])
|
|
137
|
+
};
|
|
138
|
+
}
|
|
84
139
|
const enterMatch = s.match(/\b(?:enter|type)\s+(['"])(.*?)\1\s+(?:in|into)\s+(.+)/i);
|
|
85
140
|
if (enterMatch) {
|
|
86
141
|
return {
|
|
@@ -90,6 +145,14 @@ function parseStepBinding(step) {
|
|
|
90
145
|
value: enterMatch[2].trim()
|
|
91
146
|
};
|
|
92
147
|
}
|
|
148
|
+
const enterWithoutValueMatch = s.match(/\b(?:enter|type)\s+(?:in|into)\s+(.+)/i);
|
|
149
|
+
if (enterWithoutValueMatch) {
|
|
150
|
+
return {
|
|
151
|
+
kind: "fill",
|
|
152
|
+
rawField: enterWithoutValueMatch[1].trim(),
|
|
153
|
+
fieldLabel: cleanFieldLabel(enterWithoutValueMatch[1])
|
|
154
|
+
};
|
|
155
|
+
}
|
|
93
156
|
const selectMatch = s.match(/\b(?:select|choose|pick)\s+(['"])(.*?)\1\s+(?:from|in|into|on)\s+(.+)/i);
|
|
94
157
|
if (selectMatch) {
|
|
95
158
|
return {
|
|
@@ -99,6 +162,14 @@ function parseStepBinding(step) {
|
|
|
99
162
|
value: selectMatch[2].trim()
|
|
100
163
|
};
|
|
101
164
|
}
|
|
165
|
+
const selectWithoutValueMatch = s.match(/\b(?:select|choose|pick)\s+(.+?)\s+(?:dropdown|combobox|select|menu)\b/i);
|
|
166
|
+
if (selectWithoutValueMatch) {
|
|
167
|
+
return {
|
|
168
|
+
kind: "select",
|
|
169
|
+
rawField: selectWithoutValueMatch[1].trim(),
|
|
170
|
+
fieldLabel: cleanFieldLabel(selectWithoutValueMatch[1])
|
|
171
|
+
};
|
|
172
|
+
}
|
|
102
173
|
return void 0;
|
|
103
174
|
}
|
|
104
175
|
function resolveStepVarOverride(overrides, envKey, keySuffix, rawField, fieldLabel) {
|
|
@@ -110,23 +181,38 @@ function resolveStepVarOverride(overrides, envKey, keySuffix, rawField, fieldLab
|
|
|
110
181
|
}
|
|
111
182
|
return void 0;
|
|
112
183
|
}
|
|
113
|
-
function extractStepVars(steps, overrides) {
|
|
184
|
+
function extractStepVars(steps, stepHints, assertionHints, overrides) {
|
|
114
185
|
const declarations = [];
|
|
115
186
|
const byStepIndex = /* @__PURE__ */ new Map();
|
|
116
187
|
const seen = /* @__PURE__ */ new Map();
|
|
117
188
|
let unnamedFillIndex = 0;
|
|
118
189
|
let unnamedSelectIndex = 0;
|
|
190
|
+
const assertionFallbacks = /* @__PURE__ */ new Map();
|
|
191
|
+
for (const hint of assertionHints ?? []) {
|
|
192
|
+
const parsed = parseValueAssertionHint(hint?.playwright);
|
|
193
|
+
if (!parsed?.expected) continue;
|
|
194
|
+
if (parsed.target) assertionFallbacks.set(parsed.target, parsed.expected);
|
|
195
|
+
const envSuffix = selectorTargetToEnvSuffix(parsed.target);
|
|
196
|
+
if (envSuffix) assertionFallbacks.set(`CT_VAR_${envSuffix}`, parsed.expected);
|
|
197
|
+
}
|
|
119
198
|
for (const [index, step] of steps.entries()) {
|
|
120
199
|
const binding = parseStepBinding(step);
|
|
121
200
|
if (!binding) continue;
|
|
122
|
-
const keySuffix = fieldNameToEnvSuffix(binding.rawField) ?? `${binding.kind === "select" ? "SELECT" : "INPUT"}_${binding.kind === "select" ? ++unnamedSelectIndex : ++unnamedFillIndex}`;
|
|
201
|
+
const keySuffix = fieldNameToEnvSuffix(binding.rawField) ?? fieldNameToEnvSuffix(binding.fieldLabel) ?? selectorTargetToEnvSuffix(stepHints?.[index]?.selector) ?? `${binding.kind === "select" ? "SELECT" : "INPUT"}_${binding.kind === "select" ? ++unnamedSelectIndex : ++unnamedFillIndex}`;
|
|
123
202
|
const envKey = `CT_VAR_${keySuffix}`;
|
|
203
|
+
const selectorTarget = normalizeSelectorTarget(stepHints?.[index]?.selector);
|
|
204
|
+
const assertionFallback = (selectorTarget ? assertionFallbacks.get(selectorTarget) : void 0) ?? assertionFallbacks.get(envKey);
|
|
124
205
|
let stepVar = seen.get(envKey);
|
|
125
206
|
if (!stepVar) {
|
|
207
|
+
const fallbackFromBinding = binding.value?.trim();
|
|
208
|
+
const fallback = resolveStepVarOverride(overrides, envKey, keySuffix, binding.rawField, binding.fieldLabel) ?? ((!fallbackFromBinding || isGenericFallbackValue(fallbackFromBinding)) && assertionFallback ? assertionFallback : fallbackFromBinding) ?? assertionFallback ?? (binding.kind === "select" ? "option" : "value");
|
|
126
209
|
stepVar = {
|
|
127
210
|
envKey,
|
|
128
211
|
constName: envKey,
|
|
129
|
-
fallback
|
|
212
|
+
fallback,
|
|
213
|
+
selectorHint: stepHints?.[index]?.selector,
|
|
214
|
+
selectorTarget,
|
|
215
|
+
fieldLabel: binding.fieldLabel
|
|
130
216
|
};
|
|
131
217
|
seen.set(envKey, stepVar);
|
|
132
218
|
declarations.push(stepVar);
|
|
@@ -185,6 +271,32 @@ function ensureStatement(value) {
|
|
|
185
271
|
if (!trimmed) return trimmed;
|
|
186
272
|
return trimmed.endsWith(";") ? trimmed : `${trimmed};`;
|
|
187
273
|
}
|
|
274
|
+
function parseValueAssertionHint(playwright) {
|
|
275
|
+
const statement = ensureStatement(String(playwright ?? "").trim());
|
|
276
|
+
if (!statement) return void 0;
|
|
277
|
+
const match = statement.match(/expect\(([\s\S]+)\)\.toHaveValue\(([\s\S]+)\)\s*;$/);
|
|
278
|
+
if (!match) return void 0;
|
|
279
|
+
return {
|
|
280
|
+
target: normalizeSelectorTarget(match[1]),
|
|
281
|
+
expected: parseQuotedLiteral(match[2])
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function findStepVarForTarget(target, stepVars) {
|
|
285
|
+
const normalizedTarget = normalizeSelectorTarget(target);
|
|
286
|
+
if (!normalizedTarget) return void 0;
|
|
287
|
+
const bySelector = stepVars.declarations.find((stepVar) => stepVar.selectorTarget === normalizedTarget);
|
|
288
|
+
if (bySelector) return bySelector;
|
|
289
|
+
const envSuffix = selectorTargetToEnvSuffix(normalizedTarget);
|
|
290
|
+
if (!envSuffix) return void 0;
|
|
291
|
+
return stepVars.declarations.find((stepVar) => stepVar.envKey === `CT_VAR_${envSuffix}`);
|
|
292
|
+
}
|
|
293
|
+
function rewriteValueAssertionToUseVariable(playwright, stepVars) {
|
|
294
|
+
const parsed = parseValueAssertionHint(playwright);
|
|
295
|
+
if (!parsed?.target) return void 0;
|
|
296
|
+
const stepVar = findStepVarForTarget(parsed.target, stepVars);
|
|
297
|
+
if (!stepVar) return void 0;
|
|
298
|
+
return ensureStatement(`await expect(page.${parsed.target}).toHaveValue(${stepVar.constName})`);
|
|
299
|
+
}
|
|
188
300
|
function stepToPlaywright(step, url, hint, stepVar) {
|
|
189
301
|
const s = step.trim();
|
|
190
302
|
const hintedSelector = hint?.selector ? `page.${hint.selector}` : void 0;
|
|
@@ -199,9 +311,10 @@ function stepToPlaywright(step, url, hint, stepVar) {
|
|
|
199
311
|
}
|
|
200
312
|
const binding = parseStepBinding(s);
|
|
201
313
|
if (binding?.kind === "fill") {
|
|
202
|
-
|
|
314
|
+
const fallbackValue = binding.value ?? stepVar?.fallback ?? "value";
|
|
315
|
+
if (hintedSelector) return `await ${hintedSelector}.fill(${valueExpression(fallbackValue)});`;
|
|
203
316
|
const field = binding.fieldLabel ?? "field";
|
|
204
|
-
return `await page.getByLabel('${escapeForSingleQuotedString(field)}').fill(${valueExpression(
|
|
317
|
+
return `await page.getByLabel('${escapeForSingleQuotedString(field)}').fill(${valueExpression(fallbackValue)});`;
|
|
205
318
|
}
|
|
206
319
|
if (/\bclick\b.*(button|btn|submit|sign in|log in|login|register|continue|next|save|confirm)/i.test(s)) {
|
|
207
320
|
if (hintedSelector) return `await ${hintedSelector}.click();`;
|
|
@@ -223,11 +336,12 @@ function stepToPlaywright(step, url, hint, stepVar) {
|
|
|
223
336
|
return `await page.getByText('${escapeForSingleQuotedString(target)}').click();`;
|
|
224
337
|
}
|
|
225
338
|
if (binding?.kind === "select") {
|
|
226
|
-
|
|
339
|
+
const fallbackValue = binding.value ?? stepVar?.fallback ?? "option";
|
|
340
|
+
if (hintedSelector) return `await ${hintedSelector}.selectOption(${valueExpression(fallbackValue)});`;
|
|
227
341
|
if (binding.fieldLabel) {
|
|
228
|
-
return `await page.getByLabel('${escapeForSingleQuotedString(binding.fieldLabel)}').selectOption(${valueExpression(
|
|
342
|
+
return `await page.getByLabel('${escapeForSingleQuotedString(binding.fieldLabel)}').selectOption(${valueExpression(fallbackValue)});`;
|
|
229
343
|
}
|
|
230
|
-
return `await page.getByRole('combobox').selectOption(${valueExpression(
|
|
344
|
+
return `await page.getByRole('combobox').selectOption(${valueExpression(fallbackValue)});`;
|
|
231
345
|
}
|
|
232
346
|
if (/\b(uncheck|untick|disable)\b/i.test(s)) {
|
|
233
347
|
if (hintedSelector) return `await ${hintedSelector}.uncheck();`;
|
|
@@ -260,8 +374,11 @@ function stepToPlaywright(step, url, hint, stepVar) {
|
|
|
260
374
|
}
|
|
261
375
|
return `// TODO: map to Playwright action \u2192 "${s}"`;
|
|
262
376
|
}
|
|
263
|
-
function expectedToAssertion(expected, norm, hint) {
|
|
264
|
-
if (hint?.playwright)
|
|
377
|
+
function expectedToAssertion(expected, norm, hint, stepVars) {
|
|
378
|
+
if (hint?.playwright) {
|
|
379
|
+
const rewritten = stepVars ? rewriteValueAssertionToUseVariable(hint.playwright, stepVars) : void 0;
|
|
380
|
+
return rewritten ?? ensureStatement(hint.playwright);
|
|
381
|
+
}
|
|
265
382
|
const s = expected.trim();
|
|
266
383
|
const fieldName = fieldNameFromSentence(s);
|
|
267
384
|
const currentUrlPattern = urlPatternFromAbsoluteUrl(norm?.url);
|
|
@@ -339,7 +456,12 @@ function expectedToAssertion(expected, norm, hint) {
|
|
|
339
456
|
}
|
|
340
457
|
if (/\b(field|input|value|filled)\b/i.test(s)) {
|
|
341
458
|
const valMatch = s.match(/["']([^"']+)["']/);
|
|
342
|
-
if (valMatch)
|
|
459
|
+
if (valMatch) {
|
|
460
|
+
const envSuffix = fieldNameToEnvSuffix(fieldName);
|
|
461
|
+
const stepVar = envSuffix ? stepVars?.declarations.find((candidate) => candidate.envKey === `CT_VAR_${envSuffix}`) : void 0;
|
|
462
|
+
if (stepVar) return `await expect(page.getByRole('textbox').first()).toHaveValue(${stepVar.constName});`;
|
|
463
|
+
return `await expect(page.getByRole('textbox').first()).toHaveValue('${escapeForSingleQuotedString(valMatch[1])}');`;
|
|
464
|
+
}
|
|
343
465
|
return `await expect(page.getByRole('textbox').first()).not.toBeEmpty();`;
|
|
344
466
|
}
|
|
345
467
|
if (/\b(text|content|label|message|heading)\b/i.test(s)) {
|
|
@@ -413,12 +535,12 @@ function buildSpecFile(norm, pomClassName, pomImportPath, overrides) {
|
|
|
413
535
|
const title = buildTestTitle(norm);
|
|
414
536
|
const steps = norm.steps ?? [];
|
|
415
537
|
const expected = norm.expected ?? [];
|
|
416
|
-
const stepVars = extractStepVars(steps, overrides);
|
|
538
|
+
const stepVars = extractStepVars(steps, norm.step_hints, norm.assertion_hints, overrides);
|
|
417
539
|
let importPath = pomImportPath.replace(/\\/g, "/");
|
|
418
540
|
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
419
541
|
importPath = importPath.replace(/\.ts$/, "");
|
|
420
542
|
const stepLines = steps.length ? steps.map((s, index) => ` ${stepToPlaywright(s, norm.url, norm.step_hints?.[index], stepVars.byStepIndex.get(index))}`).join("\n") : " // TODO: add steps";
|
|
421
|
-
const assertionLines = expected.length ? expected.map((e, index) => ` ${expectedToAssertion(e, norm, norm.assertion_hints?.[index])}`).join("\n") : " // TODO: add assertions";
|
|
543
|
+
const assertionLines = expected.length ? expected.map((e, index) => ` ${expectedToAssertion(e, norm, norm.assertion_hints?.[index], stepVars)}`).join("\n") : " // TODO: add assertions";
|
|
422
544
|
const reviewNote = norm.needs_review ? `
|
|
423
545
|
// \u26A0\uFE0F Flagged for review \u2014 steps or assertions may need manual refinement
|
|
424
546
|
` : "";
|
|
@@ -507,4 +629,4 @@ export {
|
|
|
507
629
|
gen,
|
|
508
630
|
genCmd
|
|
509
631
|
};
|
|
510
|
-
//# sourceMappingURL=chunk-
|
|
632
|
+
//# sourceMappingURL=chunk-3TBS4PBV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/gen.ts"],"sourcesContent":["import { Command } from 'commander';\nimport fg from 'fast-glob';\nimport { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, basename, relative, resolve } from 'node:path';\n\ntype NormalizedCase = {\n id?: string;\n title: string;\n tags?: string[];\n steps?: string[];\n step_hints?: Array<{ selector?: string }>;\n expected?: string[];\n assertion_hints?: Array<{ playwright?: string }>;\n needs_review?: boolean;\n review_reasons?: string[];\n source?: string;\n url?: string;\n};\n\ntype VarsOverrideMap = Map<string, string>;\n\ntype StepBinding = {\n kind: 'fill' | 'select';\n rawField?: string;\n fieldLabel?: string;\n value?: string;\n};\n\ntype StepVar = {\n envKey: string;\n constName: string;\n fallback: string;\n selectorHint?: string;\n selectorTarget?: string;\n fieldLabel?: string;\n};\n\nfunction escapeForSingleQuotedString(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction escapeForRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\\\/]/g, '\\\\$&');\n}\n\nfunction normalizeUrlForComparison(value?: string): string | undefined {\n if (!value) return undefined;\n try {\n return new URL(value).toString().replace(/\\/$/, '');\n } catch {\n return value.trim().replace(/\\/$/, '');\n }\n}\n\nfunction urlPatternFromAbsoluteUrl(value?: string): string | undefined {\n if (!value) return undefined;\n try {\n const parsed = new URL(value);\n const normalizedPath = `${parsed.pathname}${parsed.search}`.replace(/\\/$/, '') || '/';\n if (normalizedPath === '/') return '\\\\/';\n return normalizedPath.replace(/^\\/+/, '').split('/').map(escapeForRegex).join('\\\\/');\n } catch {\n return undefined;\n }\n}\n\nfunction pageNameToUrlPattern(value: string): string | undefined {\n const cleaned = value\n .replace(/\\b(the|a|an|user)\\b/gi, ' ')\n .replace(/\\b(page|screen)\\b/gi, ' ')\n .trim();\n const tokens = cleaned.split(/[\\s/-]+/).map(token => token.trim()).filter(Boolean);\n if (tokens.length === 0) return undefined;\n return tokens.map(token => escapeForRegex(token.toLowerCase())).join('[-_\\\\/]?');\n}\n\nfunction fieldNameFromSentence(value: string): string | undefined {\n const match =\n value.match(/\\bfor\\s+([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+field\\b/i) ??\n value.match(/\\b([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+field\\b/i) ??\n value.match(/\\b([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+input\\b/i);\n return match?.[1]?.trim();\n}\n\nfunction visibleTextRegexFromPhrase(value: string): string {\n const cleaned = value\n .replace(/[\"']/g, '')\n .replace(/\\b(the|a|an|user|should|must|is|are|be|visible|shown|showing|displayed|present|appears?|rendered)\\b/gi, ' ')\n .replace(/\\b(message|text|content|heading|label)\\b/gi, ' ')\n .trim();\n const tokens = cleaned.split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return '.+';\n return tokens.map(token => escapeForRegex(token)).join('\\\\s+');\n}\n\nfunction sanitizeEnvSegment(value: string): string {\n return value\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n}\n\nfunction normalizeOverrideKey(value: string): string {\n return sanitizeEnvSegment(value.replace(/^CT_VAR_/i, ''));\n}\n\nfunction cleanFieldLabel(value?: string): string | undefined {\n if (!value) return undefined;\n const cleaned = value\n .replace(/[.?!,:;]+$/g, '')\n .replace(/^(?:the|a|an)\\s+/i, '')\n .replace(/\\s+(?:field|input|box|area|dropdown|combobox|select|menu)\\b/gi, '')\n .trim();\n return cleaned || undefined;\n}\n\nfunction isPositionalFieldReference(value?: string): boolean {\n if (!value) return false;\n const cleaned = value.trim().toLowerCase();\n return /^(?:(?:the|a|an)\\s+)?(?:first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|\\d+(?:st|nd|rd|th)?)(?:\\s+\\w+){0,2}\\s+(?:field|input|box|area|dropdown|combobox|select|menu)$/.test(cleaned);\n}\n\nfunction fieldNameToEnvSuffix(value?: string): string | undefined {\n if (!value || isPositionalFieldReference(value)) return undefined;\n const cleaned = value\n .replace(/[.?!,:;]+$/g, '')\n .replace(/^(?:the|a|an)\\s+/i, '')\n .replace(/\\b(?:field|input|box|area|dropdown|combobox|select|menu)\\b/gi, ' ')\n .trim();\n const suffix = sanitizeEnvSegment(cleaned);\n if (!suffix) return undefined;\n if (/^(?:FIELD|INPUT|BOX|AREA|DROPDOWN|COMBOBOX|SELECT|MENU)$/.test(suffix)) return undefined;\n return suffix;\n}\n\nfunction normalizeSelectorTarget(value?: string): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n return trimmed.replace(/^page\\./, '').replace(/\\s+/g, ' ');\n}\n\nfunction parseQuotedLiteral(value?: string): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (trimmed.length < 2) return undefined;\n\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n try {\n return JSON.parse(trimmed);\n } catch {\n return trimmed.slice(1, -1);\n }\n }\n\n if (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\")) {\n return trimmed\n .slice(1, -1)\n .replace(/\\\\\\\\/g, '\\\\')\n .replace(/\\\\'/g, \"'\");\n }\n\n return undefined;\n}\n\nfunction selectorTargetToEnvSuffix(value?: string): string | undefined {\n const selector = normalizeSelectorTarget(value);\n if (!selector) return undefined;\n\n const quotedArg = (pattern: RegExp, group = 2): string | undefined => {\n const match = selector.match(pattern);\n return match?.[group]?.trim();\n };\n\n const locatorArg = quotedArg(/^(?:locator)\\((['\"])(.*?)\\1\\)/i);\n if (locatorArg) {\n const idMatch = locatorArg.match(/#([A-Za-z][\\w-]*)/);\n if (idMatch) return sanitizeEnvSegment(idMatch[1]);\n\n const attrMatch = locatorArg.match(/\\[(?:name|id|data-testid|aria-label)=['\"]?([^'\"\\]]+)['\"]?\\]/i);\n if (attrMatch) return sanitizeEnvSegment(attrMatch[1]);\n\n const classMatch = locatorArg.match(/\\.([A-Za-z][\\w-]*)/);\n if (classMatch) return sanitizeEnvSegment(classMatch[1]);\n\n return sanitizeEnvSegment(locatorArg.replace(/^[#.]+/, ''));\n }\n\n const labelArg =\n quotedArg(/^(?:getByLabel|getByPlaceholder|getByTestId|getByText)\\((['\"])(.*?)\\1/i) ??\n quotedArg(/^getByRole\\((['\"])(?:textbox|searchbox|combobox|spinbutton)\\1,\\s*\\{\\s*name:\\s*(['\"])(.*?)\\2/i, 3);\n if (labelArg) return fieldNameToEnvSuffix(labelArg) ?? sanitizeEnvSegment(labelArg);\n\n return undefined;\n}\n\nfunction isGenericFallbackValue(value?: string): boolean {\n if (!value) return true;\n return /^(?:value|text|input|option|selection|selected value|default)$/i.test(value.trim());\n}\n\nfunction parseStepBinding(step: string): StepBinding | undefined {\n const s = step.trim();\n\n const fillMatch = s.match(/\\b(?:fill|input|write)\\s+(?:in\\s+)?(.+?)\\s+with\\s+(['\"])(.*?)\\2/i);\n if (fillMatch) {\n return {\n kind: 'fill',\n rawField: fillMatch[1].trim(),\n fieldLabel: cleanFieldLabel(fillMatch[1]),\n value: fillMatch[3].trim(),\n };\n }\n\n const fillWithoutValueMatch = s.match(/\\b(?:fill|input|write)\\s+(?:in\\s+)?(.+?)$/i);\n if (fillWithoutValueMatch) {\n return {\n kind: 'fill',\n rawField: fillWithoutValueMatch[1].trim(),\n fieldLabel: cleanFieldLabel(fillWithoutValueMatch[1]),\n };\n }\n\n const enterMatch = s.match(/\\b(?:enter|type)\\s+(['\"])(.*?)\\1\\s+(?:in|into)\\s+(.+)/i);\n if (enterMatch) {\n return {\n kind: 'fill',\n rawField: enterMatch[3].trim(),\n fieldLabel: cleanFieldLabel(enterMatch[3]),\n value: enterMatch[2].trim(),\n };\n }\n\n const enterWithoutValueMatch = s.match(/\\b(?:enter|type)\\s+(?:in|into)\\s+(.+)/i);\n if (enterWithoutValueMatch) {\n return {\n kind: 'fill',\n rawField: enterWithoutValueMatch[1].trim(),\n fieldLabel: cleanFieldLabel(enterWithoutValueMatch[1]),\n };\n }\n\n const selectMatch = s.match(/\\b(?:select|choose|pick)\\s+(['\"])(.*?)\\1\\s+(?:from|in|into|on)\\s+(.+)/i);\n if (selectMatch) {\n return {\n kind: 'select',\n rawField: selectMatch[3].trim(),\n fieldLabel: cleanFieldLabel(selectMatch[3]),\n value: selectMatch[2].trim(),\n };\n }\n\n const selectWithoutValueMatch = s.match(/\\b(?:select|choose|pick)\\s+(.+?)\\s+(?:dropdown|combobox|select|menu)\\b/i);\n if (selectWithoutValueMatch) {\n return {\n kind: 'select',\n rawField: selectWithoutValueMatch[1].trim(),\n fieldLabel: cleanFieldLabel(selectWithoutValueMatch[1]),\n };\n }\n\n return undefined;\n}\n\nfunction resolveStepVarOverride(\n overrides: VarsOverrideMap,\n envKey: string,\n keySuffix: string,\n rawField?: string,\n fieldLabel?: string,\n): string | undefined {\n const candidates = [envKey, keySuffix, rawField, fieldLabel];\n for (const candidate of candidates) {\n if (!candidate) continue;\n const normalized = normalizeOverrideKey(candidate);\n if (normalized && overrides.has(normalized)) return overrides.get(normalized);\n }\n return undefined;\n}\n\nfunction extractStepVars(\n steps: string[],\n stepHints: Array<{ selector?: string }> | undefined,\n assertionHints: Array<{ playwright?: string }> | undefined,\n overrides: VarsOverrideMap,\n): { declarations: StepVar[]; byStepIndex: Map<number, StepVar> } {\n const declarations: StepVar[] = [];\n const byStepIndex = new Map<number, StepVar>();\n const seen = new Map<string, StepVar>();\n let unnamedFillIndex = 0;\n let unnamedSelectIndex = 0;\n\n const assertionFallbacks = new Map<string, string>();\n for (const hint of assertionHints ?? []) {\n const parsed = parseValueAssertionHint(hint?.playwright);\n if (!parsed?.expected) continue;\n if (parsed.target) assertionFallbacks.set(parsed.target, parsed.expected);\n const envSuffix = selectorTargetToEnvSuffix(parsed.target);\n if (envSuffix) assertionFallbacks.set(`CT_VAR_${envSuffix}`, parsed.expected);\n }\n\n for (const [index, step] of steps.entries()) {\n const binding = parseStepBinding(step);\n if (!binding) continue;\n\n const keySuffix =\n fieldNameToEnvSuffix(binding.rawField) ??\n fieldNameToEnvSuffix(binding.fieldLabel) ??\n selectorTargetToEnvSuffix(stepHints?.[index]?.selector) ??\n `${binding.kind === 'select' ? 'SELECT' : 'INPUT'}_${binding.kind === 'select' ? ++unnamedSelectIndex : ++unnamedFillIndex}`;\n const envKey = `CT_VAR_${keySuffix}`;\n const selectorTarget = normalizeSelectorTarget(stepHints?.[index]?.selector);\n const assertionFallback =\n (selectorTarget ? assertionFallbacks.get(selectorTarget) : undefined) ??\n assertionFallbacks.get(envKey);\n\n let stepVar = seen.get(envKey);\n if (!stepVar) {\n const fallbackFromBinding = binding.value?.trim();\n const fallback =\n resolveStepVarOverride(overrides, envKey, keySuffix, binding.rawField, binding.fieldLabel) ??\n ((!fallbackFromBinding || isGenericFallbackValue(fallbackFromBinding)) && assertionFallback\n ? assertionFallback\n : fallbackFromBinding) ??\n assertionFallback ??\n (binding.kind === 'select' ? 'option' : 'value');\n\n stepVar = {\n envKey,\n constName: envKey,\n fallback,\n selectorHint: stepHints?.[index]?.selector,\n selectorTarget,\n fieldLabel: binding.fieldLabel,\n };\n seen.set(envKey, stepVar);\n declarations.push(stepVar);\n }\n\n byStepIndex.set(index, stepVar);\n }\n\n return { declarations, byStepIndex };\n}\n\nfunction buildVariableHeaderComment(stepVars: StepVar[]): string {\n if (stepVars.length === 0) return '';\n const inlineExample = stepVars\n .map(stepVar => `${stepVar.envKey}=${stepVar.fallback}`)\n .join(' ');\n return [\n '// Test variables:',\n '// Set these environment variables before running this spec:',\n '//',\n ...stepVars.map(stepVar => `// ${stepVar.envKey}=your-value`),\n '//',\n '// Or pass them inline:',\n `// ${inlineExample} npx playwright test`,\n '',\n ].join('\\n');\n}\n\nfunction buildVariableDeclarations(stepVars: StepVar[]): string {\n if (stepVars.length === 0) return '';\n return [\n ' // Test variables',\n ...stepVars.map(\n stepVar =>\n ` const ${stepVar.constName} = process.env['${stepVar.envKey}'] ?? '${escapeForSingleQuotedString(stepVar.fallback)}';`,\n ),\n '',\n ].join('\\n');\n}\n\nfunction parseVarsOverride(raw?: string): VarsOverrideMap {\n if (!raw) return new Map();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n console.error('❌ --vars must be a valid JSON object');\n process.exit(1);\n }\n\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n console.error('❌ --vars must be a valid JSON object');\n process.exit(1);\n }\n\n const overrides = new Map<string, string>();\n for (const [key, value] of Object.entries(parsed)) {\n const normalizedKey = normalizeOverrideKey(key);\n if (!normalizedKey) continue;\n overrides.set(normalizedKey, String(value));\n }\n return overrides;\n}\n\n// ─── Step → Playwright action ─────────────────────────────────────────────────\n\nfunction ensureStatement(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return trimmed;\n return trimmed.endsWith(';') ? trimmed : `${trimmed};`;\n}\n\nfunction parseValueAssertionHint(playwright?: string): { target?: string; expected?: string } | undefined {\n const statement = ensureStatement(String(playwright ?? '').trim());\n if (!statement) return undefined;\n\n const match = statement.match(/expect\\(([\\s\\S]+)\\)\\.toHaveValue\\(([\\s\\S]+)\\)\\s*;$/);\n if (!match) return undefined;\n\n return {\n target: normalizeSelectorTarget(match[1]),\n expected: parseQuotedLiteral(match[2]),\n };\n}\n\nfunction findStepVarForTarget(\n target: string | undefined,\n stepVars: { declarations: StepVar[] },\n): StepVar | undefined {\n const normalizedTarget = normalizeSelectorTarget(target);\n if (!normalizedTarget) return undefined;\n\n const bySelector = stepVars.declarations.find(stepVar => stepVar.selectorTarget === normalizedTarget);\n if (bySelector) return bySelector;\n\n const envSuffix = selectorTargetToEnvSuffix(normalizedTarget);\n if (!envSuffix) return undefined;\n\n return stepVars.declarations.find(stepVar => stepVar.envKey === `CT_VAR_${envSuffix}`);\n}\n\nfunction rewriteValueAssertionToUseVariable(\n playwright: string,\n stepVars: { declarations: StepVar[] },\n): string | undefined {\n const parsed = parseValueAssertionHint(playwright);\n if (!parsed?.target) return undefined;\n\n const stepVar = findStepVarForTarget(parsed.target, stepVars);\n if (!stepVar) return undefined;\n\n return ensureStatement(`await expect(page.${parsed.target}).toHaveValue(${stepVar.constName})`);\n}\n\nfunction stepToPlaywright(\n step: string,\n url?: string,\n hint?: { selector?: string },\n stepVar?: StepVar,\n): string {\n const s = step.trim();\n const hintedSelector = hint?.selector ? `page.${hint.selector}` : undefined;\n const valueExpression = (value: string): string =>\n stepVar?.constName ?? `'${escapeForSingleQuotedString(value)}'`;\n\n // Navigate\n if (/^(navigate|go to|open|visit|load)/i.test(s)) {\n const urlMatch = s.match(/https?:\\/\\/[^\\s'\"]+/) || s.match(/[\"']([^\"']+)[\"']/);\n const dest = urlMatch?.[1] ?? urlMatch?.[0] ?? url ?? '/';\n if (\n normalizeUrlForComparison(dest) &&\n normalizeUrlForComparison(dest) === normalizeUrlForComparison(url)\n ) {\n return `// navigation handled by pomPage.goto()`;\n }\n return `await page.goto('${escapeForSingleQuotedString(dest)}');`;\n }\n\n // Fill / type / enter into a field\n // Pattern: \"Fill in <field> with '<value>'\" or \"Enter '<value>' in <field>\"\n const binding = parseStepBinding(s);\n if (binding?.kind === 'fill') {\n const fallbackValue = binding.value ?? stepVar?.fallback ?? 'value';\n if (hintedSelector) return `await ${hintedSelector}.fill(${valueExpression(fallbackValue)});`;\n const field = binding.fieldLabel ?? 'field';\n return `await page.getByLabel('${escapeForSingleQuotedString(field)}').fill(${valueExpression(fallbackValue)});`;\n }\n\n // Click a button\n if (/\\bclick\\b.*(button|btn|submit|sign in|log in|login|register|continue|next|save|confirm)/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/click\\s+(the\\s+)?/i, '').trim();\n return `await page.getByRole('button', { name: '${escapeForSingleQuotedString(name)}' }).click();`;\n }\n\n // Click a link\n if (/\\bclick\\b.*(link|anchor|nav)/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/click\\s+(the\\s+)?/i, '').trim();\n return `await page.getByRole('link', { name: '${escapeForSingleQuotedString(name)}' }).click();`;\n }\n\n // Generic click\n if (/^(click|press|tap)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (nameMatch) return `await page.getByText('${escapeForSingleQuotedString(nameMatch[1])}').click();`;\n const target = s.replace(/^(click|press|tap)\\s+(on\\s+)?/i, '').trim();\n return `await page.getByText('${escapeForSingleQuotedString(target)}').click();`;\n }\n\n // Select dropdown\n if (binding?.kind === 'select') {\n const fallbackValue = binding.value ?? stepVar?.fallback ?? 'option';\n if (hintedSelector) return `await ${hintedSelector}.selectOption(${valueExpression(fallbackValue)});`;\n if (binding.fieldLabel) {\n return `await page.getByLabel('${escapeForSingleQuotedString(binding.fieldLabel)}').selectOption(${valueExpression(fallbackValue)});`;\n }\n return `await page.getByRole('combobox').selectOption(${valueExpression(fallbackValue)});`;\n }\n\n // Check / uncheck\n if (/\\b(uncheck|untick|disable)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.uncheck();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/uncheck|untick|disable/gi, '').trim();\n return `await page.getByLabel('${escapeForSingleQuotedString(name)}').uncheck();`;\n }\n if (/\\b(check|tick|enable)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.check();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/check|tick|enable/gi, '').trim();\n return `await page.getByLabel('${escapeForSingleQuotedString(name)}').check();`;\n }\n\n // Keyboard key\n if (/press.*(enter|tab|escape|esc|space|backspace)/i.test(s)) {\n const keyMatch = s.match(/enter|tab|escape|esc|space|backspace/i);\n const key = (keyMatch?.[0] ?? 'Enter');\n return `await page.keyboard.press('${key.charAt(0).toUpperCase() + key.slice(1).toLowerCase()}');`;\n }\n\n // Reload\n if (/\\b(reload|refresh)\\b/i.test(s)) {\n return `await page.reload();`;\n }\n\n // Hover\n if (/\\bhover\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.hover();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/hover\\s+(over\\s+)?/i, '').trim();\n return `await page.getByText('${escapeForSingleQuotedString(name)}').hover();`;\n }\n\n // Scroll\n if (/\\bscroll\\b/i.test(s)) {\n return `await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));`;\n }\n\n // Fallback — preserve the step as a comment so the file is still valid\n return `// TODO: map to Playwright action → \"${s}\"`;\n}\n\n// ─── Expected result → assertion ──────────────────────────────────────────────\n\nfunction expectedToAssertion(\n expected: string,\n norm?: NormalizedCase,\n hint?: { playwright?: string },\n stepVars?: { declarations: StepVar[] },\n): string {\n if (hint?.playwright) {\n const rewritten = stepVars ? rewriteValueAssertionToUseVariable(hint.playwright, stepVars) : undefined;\n return rewritten ?? ensureStatement(hint.playwright);\n }\n const s = expected.trim();\n const fieldName = fieldNameFromSentence(s);\n const currentUrlPattern = urlPatternFromAbsoluteUrl(norm?.url);\n\n // Cleared / empty field\n if (/\\b(clear|cleared|empty|blank)\\b/i.test(s) && /\\b(field|input|value)\\b/i.test(s) && fieldName) {\n return `await expect(page.getByLabel('${escapeForSingleQuotedString(fieldName)}')).toHaveValue('');`;\n }\n\n // Stayed on the same page / did not submit\n if (/\\bform\\b.*\\b(?:does not submit|doesn't submit|not submit|not submitted)\\b/i.test(s)) {\n const fallbackPattern = currentUrlPattern || 'login';\n return `await expect(page).toHaveURL(/${fallbackPattern}/); // form did not navigate`;\n }\n if (/\\b(remains?|stays?|still)\\s+on\\b/i.test(s)) {\n const pageMatch = s.match(/\\b(?:remains?|stays?|still)\\s+on\\s+(?:the\\s+)?(.+?)(?:\\s+page|\\s+screen)?$/i);\n const pagePattern = pageMatch?.[1] ? pageNameToUrlPattern(pageMatch[1]) : undefined;\n return `await expect(page).toHaveURL(/${pagePattern || currentUrlPattern || 'login'}/);`;\n }\n\n // URL / redirect check\n if (/\\b(url|redirect(?:s|ed)?|navigate(?:s|d)?|route|path)\\b/i.test(s)) {\n const pathMatch =\n s.match(/[\"'](\\/[^\"']+)[\"']/) ??\n s.match(/to\\s+(\\/[\\w/-]+)/i) ??\n s.match(/https?:\\/\\/[^\\s'\"]+/i);\n const matchedPath = pathMatch?.[1] ?? pathMatch?.[0];\n if (matchedPath) {\n const fromUrl = matchedPath.startsWith('http') ? urlPatternFromAbsoluteUrl(matchedPath) : undefined;\n const directPath = matchedPath.startsWith('/') ? matchedPath.replace(/^\\/+/, '').split('/').map(escapeForRegex).join('\\\\/') : undefined;\n const finalPattern = fromUrl ?? directPath ?? currentUrlPattern;\n if (finalPattern !== undefined) return `await expect(page).toHaveURL(/${finalPattern}/);`;\n }\n return `await expect(page).toHaveURL(/dashboard|success|home/);`;\n }\n\n // Page title\n if (/\\bpage title\\b|\\btitle should\\b|\\bdocument title\\b/i.test(s)) {\n const titleMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (titleMatch) return `await expect(page).toHaveTitle('${escapeForSingleQuotedString(titleMatch[1])}');`;\n return `await expect(page).toHaveTitle(/.+/);`;\n }\n\n // Error / validation message\n if (/\\b(error|invalid|fail|incorrect|required|validation)\\b/i.test(s)) {\n const msgMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (msgMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(msgMatch[1])}')).toBeVisible();`;\n if (fieldName) return `await expect(page.getByText(/${escapeForRegex(fieldName)}/i)).toBeVisible();`;\n return `await expect(page.getByRole('alert')).toBeVisible();`;\n }\n\n // Success / confirmation\n if (/\\b(success|confirm|complete|thank|welcome|sent|saved)\\b/i.test(s)) {\n const msgMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (msgMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(msgMatch[1])}')).toBeVisible();`;\n if (/\\bwelcome\\b/i.test(s)) return `await expect(page.getByText(/welcome/i)).toBeVisible();`;\n return `await expect(page.getByRole('status')).toBeVisible();`;\n }\n\n // Not visible / hidden\n if (/\\b(not visible|hidden|disappear|removed|gone)\\b/i.test(s)) {\n const elementMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (elementMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(elementMatch[1])}')).not.toBeVisible();`;\n return `await expect(page.locator('.modal, [role=\"dialog\"]').first()).not.toBeVisible();`;\n }\n\n // Visible / present\n if (/\\b(visible|appear|display|show|render|present)\\b/i.test(s)) {\n const elementMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (elementMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(elementMatch[1])}')).toBeVisible();`;\n const subjectMatch =\n s.match(/^(?:the\\s+)?(.+?)\\s+(?:is|are|should|must|becomes?|appears?|renders?|shows?|displays?)\\b/i) ??\n s.match(/^(?:the\\s+)?(.+?)\\s+(?:visible|present)\\b/i);\n const subject = subjectMatch?.[1]?.trim();\n if (subject) {\n if (/\\bnavigation\\b.*\\bmenu\\b|\\bmenu\\b.*\\bnavigation\\b|\\bnavigation\\b/i.test(subject)) {\n return `await expect(page.getByRole('navigation')).toBeVisible();`;\n }\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(subject)}/i)).toBeVisible();`;\n }\n return `await expect(page.locator('[data-testid]').first()).toBeVisible();`;\n }\n\n // Count of items\n if (/\\b(count|number of|list of|\\d+\\s+item)\\b/i.test(s)) {\n const countMatch = s.match(/(\\d+)/);\n if (countMatch) return `await expect(page.locator('li, tr, [role=\"listitem\"]')).toHaveCount(${countMatch[1]});`;\n return `await expect(page.locator('li, tr').first()).toBeVisible();`;\n }\n\n // Enabled / disabled\n if (/\\b(enabled|clickable|active)\\b/i.test(s)) {\n return `await expect(page.getByRole('button').first()).toBeEnabled();`;\n }\n if (/\\b(disabled|inactive)\\b/i.test(s)) {\n return `await expect(page.getByRole('button').first()).toBeDisabled();`;\n }\n\n // Input value\n if (/\\b(clear|cleared|empty|blank)\\b/i.test(s) && /\\b(field|input|value)\\b/i.test(s)) {\n return `await expect(page.getByRole('textbox').first()).toHaveValue('');`;\n }\n if (/\\b(field|input|value|filled)\\b/i.test(s)) {\n const valMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (valMatch) {\n const envSuffix = fieldNameToEnvSuffix(fieldName);\n const stepVar = envSuffix ? stepVars?.declarations.find(candidate => candidate.envKey === `CT_VAR_${envSuffix}`) : undefined;\n if (stepVar) return `await expect(page.getByRole('textbox').first()).toHaveValue(${stepVar.constName});`;\n return `await expect(page.getByRole('textbox').first()).toHaveValue('${escapeForSingleQuotedString(valMatch[1])}');`;\n }\n return `await expect(page.getByRole('textbox').first()).not.toBeEmpty();`;\n }\n\n // Text / heading / content (broad fallback before final fallback)\n if (/\\b(text|content|label|message|heading)\\b/i.test(s)) {\n const textMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (textMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(textMatch[1])}')).toBeVisible();`;\n const words = s.replace(/\\b(the|should|must|contain|display|show|have|text|content)\\b/gi, '').trim();\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(words)}/i)).toBeVisible();`;\n }\n\n // Final fallback\n const cleaned = s.replace(/[\"']/g, '').trim();\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(cleaned)}/i)).toBeVisible(); // TODO: refine assertion`;\n}\n\n// ─── POM class name / filename ────────────────────────────────────────────────\n\nfunction toIdentifier(value: string, fallback = 'Landing'): string {\n const tokens = value\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .split(/[^a-zA-Z0-9_]+/)\n .filter(Boolean);\n\n const identifier = tokens\n .map((token) => token.charAt(0).toUpperCase() + token.slice(1))\n .join('');\n\n if (!identifier) return fallback;\n return /^[0-9]/.test(identifier) ? `Case${identifier}` : identifier;\n}\n\nfunction derivePomClassName(norm: NormalizedCase): string {\n const raw = norm.id ?? norm.title ?? 'Landing';\n const withoutNum = raw.replace(/-\\d+$/, '');\n return `${toIdentifier(withoutNum)}Page`;\n}\n\n// ─── POM class source ─────────────────────────────────────────────────────────\n\nfunction buildPomClass(className: string, norm: NormalizedCase, lang: 'ts' | 'js'): string {\n const pageUrl = norm.url ?? '/';\n const isTs = lang === 'ts';\n\n return isTs\n ? `import { Page } from '@playwright/test';\n\nexport class ${className} {\n readonly page: Page;\n\n constructor(page: Page) {\n this.page = page;\n }\n\n async goto(): Promise<void> {\n await this.page.goto('${escapeForSingleQuotedString(pageUrl)}');\n }\n\n async waitForLoad(): Promise<void> {\n await this.page.waitForLoadState('domcontentloaded');\n }\n}\n`\n : `// @ts-check\nexport class ${className} {\n /** @param {import('@playwright/test').Page} page */\n constructor(page) {\n /** @type {import('@playwright/test').Page} */\n this.page = page;\n }\n\n async goto() {\n await this.page.goto('${escapeForSingleQuotedString(pageUrl)}');\n }\n\n async waitForLoad() {\n await this.page.waitForLoadState('domcontentloaded');\n }\n}\n`;\n}\n\n// ─── Spec file source ─────────────────────────────────────────────────────────\n\nfunction buildTestTitle(norm: NormalizedCase): string {\n const idPart = norm.id ?? '';\n\n // norm.title may already include the ID prefix (e.g. \"AUTH-001 — User can log in\")\n // Strip it to avoid \"AUTH-001 — AUTH-001 — User can log in\" in the test name.\n let cleanTitle = norm.title || 'Untitled';\n if (idPart && cleanTitle.startsWith(idPart)) {\n cleanTitle = cleanTitle.slice(idPart.length).replace(/^\\s*[—\\-–]+\\s*/, '').trim();\n }\n\n const tagSuffix = (norm.tags ?? []).map(t => `@${t}`).join(' ');\n return [idPart, cleanTitle, tagSuffix].filter(Boolean).join(' — ').trim();\n}\n\nfunction buildSpecFile(\n norm: NormalizedCase,\n pomClassName: string,\n pomImportPath: string,\n overrides: VarsOverrideMap,\n): string {\n const title = buildTestTitle(norm);\n const steps = norm.steps ?? [];\n const expected = norm.expected ?? [];\n const stepVars = extractStepVars(steps, norm.step_hints, norm.assertion_hints, overrides);\n\n let importPath = pomImportPath.replace(/\\\\/g, '/');\n if (!importPath.startsWith('.')) importPath = `./${importPath}`;\n // Strip .ts extension — TypeScript resolves without it\n importPath = importPath.replace(/\\.ts$/, '');\n\n const stepLines = steps.length\n ? steps\n .map((s, index) => ` ${stepToPlaywright(s, norm.url, norm.step_hints?.[index], stepVars.byStepIndex.get(index))}`)\n .join('\\n')\n : ' // TODO: add steps';\n\n const assertionLines = expected.length\n ? expected.map((e, index) => ` ${expectedToAssertion(e, norm, norm.assertion_hints?.[index], stepVars)}`).join('\\n')\n : ' // TODO: add assertions';\n\n const reviewNote = norm.needs_review\n ? `\\n // ⚠️ Flagged for review — steps or assertions may need manual refinement\\n`\n : '';\n const variableHeader = buildVariableHeaderComment(stepVars.declarations);\n const variableDeclarations = buildVariableDeclarations(stepVars.declarations);\n\n return `import { test, expect } from '@playwright/test';\nimport { ${pomClassName} } from '${importPath}';\n\n${variableHeader}test('${title}', async ({ page }) => {${reviewNote}\n const pomPage = new ${pomClassName}(page);\n\n${variableDeclarations} // ── Setup ─────────────────────────────────────────────────────────────────\n await pomPage.goto();\n await pomPage.waitForLoad();\n\n // ── Steps ─────────────────────────────────────────────────────────────────\n${stepLines}\n\n // ── Assertions ────────────────────────────────────────────────────────────\n${assertionLines}\n});\n`;\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport async function gen(opts: { lang: string; out: string; vars?: string }) {\n if (opts.lang !== 'ts' && opts.lang !== 'js') {\n console.error('❌ --lang must be ts or js');\n process.exit(1);\n }\n const lang = opts.lang as 'ts' | 'js';\n const specExt = lang === 'js' ? 'spec.js' : 'spec.ts';\n const pomExt = lang === 'js' ? '.js' : '.ts';\n const varsOverride = parseVarsOverride(opts.vars);\n\n const normalized = await fg([\n '.cementic/normalized/*.json',\n '!.cementic/normalized/_index.json',\n ]);\n\n if (normalized.length === 0) {\n console.warn('⚠️ No normalized cases found in .cementic/normalized/');\n console.warn(' Run: ct normalize ./cases');\n process.exit(1);\n }\n\n const projectRoot = process.cwd();\n const testsOutDir = resolve(projectRoot, opts.out);\n const pagesOutDir = resolve(projectRoot, 'pages');\n\n mkdirSync(testsOutDir, { recursive: true });\n mkdirSync(pagesOutDir, { recursive: true });\n\n let specCount = 0;\n let pomCount = 0;\n\n for (const f of normalized) {\n const norm = JSON.parse(readFileSync(f, 'utf8')) as NormalizedCase;\n\n const pomClassName = derivePomClassName(norm);\n const pomFileName = pomClassName + pomExt;\n const pomFilePath = join(pagesOutDir, pomFileName);\n\n // Only write a POM file if one doesn't exist yet — never overwrite user edits\n if (!existsSync(pomFilePath)) {\n writeFileSync(pomFilePath, buildPomClass(pomClassName, norm, lang));\n pomCount++;\n }\n\n // Relative import from the test file location to the POM file\n const relToPages = relative(testsOutDir, pagesOutDir);\n const pomImportPath = join(relToPages, pomFileName);\n\n const stem = basename(f).replace(/\\.json$/, '').replace(/[^\\w-]+/g, '-');\n const specPath = join(testsOutDir, `${stem}.${specExt}`);\n writeFileSync(specPath, buildSpecFile(norm, pomClassName, pomImportPath, varsOverride));\n specCount++;\n }\n\n console.log(`✅ Generated ${specCount} spec file(s) → ${opts.out}/`);\n if (pomCount > 0) {\n console.log(`✅ Generated ${pomCount} POM class file(s) → pages/`);\n }\n if (pomCount === 0 && specCount > 0) {\n console.log(`ℹ️ POM classes already exist — skipped regeneration`);\n }\n}\n\nexport function genCmd() {\n const cmd = new Command('gen')\n .description('Generate Playwright POM spec + page-object files from normalized cases')\n .addHelpText('after', `\nExamples:\n $ ct gen --lang ts\n $ ct gen --lang js --out tests/e2e\n $ ct gen --lang ts --vars '{\"email\":\"admin@example.com\"}'\n`)\n .option('--lang <lang>', 'Target language (ts|js)', 'ts')\n .option('--out <dir>', 'Output directory for spec files', 'tests/generated')\n .option('--vars <json>', 'JSON object of fallback values for generated CT_VAR_* constants')\n .action(async opts => {\n await gen({ lang: opts.lang, out: opts.out, vars: opts.vars });\n });\n return cmd;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
genCmd
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3TBS4PBV.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command as Command9 } from "commander";
|
|
@@ -321,7 +321,7 @@ Examples:
|
|
|
321
321
|
}
|
|
322
322
|
console.log(`\u2705 Normalized ${index.summary.parsed} case(s). Output \u2192 .cementic/normalized/`);
|
|
323
323
|
if (opts.andGen) {
|
|
324
|
-
const { gen } = await import("./gen-
|
|
324
|
+
const { gen } = await import("./gen-YF22KYYU.js");
|
|
325
325
|
await gen({ lang: opts.lang || "ts", out: "tests/generated" });
|
|
326
326
|
}
|
|
327
327
|
});
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/gen.ts"],"sourcesContent":["import { Command } from 'commander';\nimport fg from 'fast-glob';\nimport { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, basename, relative, resolve } from 'node:path';\n\ntype NormalizedCase = {\n id?: string;\n title: string;\n tags?: string[];\n steps?: string[];\n step_hints?: Array<{ selector?: string }>;\n expected?: string[];\n assertion_hints?: Array<{ playwright?: string }>;\n needs_review?: boolean;\n review_reasons?: string[];\n source?: string;\n url?: string;\n};\n\ntype VarsOverrideMap = Map<string, string>;\n\ntype StepBinding = {\n kind: 'fill' | 'select';\n rawField?: string;\n fieldLabel?: string;\n value: string;\n};\n\ntype StepVar = {\n envKey: string;\n constName: string;\n fallback: string;\n};\n\nfunction escapeForSingleQuotedString(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction escapeForRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\\\/]/g, '\\\\$&');\n}\n\nfunction normalizeUrlForComparison(value?: string): string | undefined {\n if (!value) return undefined;\n try {\n return new URL(value).toString().replace(/\\/$/, '');\n } catch {\n return value.trim().replace(/\\/$/, '');\n }\n}\n\nfunction urlPatternFromAbsoluteUrl(value?: string): string | undefined {\n if (!value) return undefined;\n try {\n const parsed = new URL(value);\n const normalizedPath = `${parsed.pathname}${parsed.search}`.replace(/\\/$/, '') || '/';\n if (normalizedPath === '/') return '\\\\/';\n return normalizedPath.replace(/^\\/+/, '').split('/').map(escapeForRegex).join('\\\\/');\n } catch {\n return undefined;\n }\n}\n\nfunction pageNameToUrlPattern(value: string): string | undefined {\n const cleaned = value\n .replace(/\\b(the|a|an|user)\\b/gi, ' ')\n .replace(/\\b(page|screen)\\b/gi, ' ')\n .trim();\n const tokens = cleaned.split(/[\\s/-]+/).map(token => token.trim()).filter(Boolean);\n if (tokens.length === 0) return undefined;\n return tokens.map(token => escapeForRegex(token.toLowerCase())).join('[-_\\\\/]?');\n}\n\nfunction fieldNameFromSentence(value: string): string | undefined {\n const match =\n value.match(/\\bfor\\s+([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+field\\b/i) ??\n value.match(/\\b([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+field\\b/i) ??\n value.match(/\\b([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+input\\b/i);\n return match?.[1]?.trim();\n}\n\nfunction visibleTextRegexFromPhrase(value: string): string {\n const cleaned = value\n .replace(/[\"']/g, '')\n .replace(/\\b(the|a|an|user|should|must|is|are|be|visible|shown|showing|displayed|present|appears?|rendered)\\b/gi, ' ')\n .replace(/\\b(message|text|content|heading|label)\\b/gi, ' ')\n .trim();\n const tokens = cleaned.split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return '.+';\n return tokens.map(token => escapeForRegex(token)).join('\\\\s+');\n}\n\nfunction sanitizeEnvSegment(value: string): string {\n return value\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n}\n\nfunction normalizeOverrideKey(value: string): string {\n return sanitizeEnvSegment(value.replace(/^CT_VAR_/i, ''));\n}\n\nfunction cleanFieldLabel(value?: string): string | undefined {\n if (!value) return undefined;\n const cleaned = value\n .replace(/[.?!,:;]+$/g, '')\n .replace(/^(?:the|a|an)\\s+/i, '')\n .replace(/\\s+(?:field|input|box|area|dropdown|combobox|select|menu)\\b/gi, '')\n .trim();\n return cleaned || undefined;\n}\n\nfunction isPositionalFieldReference(value?: string): boolean {\n if (!value) return false;\n const cleaned = value.trim().toLowerCase();\n return /^(?:(?:the|a|an)\\s+)?(?:first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|\\d+(?:st|nd|rd|th)?)(?:\\s+\\w+){0,2}\\s+(?:field|input|box|area|dropdown|combobox|select|menu)$/.test(cleaned);\n}\n\nfunction fieldNameToEnvSuffix(value?: string): string | undefined {\n if (!value || isPositionalFieldReference(value)) return undefined;\n const cleaned = value\n .replace(/[.?!,:;]+$/g, '')\n .replace(/^(?:the|a|an)\\s+/i, '')\n .replace(/\\b(?:field|input|box|area|dropdown|combobox|select|menu)\\b/gi, ' ')\n .trim();\n const suffix = sanitizeEnvSegment(cleaned);\n if (!suffix) return undefined;\n if (/^(?:FIELD|INPUT|BOX|AREA|DROPDOWN|COMBOBOX|SELECT|MENU)$/.test(suffix)) return undefined;\n return suffix;\n}\n\nfunction parseStepBinding(step: string): StepBinding | undefined {\n const s = step.trim();\n\n const fillMatch = s.match(/\\b(?:fill|input|write)\\s+(?:in\\s+)?(.+?)\\s+with\\s+(['\"])(.*?)\\2/i);\n if (fillMatch) {\n return {\n kind: 'fill',\n rawField: fillMatch[1].trim(),\n fieldLabel: cleanFieldLabel(fillMatch[1]),\n value: fillMatch[3].trim(),\n };\n }\n\n const enterMatch = s.match(/\\b(?:enter|type)\\s+(['\"])(.*?)\\1\\s+(?:in|into)\\s+(.+)/i);\n if (enterMatch) {\n return {\n kind: 'fill',\n rawField: enterMatch[3].trim(),\n fieldLabel: cleanFieldLabel(enterMatch[3]),\n value: enterMatch[2].trim(),\n };\n }\n\n const selectMatch = s.match(/\\b(?:select|choose|pick)\\s+(['\"])(.*?)\\1\\s+(?:from|in|into|on)\\s+(.+)/i);\n if (selectMatch) {\n return {\n kind: 'select',\n rawField: selectMatch[3].trim(),\n fieldLabel: cleanFieldLabel(selectMatch[3]),\n value: selectMatch[2].trim(),\n };\n }\n\n return undefined;\n}\n\nfunction resolveStepVarOverride(\n overrides: VarsOverrideMap,\n envKey: string,\n keySuffix: string,\n rawField?: string,\n fieldLabel?: string,\n): string | undefined {\n const candidates = [envKey, keySuffix, rawField, fieldLabel];\n for (const candidate of candidates) {\n if (!candidate) continue;\n const normalized = normalizeOverrideKey(candidate);\n if (normalized && overrides.has(normalized)) return overrides.get(normalized);\n }\n return undefined;\n}\n\nfunction extractStepVars(\n steps: string[],\n overrides: VarsOverrideMap,\n): { declarations: StepVar[]; byStepIndex: Map<number, StepVar> } {\n const declarations: StepVar[] = [];\n const byStepIndex = new Map<number, StepVar>();\n const seen = new Map<string, StepVar>();\n let unnamedFillIndex = 0;\n let unnamedSelectIndex = 0;\n\n for (const [index, step] of steps.entries()) {\n const binding = parseStepBinding(step);\n if (!binding) continue;\n\n const keySuffix =\n fieldNameToEnvSuffix(binding.rawField) ??\n `${binding.kind === 'select' ? 'SELECT' : 'INPUT'}_${binding.kind === 'select' ? ++unnamedSelectIndex : ++unnamedFillIndex}`;\n const envKey = `CT_VAR_${keySuffix}`;\n\n let stepVar = seen.get(envKey);\n if (!stepVar) {\n stepVar = {\n envKey,\n constName: envKey,\n fallback:\n resolveStepVarOverride(overrides, envKey, keySuffix, binding.rawField, binding.fieldLabel) ??\n binding.value,\n };\n seen.set(envKey, stepVar);\n declarations.push(stepVar);\n }\n\n byStepIndex.set(index, stepVar);\n }\n\n return { declarations, byStepIndex };\n}\n\nfunction buildVariableHeaderComment(stepVars: StepVar[]): string {\n if (stepVars.length === 0) return '';\n const inlineExample = stepVars\n .map(stepVar => `${stepVar.envKey}=${stepVar.fallback}`)\n .join(' ');\n return [\n '// Test variables:',\n '// Set these environment variables before running this spec:',\n '//',\n ...stepVars.map(stepVar => `// ${stepVar.envKey}=your-value`),\n '//',\n '// Or pass them inline:',\n `// ${inlineExample} npx playwright test`,\n '',\n ].join('\\n');\n}\n\nfunction buildVariableDeclarations(stepVars: StepVar[]): string {\n if (stepVars.length === 0) return '';\n return [\n ' // Test variables',\n ...stepVars.map(\n stepVar =>\n ` const ${stepVar.constName} = process.env['${stepVar.envKey}'] ?? '${escapeForSingleQuotedString(stepVar.fallback)}';`,\n ),\n '',\n ].join('\\n');\n}\n\nfunction parseVarsOverride(raw?: string): VarsOverrideMap {\n if (!raw) return new Map();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n console.error('❌ --vars must be a valid JSON object');\n process.exit(1);\n }\n\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n console.error('❌ --vars must be a valid JSON object');\n process.exit(1);\n }\n\n const overrides = new Map<string, string>();\n for (const [key, value] of Object.entries(parsed)) {\n const normalizedKey = normalizeOverrideKey(key);\n if (!normalizedKey) continue;\n overrides.set(normalizedKey, String(value));\n }\n return overrides;\n}\n\n// ─── Step → Playwright action ─────────────────────────────────────────────────\n\nfunction ensureStatement(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return trimmed;\n return trimmed.endsWith(';') ? trimmed : `${trimmed};`;\n}\n\nfunction stepToPlaywright(\n step: string,\n url?: string,\n hint?: { selector?: string },\n stepVar?: StepVar,\n): string {\n const s = step.trim();\n const hintedSelector = hint?.selector ? `page.${hint.selector}` : undefined;\n const valueExpression = (value: string): string =>\n stepVar?.constName ?? `'${escapeForSingleQuotedString(value)}'`;\n\n // Navigate\n if (/^(navigate|go to|open|visit|load)/i.test(s)) {\n const urlMatch = s.match(/https?:\\/\\/[^\\s'\"]+/) || s.match(/[\"']([^\"']+)[\"']/);\n const dest = urlMatch?.[1] ?? urlMatch?.[0] ?? url ?? '/';\n if (\n normalizeUrlForComparison(dest) &&\n normalizeUrlForComparison(dest) === normalizeUrlForComparison(url)\n ) {\n return `// navigation handled by pomPage.goto()`;\n }\n return `await page.goto('${escapeForSingleQuotedString(dest)}');`;\n }\n\n // Fill / type / enter into a field\n // Pattern: \"Fill in <field> with '<value>'\" or \"Enter '<value>' in <field>\"\n const binding = parseStepBinding(s);\n if (binding?.kind === 'fill') {\n if (hintedSelector) return `await ${hintedSelector}.fill(${valueExpression(binding.value)});`;\n const field = binding.fieldLabel ?? 'field';\n return `await page.getByLabel('${escapeForSingleQuotedString(field)}').fill(${valueExpression(binding.value)});`;\n }\n\n // Click a button\n if (/\\bclick\\b.*(button|btn|submit|sign in|log in|login|register|continue|next|save|confirm)/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/click\\s+(the\\s+)?/i, '').trim();\n return `await page.getByRole('button', { name: '${escapeForSingleQuotedString(name)}' }).click();`;\n }\n\n // Click a link\n if (/\\bclick\\b.*(link|anchor|nav)/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/click\\s+(the\\s+)?/i, '').trim();\n return `await page.getByRole('link', { name: '${escapeForSingleQuotedString(name)}' }).click();`;\n }\n\n // Generic click\n if (/^(click|press|tap)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (nameMatch) return `await page.getByText('${escapeForSingleQuotedString(nameMatch[1])}').click();`;\n const target = s.replace(/^(click|press|tap)\\s+(on\\s+)?/i, '').trim();\n return `await page.getByText('${escapeForSingleQuotedString(target)}').click();`;\n }\n\n // Select dropdown\n if (binding?.kind === 'select') {\n if (hintedSelector) return `await ${hintedSelector}.selectOption(${valueExpression(binding.value)});`;\n if (binding.fieldLabel) {\n return `await page.getByLabel('${escapeForSingleQuotedString(binding.fieldLabel)}').selectOption(${valueExpression(binding.value)});`;\n }\n return `await page.getByRole('combobox').selectOption(${valueExpression(binding.value)});`;\n }\n\n // Check / uncheck\n if (/\\b(uncheck|untick|disable)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.uncheck();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/uncheck|untick|disable/gi, '').trim();\n return `await page.getByLabel('${escapeForSingleQuotedString(name)}').uncheck();`;\n }\n if (/\\b(check|tick|enable)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.check();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/check|tick|enable/gi, '').trim();\n return `await page.getByLabel('${escapeForSingleQuotedString(name)}').check();`;\n }\n\n // Keyboard key\n if (/press.*(enter|tab|escape|esc|space|backspace)/i.test(s)) {\n const keyMatch = s.match(/enter|tab|escape|esc|space|backspace/i);\n const key = (keyMatch?.[0] ?? 'Enter');\n return `await page.keyboard.press('${key.charAt(0).toUpperCase() + key.slice(1).toLowerCase()}');`;\n }\n\n // Reload\n if (/\\b(reload|refresh)\\b/i.test(s)) {\n return `await page.reload();`;\n }\n\n // Hover\n if (/\\bhover\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.hover();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/hover\\s+(over\\s+)?/i, '').trim();\n return `await page.getByText('${escapeForSingleQuotedString(name)}').hover();`;\n }\n\n // Scroll\n if (/\\bscroll\\b/i.test(s)) {\n return `await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));`;\n }\n\n // Fallback — preserve the step as a comment so the file is still valid\n return `// TODO: map to Playwright action → \"${s}\"`;\n}\n\n// ─── Expected result → assertion ──────────────────────────────────────────────\n\nfunction expectedToAssertion(\n expected: string,\n norm?: NormalizedCase,\n hint?: { playwright?: string },\n): string {\n if (hint?.playwright) return ensureStatement(hint.playwright);\n const s = expected.trim();\n const fieldName = fieldNameFromSentence(s);\n const currentUrlPattern = urlPatternFromAbsoluteUrl(norm?.url);\n\n // Cleared / empty field\n if (/\\b(clear|cleared|empty|blank)\\b/i.test(s) && /\\b(field|input|value)\\b/i.test(s) && fieldName) {\n return `await expect(page.getByLabel('${escapeForSingleQuotedString(fieldName)}')).toHaveValue('');`;\n }\n\n // Stayed on the same page / did not submit\n if (/\\bform\\b.*\\b(?:does not submit|doesn't submit|not submit|not submitted)\\b/i.test(s)) {\n const fallbackPattern = currentUrlPattern || 'login';\n return `await expect(page).toHaveURL(/${fallbackPattern}/); // form did not navigate`;\n }\n if (/\\b(remains?|stays?|still)\\s+on\\b/i.test(s)) {\n const pageMatch = s.match(/\\b(?:remains?|stays?|still)\\s+on\\s+(?:the\\s+)?(.+?)(?:\\s+page|\\s+screen)?$/i);\n const pagePattern = pageMatch?.[1] ? pageNameToUrlPattern(pageMatch[1]) : undefined;\n return `await expect(page).toHaveURL(/${pagePattern || currentUrlPattern || 'login'}/);`;\n }\n\n // URL / redirect check\n if (/\\b(url|redirect(?:s|ed)?|navigate(?:s|d)?|route|path)\\b/i.test(s)) {\n const pathMatch =\n s.match(/[\"'](\\/[^\"']+)[\"']/) ??\n s.match(/to\\s+(\\/[\\w/-]+)/i) ??\n s.match(/https?:\\/\\/[^\\s'\"]+/i);\n const matchedPath = pathMatch?.[1] ?? pathMatch?.[0];\n if (matchedPath) {\n const fromUrl = matchedPath.startsWith('http') ? urlPatternFromAbsoluteUrl(matchedPath) : undefined;\n const directPath = matchedPath.startsWith('/') ? matchedPath.replace(/^\\/+/, '').split('/').map(escapeForRegex).join('\\\\/') : undefined;\n const finalPattern = fromUrl ?? directPath ?? currentUrlPattern;\n if (finalPattern !== undefined) return `await expect(page).toHaveURL(/${finalPattern}/);`;\n }\n return `await expect(page).toHaveURL(/dashboard|success|home/);`;\n }\n\n // Page title\n if (/\\bpage title\\b|\\btitle should\\b|\\bdocument title\\b/i.test(s)) {\n const titleMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (titleMatch) return `await expect(page).toHaveTitle('${escapeForSingleQuotedString(titleMatch[1])}');`;\n return `await expect(page).toHaveTitle(/.+/);`;\n }\n\n // Error / validation message\n if (/\\b(error|invalid|fail|incorrect|required|validation)\\b/i.test(s)) {\n const msgMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (msgMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(msgMatch[1])}')).toBeVisible();`;\n if (fieldName) return `await expect(page.getByText(/${escapeForRegex(fieldName)}/i)).toBeVisible();`;\n return `await expect(page.getByRole('alert')).toBeVisible();`;\n }\n\n // Success / confirmation\n if (/\\b(success|confirm|complete|thank|welcome|sent|saved)\\b/i.test(s)) {\n const msgMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (msgMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(msgMatch[1])}')).toBeVisible();`;\n if (/\\bwelcome\\b/i.test(s)) return `await expect(page.getByText(/welcome/i)).toBeVisible();`;\n return `await expect(page.getByRole('status')).toBeVisible();`;\n }\n\n // Not visible / hidden\n if (/\\b(not visible|hidden|disappear|removed|gone)\\b/i.test(s)) {\n const elementMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (elementMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(elementMatch[1])}')).not.toBeVisible();`;\n return `await expect(page.locator('.modal, [role=\"dialog\"]').first()).not.toBeVisible();`;\n }\n\n // Visible / present\n if (/\\b(visible|appear|display|show|render|present)\\b/i.test(s)) {\n const elementMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (elementMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(elementMatch[1])}')).toBeVisible();`;\n const subjectMatch =\n s.match(/^(?:the\\s+)?(.+?)\\s+(?:is|are|should|must|becomes?|appears?|renders?|shows?|displays?)\\b/i) ??\n s.match(/^(?:the\\s+)?(.+?)\\s+(?:visible|present)\\b/i);\n const subject = subjectMatch?.[1]?.trim();\n if (subject) {\n if (/\\bnavigation\\b.*\\bmenu\\b|\\bmenu\\b.*\\bnavigation\\b|\\bnavigation\\b/i.test(subject)) {\n return `await expect(page.getByRole('navigation')).toBeVisible();`;\n }\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(subject)}/i)).toBeVisible();`;\n }\n return `await expect(page.locator('[data-testid]').first()).toBeVisible();`;\n }\n\n // Count of items\n if (/\\b(count|number of|list of|\\d+\\s+item)\\b/i.test(s)) {\n const countMatch = s.match(/(\\d+)/);\n if (countMatch) return `await expect(page.locator('li, tr, [role=\"listitem\"]')).toHaveCount(${countMatch[1]});`;\n return `await expect(page.locator('li, tr').first()).toBeVisible();`;\n }\n\n // Enabled / disabled\n if (/\\b(enabled|clickable|active)\\b/i.test(s)) {\n return `await expect(page.getByRole('button').first()).toBeEnabled();`;\n }\n if (/\\b(disabled|inactive)\\b/i.test(s)) {\n return `await expect(page.getByRole('button').first()).toBeDisabled();`;\n }\n\n // Input value\n if (/\\b(clear|cleared|empty|blank)\\b/i.test(s) && /\\b(field|input|value)\\b/i.test(s)) {\n return `await expect(page.getByRole('textbox').first()).toHaveValue('');`;\n }\n if (/\\b(field|input|value|filled)\\b/i.test(s)) {\n const valMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (valMatch) return `await expect(page.getByRole('textbox').first()).toHaveValue('${escapeForSingleQuotedString(valMatch[1])}');`;\n return `await expect(page.getByRole('textbox').first()).not.toBeEmpty();`;\n }\n\n // Text / heading / content (broad fallback before final fallback)\n if (/\\b(text|content|label|message|heading)\\b/i.test(s)) {\n const textMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (textMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(textMatch[1])}')).toBeVisible();`;\n const words = s.replace(/\\b(the|should|must|contain|display|show|have|text|content)\\b/gi, '').trim();\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(words)}/i)).toBeVisible();`;\n }\n\n // Final fallback\n const cleaned = s.replace(/[\"']/g, '').trim();\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(cleaned)}/i)).toBeVisible(); // TODO: refine assertion`;\n}\n\n// ─── POM class name / filename ────────────────────────────────────────────────\n\nfunction toIdentifier(value: string, fallback = 'Landing'): string {\n const tokens = value\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .split(/[^a-zA-Z0-9_]+/)\n .filter(Boolean);\n\n const identifier = tokens\n .map((token) => token.charAt(0).toUpperCase() + token.slice(1))\n .join('');\n\n if (!identifier) return fallback;\n return /^[0-9]/.test(identifier) ? `Case${identifier}` : identifier;\n}\n\nfunction derivePomClassName(norm: NormalizedCase): string {\n const raw = norm.id ?? norm.title ?? 'Landing';\n const withoutNum = raw.replace(/-\\d+$/, '');\n return `${toIdentifier(withoutNum)}Page`;\n}\n\n// ─── POM class source ─────────────────────────────────────────────────────────\n\nfunction buildPomClass(className: string, norm: NormalizedCase, lang: 'ts' | 'js'): string {\n const pageUrl = norm.url ?? '/';\n const isTs = lang === 'ts';\n\n return isTs\n ? `import { Page } from '@playwright/test';\n\nexport class ${className} {\n readonly page: Page;\n\n constructor(page: Page) {\n this.page = page;\n }\n\n async goto(): Promise<void> {\n await this.page.goto('${escapeForSingleQuotedString(pageUrl)}');\n }\n\n async waitForLoad(): Promise<void> {\n await this.page.waitForLoadState('domcontentloaded');\n }\n}\n`\n : `// @ts-check\nexport class ${className} {\n /** @param {import('@playwright/test').Page} page */\n constructor(page) {\n /** @type {import('@playwright/test').Page} */\n this.page = page;\n }\n\n async goto() {\n await this.page.goto('${escapeForSingleQuotedString(pageUrl)}');\n }\n\n async waitForLoad() {\n await this.page.waitForLoadState('domcontentloaded');\n }\n}\n`;\n}\n\n// ─── Spec file source ─────────────────────────────────────────────────────────\n\nfunction buildTestTitle(norm: NormalizedCase): string {\n const idPart = norm.id ?? '';\n\n // norm.title may already include the ID prefix (e.g. \"AUTH-001 — User can log in\")\n // Strip it to avoid \"AUTH-001 — AUTH-001 — User can log in\" in the test name.\n let cleanTitle = norm.title || 'Untitled';\n if (idPart && cleanTitle.startsWith(idPart)) {\n cleanTitle = cleanTitle.slice(idPart.length).replace(/^\\s*[—\\-–]+\\s*/, '').trim();\n }\n\n const tagSuffix = (norm.tags ?? []).map(t => `@${t}`).join(' ');\n return [idPart, cleanTitle, tagSuffix].filter(Boolean).join(' — ').trim();\n}\n\nfunction buildSpecFile(\n norm: NormalizedCase,\n pomClassName: string,\n pomImportPath: string,\n overrides: VarsOverrideMap,\n): string {\n const title = buildTestTitle(norm);\n const steps = norm.steps ?? [];\n const expected = norm.expected ?? [];\n const stepVars = extractStepVars(steps, overrides);\n\n let importPath = pomImportPath.replace(/\\\\/g, '/');\n if (!importPath.startsWith('.')) importPath = `./${importPath}`;\n // Strip .ts extension — TypeScript resolves without it\n importPath = importPath.replace(/\\.ts$/, '');\n\n const stepLines = steps.length\n ? steps\n .map((s, index) => ` ${stepToPlaywright(s, norm.url, norm.step_hints?.[index], stepVars.byStepIndex.get(index))}`)\n .join('\\n')\n : ' // TODO: add steps';\n\n const assertionLines = expected.length\n ? expected.map((e, index) => ` ${expectedToAssertion(e, norm, norm.assertion_hints?.[index])}`).join('\\n')\n : ' // TODO: add assertions';\n\n const reviewNote = norm.needs_review\n ? `\\n // ⚠️ Flagged for review — steps or assertions may need manual refinement\\n`\n : '';\n const variableHeader = buildVariableHeaderComment(stepVars.declarations);\n const variableDeclarations = buildVariableDeclarations(stepVars.declarations);\n\n return `import { test, expect } from '@playwright/test';\nimport { ${pomClassName} } from '${importPath}';\n\n${variableHeader}test('${title}', async ({ page }) => {${reviewNote}\n const pomPage = new ${pomClassName}(page);\n\n${variableDeclarations} // ── Setup ─────────────────────────────────────────────────────────────────\n await pomPage.goto();\n await pomPage.waitForLoad();\n\n // ── Steps ─────────────────────────────────────────────────────────────────\n${stepLines}\n\n // ── Assertions ────────────────────────────────────────────────────────────\n${assertionLines}\n});\n`;\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport async function gen(opts: { lang: string; out: string; vars?: string }) {\n if (opts.lang !== 'ts' && opts.lang !== 'js') {\n console.error('❌ --lang must be ts or js');\n process.exit(1);\n }\n const lang = opts.lang as 'ts' | 'js';\n const specExt = lang === 'js' ? 'spec.js' : 'spec.ts';\n const pomExt = lang === 'js' ? '.js' : '.ts';\n const varsOverride = parseVarsOverride(opts.vars);\n\n const normalized = await fg([\n '.cementic/normalized/*.json',\n '!.cementic/normalized/_index.json',\n ]);\n\n if (normalized.length === 0) {\n console.warn('⚠️ No normalized cases found in .cementic/normalized/');\n console.warn(' Run: ct normalize ./cases');\n process.exit(1);\n }\n\n const projectRoot = process.cwd();\n const testsOutDir = resolve(projectRoot, opts.out);\n const pagesOutDir = resolve(projectRoot, 'pages');\n\n mkdirSync(testsOutDir, { recursive: true });\n mkdirSync(pagesOutDir, { recursive: true });\n\n let specCount = 0;\n let pomCount = 0;\n\n for (const f of normalized) {\n const norm = JSON.parse(readFileSync(f, 'utf8')) as NormalizedCase;\n\n const pomClassName = derivePomClassName(norm);\n const pomFileName = pomClassName + pomExt;\n const pomFilePath = join(pagesOutDir, pomFileName);\n\n // Only write a POM file if one doesn't exist yet — never overwrite user edits\n if (!existsSync(pomFilePath)) {\n writeFileSync(pomFilePath, buildPomClass(pomClassName, norm, lang));\n pomCount++;\n }\n\n // Relative import from the test file location to the POM file\n const relToPages = relative(testsOutDir, pagesOutDir);\n const pomImportPath = join(relToPages, pomFileName);\n\n const stem = basename(f).replace(/\\.json$/, '').replace(/[^\\w-]+/g, '-');\n const specPath = join(testsOutDir, `${stem}.${specExt}`);\n writeFileSync(specPath, buildSpecFile(norm, pomClassName, pomImportPath, varsOverride));\n specCount++;\n }\n\n console.log(`✅ Generated ${specCount} spec file(s) → ${opts.out}/`);\n if (pomCount > 0) {\n console.log(`✅ Generated ${pomCount} POM class file(s) → pages/`);\n }\n if (pomCount === 0 && specCount > 0) {\n console.log(`ℹ️ POM classes already exist — skipped regeneration`);\n }\n}\n\nexport function genCmd() {\n const cmd = new Command('gen')\n .description('Generate Playwright POM spec + page-object files from normalized cases')\n .addHelpText('after', `\nExamples:\n $ ct gen --lang ts\n $ ct gen --lang js --out tests/e2e\n $ ct gen --lang ts --vars '{\"email\":\"admin@example.com\"}'\n`)\n .option('--lang <lang>', 'Target language (ts|js)', 'ts')\n .option('--out <dir>', 'Output directory for spec files', 'tests/generated')\n .option('--vars <json>', 'JSON object of fallback values for generated CT_VAR_* constants')\n .action(async opts => {\n await gen({ lang: opts.lang, out: opts.out, vars: opts.vars });\n });\n return cmd;\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,OAAO,QAAQ;AACf,SAAS,cAAc,WAAW,eAAe,kBAAkB;AACnE,SAAS,MAAM,UAAU,UAAU,eAAe;AA+BlD,SAAS,4BAA4B,OAAuB;AAC1D,SAAO,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACzD;AAEA,SAAS,eAAe,OAAuB;AAC7C,SAAO,MAAM,QAAQ,yBAAyB,MAAM;AACtD;AAEA,SAAS,0BAA0B,OAAoC;AACrE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,WAAO,IAAI,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ,OAAO,EAAE;AAAA,EACpD,QAAQ;AACN,WAAO,MAAM,KAAK,EAAE,QAAQ,OAAO,EAAE;AAAA,EACvC;AACF;AAEA,SAAS,0BAA0B,OAAoC;AACrE,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,KAAK;AAC5B,UAAM,iBAAiB,GAAG,OAAO,QAAQ,GAAG,OAAO,MAAM,GAAG,QAAQ,OAAO,EAAE,KAAK;AAClF,QAAI,mBAAmB,IAAK,QAAO;AACnC,WAAO,eAAe,QAAQ,QAAQ,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,cAAc,EAAE,KAAK,KAAK;AAAA,EACrF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,qBAAqB,OAAmC;AAC/D,QAAM,UAAU,MACb,QAAQ,yBAAyB,GAAG,EACpC,QAAQ,uBAAuB,GAAG,EAClC,KAAK;AACR,QAAM,SAAS,QAAQ,MAAM,SAAS,EAAE,IAAI,WAAS,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO;AACjF,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,IAAI,WAAS,eAAe,MAAM,YAAY,CAAC,CAAC,EAAE,KAAK,UAAU;AACjF;AAEA,SAAS,sBAAsB,OAAmC;AAChE,QAAM,QACJ,MAAM,MAAM,iDAAiD,KAC7D,MAAM,MAAM,2CAA2C,KACvD,MAAM,MAAM,2CAA2C;AACzD,SAAO,QAAQ,CAAC,GAAG,KAAK;AAC1B;AAEA,SAAS,2BAA2B,OAAuB;AACzD,QAAM,UAAU,MACb,QAAQ,SAAS,EAAE,EACnB,QAAQ,yGAAyG,GAAG,EACpH,QAAQ,8CAA8C,GAAG,EACzD,KAAK;AACR,QAAM,SAAS,QAAQ,MAAM,KAAK,EAAE,OAAO,OAAO;AAClD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,IAAI,WAAS,eAAe,KAAK,CAAC,EAAE,KAAK,MAAM;AAC/D;AAEA,SAAS,mBAAmB,OAAuB;AACjD,SAAO,MACJ,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE;AACzB;AAEA,SAAS,qBAAqB,OAAuB;AACnD,SAAO,mBAAmB,MAAM,QAAQ,aAAa,EAAE,CAAC;AAC1D;AAEA,SAAS,gBAAgB,OAAoC;AAC3D,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,MACb,QAAQ,eAAe,EAAE,EACzB,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,iEAAiE,EAAE,EAC3E,KAAK;AACR,SAAO,WAAW;AACpB;AAEA,SAAS,2BAA2B,OAAyB;AAC3D,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,MAAM,KAAK,EAAE,YAAY;AACzC,SAAO,yLAAyL,KAAK,OAAO;AAC9M;AAEA,SAAS,qBAAqB,OAAoC;AAChE,MAAI,CAAC,SAAS,2BAA2B,KAAK,EAAG,QAAO;AACxD,QAAM,UAAU,MACb,QAAQ,eAAe,EAAE,EACzB,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,gEAAgE,GAAG,EAC3E,KAAK;AACR,QAAM,SAAS,mBAAmB,OAAO;AACzC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,2DAA2D,KAAK,MAAM,EAAG,QAAO;AACpF,SAAO;AACT;AAEA,SAAS,iBAAiB,MAAuC;AAC/D,QAAM,IAAI,KAAK,KAAK;AAEpB,QAAM,YAAY,EAAE,MAAM,kEAAkE;AAC5F,MAAI,WAAW;AACb,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,UAAU,CAAC,EAAE,KAAK;AAAA,MAC5B,YAAY,gBAAgB,UAAU,CAAC,CAAC;AAAA,MACxC,OAAO,UAAU,CAAC,EAAE,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,aAAa,EAAE,MAAM,wDAAwD;AACnF,MAAI,YAAY;AACd,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,WAAW,CAAC,EAAE,KAAK;AAAA,MAC7B,YAAY,gBAAgB,WAAW,CAAC,CAAC;AAAA,MACzC,OAAO,WAAW,CAAC,EAAE,KAAK;AAAA,IAC5B;AAAA,EACF;AAEA,QAAM,cAAc,EAAE,MAAM,wEAAwE;AACpG,MAAI,aAAa;AACf,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU,YAAY,CAAC,EAAE,KAAK;AAAA,MAC9B,YAAY,gBAAgB,YAAY,CAAC,CAAC;AAAA,MAC1C,OAAO,YAAY,CAAC,EAAE,KAAK;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,uBACP,WACA,QACA,WACA,UACA,YACoB;AACpB,QAAM,aAAa,CAAC,QAAQ,WAAW,UAAU,UAAU;AAC3D,aAAW,aAAa,YAAY;AAClC,QAAI,CAAC,UAAW;AAChB,UAAM,aAAa,qBAAqB,SAAS;AACjD,QAAI,cAAc,UAAU,IAAI,UAAU,EAAG,QAAO,UAAU,IAAI,UAAU;AAAA,EAC9E;AACA,SAAO;AACT;AAEA,SAAS,gBACP,OACA,WACgE;AAChE,QAAM,eAA0B,CAAC;AACjC,QAAM,cAAc,oBAAI,IAAqB;AAC7C,QAAM,OAAO,oBAAI,IAAqB;AACtC,MAAI,mBAAmB;AACvB,MAAI,qBAAqB;AAEzB,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG;AAC3C,UAAM,UAAU,iBAAiB,IAAI;AACrC,QAAI,CAAC,QAAS;AAEd,UAAM,YACJ,qBAAqB,QAAQ,QAAQ,KACrC,GAAG,QAAQ,SAAS,WAAW,WAAW,OAAO,IAAI,QAAQ,SAAS,WAAW,EAAE,qBAAqB,EAAE,gBAAgB;AAC5H,UAAM,SAAS,UAAU,SAAS;AAElC,QAAI,UAAU,KAAK,IAAI,MAAM;AAC7B,QAAI,CAAC,SAAS;AACZ,gBAAU;AAAA,QACR;AAAA,QACA,WAAW;AAAA,QACX,UACE,uBAAuB,WAAW,QAAQ,WAAW,QAAQ,UAAU,QAAQ,UAAU,KACzF,QAAQ;AAAA,MACZ;AACA,WAAK,IAAI,QAAQ,OAAO;AACxB,mBAAa,KAAK,OAAO;AAAA,IAC3B;AAEA,gBAAY,IAAI,OAAO,OAAO;AAAA,EAChC;AAEA,SAAO,EAAE,cAAc,YAAY;AACrC;AAEA,SAAS,2BAA2B,UAA6B;AAC/D,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,gBAAgB,SACnB,IAAI,aAAW,GAAG,QAAQ,MAAM,IAAI,QAAQ,QAAQ,EAAE,EACtD,KAAK,GAAG;AACX,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG,SAAS,IAAI,aAAW,QAAQ,QAAQ,MAAM,aAAa;AAAA,IAC9D;AAAA,IACA;AAAA,IACA,QAAQ,aAAa;AAAA,IACrB;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,0BAA0B,UAA6B;AAC9D,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SAAO;AAAA,IACL;AAAA,IACA,GAAG,SAAS;AAAA,MACV,aACE,WAAW,QAAQ,SAAS,mBAAmB,QAAQ,MAAM,UAAU,4BAA4B,QAAQ,QAAQ,CAAC;AAAA,IACxH;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,kBAAkB,KAA+B;AACxD,MAAI,CAAC,IAAK,QAAO,oBAAI,IAAI;AAEzB,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,YAAQ,MAAM,2CAAsC;AACpD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAClE,YAAQ,MAAM,2CAAsC;AACpD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAY,oBAAI,IAAoB;AAC1C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,gBAAgB,qBAAqB,GAAG;AAC9C,QAAI,CAAC,cAAe;AACpB,cAAU,IAAI,eAAe,OAAO,KAAK,CAAC;AAAA,EAC5C;AACA,SAAO;AACT;AAIA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,QAAQ,SAAS,GAAG,IAAI,UAAU,GAAG,OAAO;AACrD;AAEA,SAAS,iBACP,MACA,KACA,MACA,SACQ;AACR,QAAM,IAAI,KAAK,KAAK;AACpB,QAAM,iBAAiB,MAAM,WAAW,QAAQ,KAAK,QAAQ,KAAK;AAClE,QAAM,kBAAkB,CAAC,UACvB,SAAS,aAAa,IAAI,4BAA4B,KAAK,CAAC;AAG9D,MAAI,qCAAqC,KAAK,CAAC,GAAG;AAChD,UAAM,WAAW,EAAE,MAAM,qBAAqB,KAAK,EAAE,MAAM,kBAAkB;AAC7E,UAAM,OAAO,WAAW,CAAC,KAAK,WAAW,CAAC,KAAK,OAAO;AACtD,QACE,0BAA0B,IAAI,KAC9B,0BAA0B,IAAI,MAAM,0BAA0B,GAAG,GACjE;AACA,aAAO;AAAA,IACT;AACA,WAAO,oBAAoB,4BAA4B,IAAI,CAAC;AAAA,EAC9D;AAIA,QAAM,UAAU,iBAAiB,CAAC;AAClC,MAAI,SAAS,SAAS,QAAQ;AAC5B,QAAI,eAAgB,QAAO,SAAS,cAAc,SAAS,gBAAgB,QAAQ,KAAK,CAAC;AACzF,UAAM,QAAQ,QAAQ,cAAc;AACpC,WAAO,0BAA0B,4BAA4B,KAAK,CAAC,WAAW,gBAAgB,QAAQ,KAAK,CAAC;AAAA,EAC9G;AAGA,MAAI,2FAA2F,KAAK,CAAC,GAAG;AACtG,QAAI,eAAgB,QAAO,SAAS,cAAc;AAClD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,UAAM,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,sBAAsB,EAAE,EAAE,KAAK;AACxE,WAAO,2CAA2C,4BAA4B,IAAI,CAAC;AAAA,EACrF;AAGA,MAAI,gCAAgC,KAAK,CAAC,GAAG;AAC3C,QAAI,eAAgB,QAAO,SAAS,cAAc;AAClD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,UAAM,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,sBAAsB,EAAE,EAAE,KAAK;AACxE,WAAO,yCAAyC,4BAA4B,IAAI,CAAC;AAAA,EACnF;AAGA,MAAI,wBAAwB,KAAK,CAAC,GAAG;AACnC,QAAI,eAAgB,QAAO,SAAS,cAAc;AAClD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,QAAI,UAAW,QAAO,yBAAyB,4BAA4B,UAAU,CAAC,CAAC,CAAC;AACxF,UAAM,SAAS,EAAE,QAAQ,kCAAkC,EAAE,EAAE,KAAK;AACpE,WAAO,yBAAyB,4BAA4B,MAAM,CAAC;AAAA,EACrE;AAGA,MAAI,SAAS,SAAS,UAAU;AAC9B,QAAI,eAAgB,QAAO,SAAS,cAAc,iBAAiB,gBAAgB,QAAQ,KAAK,CAAC;AACjG,QAAI,QAAQ,YAAY;AACtB,aAAO,0BAA0B,4BAA4B,QAAQ,UAAU,CAAC,mBAAmB,gBAAgB,QAAQ,KAAK,CAAC;AAAA,IACnI;AACA,WAAO,iDAAiD,gBAAgB,QAAQ,KAAK,CAAC;AAAA,EACxF;AAGA,MAAI,gCAAgC,KAAK,CAAC,GAAG;AAC3C,QAAI,eAAgB,QAAO,SAAS,cAAc;AAClD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,UAAM,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,4BAA4B,EAAE,EAAE,KAAK;AAC9E,WAAO,0BAA0B,4BAA4B,IAAI,CAAC;AAAA,EACpE;AACA,MAAI,2BAA2B,KAAK,CAAC,GAAG;AACtC,QAAI,eAAgB,QAAO,SAAS,cAAc;AAClD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,UAAM,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,uBAAuB,EAAE,EAAE,KAAK;AACzE,WAAO,0BAA0B,4BAA4B,IAAI,CAAC;AAAA,EACpE;AAGA,MAAI,iDAAiD,KAAK,CAAC,GAAG;AAC5D,UAAM,WAAW,EAAE,MAAM,uCAAuC;AAChE,UAAM,MAAO,WAAW,CAAC,KAAK;AAC9B,WAAO,8BAA8B,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC,EAAE,YAAY,CAAC;AAAA,EAC/F;AAGA,MAAI,wBAAwB,KAAK,CAAC,GAAG;AACnC,WAAO;AAAA,EACT;AAGA,MAAI,aAAa,KAAK,CAAC,GAAG;AACxB,QAAI,eAAgB,QAAO,SAAS,cAAc;AAClD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,UAAM,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,uBAAuB,EAAE,EAAE,KAAK;AACzE,WAAO,yBAAyB,4BAA4B,IAAI,CAAC;AAAA,EACnE;AAGA,MAAI,cAAc,KAAK,CAAC,GAAG;AACzB,WAAO;AAAA,EACT;AAGA,SAAO,6CAAwC,CAAC;AAClD;AAIA,SAAS,oBACP,UACA,MACA,MACQ;AACR,MAAI,MAAM,WAAY,QAAO,gBAAgB,KAAK,UAAU;AAC5D,QAAM,IAAI,SAAS,KAAK;AACxB,QAAM,YAAY,sBAAsB,CAAC;AACzC,QAAM,oBAAoB,0BAA0B,MAAM,GAAG;AAG7D,MAAI,mCAAmC,KAAK,CAAC,KAAK,2BAA2B,KAAK,CAAC,KAAK,WAAW;AACjG,WAAO,iCAAiC,4BAA4B,SAAS,CAAC;AAAA,EAChF;AAGA,MAAI,6EAA6E,KAAK,CAAC,GAAG;AACxF,UAAM,kBAAkB,qBAAqB;AAC7C,WAAO,iCAAiC,eAAe;AAAA,EACzD;AACA,MAAI,oCAAoC,KAAK,CAAC,GAAG;AAC/C,UAAM,YAAY,EAAE,MAAM,6EAA6E;AACvG,UAAM,cAAc,YAAY,CAAC,IAAI,qBAAqB,UAAU,CAAC,CAAC,IAAI;AAC1E,WAAO,iCAAiC,eAAe,qBAAqB,OAAO;AAAA,EACrF;AAGA,MAAI,2DAA2D,KAAK,CAAC,GAAG;AACtE,UAAM,YACJ,EAAE,MAAM,oBAAoB,KAC5B,EAAE,MAAM,mBAAmB,KAC3B,EAAE,MAAM,sBAAsB;AAChC,UAAM,cAAc,YAAY,CAAC,KAAK,YAAY,CAAC;AACnD,QAAI,aAAa;AACf,YAAM,UAAU,YAAY,WAAW,MAAM,IAAI,0BAA0B,WAAW,IAAI;AAC1F,YAAM,aAAa,YAAY,WAAW,GAAG,IAAI,YAAY,QAAQ,QAAQ,EAAE,EAAE,MAAM,GAAG,EAAE,IAAI,cAAc,EAAE,KAAK,KAAK,IAAI;AAC9H,YAAM,eAAe,WAAW,cAAc;AAC9C,UAAI,iBAAiB,OAAW,QAAO,iCAAiC,YAAY;AAAA,IACtF;AACA,WAAO;AAAA,EACT;AAGA,MAAI,sDAAsD,KAAK,CAAC,GAAG;AACjE,UAAM,aAAa,EAAE,MAAM,kBAAkB;AAC7C,QAAI,WAAY,QAAO,mCAAmC,4BAA4B,WAAW,CAAC,CAAC,CAAC;AACpG,WAAO;AAAA,EACT;AAGA,MAAI,0DAA0D,KAAK,CAAC,GAAG;AACrE,UAAM,WAAW,EAAE,MAAM,kBAAkB;AAC3C,QAAI,SAAU,QAAO,gCAAgC,4BAA4B,SAAS,CAAC,CAAC,CAAC;AAC7F,QAAI,UAAW,QAAO,gCAAgC,eAAe,SAAS,CAAC;AAC/E,WAAO;AAAA,EACT;AAGA,MAAI,2DAA2D,KAAK,CAAC,GAAG;AACtE,UAAM,WAAW,EAAE,MAAM,kBAAkB;AAC3C,QAAI,SAAU,QAAO,gCAAgC,4BAA4B,SAAS,CAAC,CAAC,CAAC;AAC7F,QAAI,eAAe,KAAK,CAAC,EAAG,QAAO;AACnC,WAAO;AAAA,EACT;AAGA,MAAI,mDAAmD,KAAK,CAAC,GAAG;AAC9D,UAAM,eAAe,EAAE,MAAM,kBAAkB;AAC/C,QAAI,aAAc,QAAO,gCAAgC,4BAA4B,aAAa,CAAC,CAAC,CAAC;AACrG,WAAO;AAAA,EACT;AAGA,MAAI,oDAAoD,KAAK,CAAC,GAAG;AAC/D,UAAM,eAAe,EAAE,MAAM,kBAAkB;AAC/C,QAAI,aAAc,QAAO,gCAAgC,4BAA4B,aAAa,CAAC,CAAC,CAAC;AACrG,UAAM,eACJ,EAAE,MAAM,2FAA2F,KACnG,EAAE,MAAM,4CAA4C;AACtD,UAAM,UAAU,eAAe,CAAC,GAAG,KAAK;AACxC,QAAI,SAAS;AACX,UAAI,oEAAoE,KAAK,OAAO,GAAG;AACrF,eAAO;AAAA,MACT;AACA,aAAO,gCAAgC,2BAA2B,OAAO,CAAC;AAAA,IAC5E;AACA,WAAO;AAAA,EACT;AAGA,MAAI,4CAA4C,KAAK,CAAC,GAAG;AACvD,UAAM,aAAa,EAAE,MAAM,OAAO;AAClC,QAAI,WAAY,QAAO,uEAAuE,WAAW,CAAC,CAAC;AAC3G,WAAO;AAAA,EACT;AAGA,MAAI,kCAAkC,KAAK,CAAC,GAAG;AAC7C,WAAO;AAAA,EACT;AACA,MAAI,2BAA2B,KAAK,CAAC,GAAG;AACtC,WAAO;AAAA,EACT;AAGA,MAAI,mCAAmC,KAAK,CAAC,KAAK,2BAA2B,KAAK,CAAC,GAAG;AACpF,WAAO;AAAA,EACT;AACA,MAAI,kCAAkC,KAAK,CAAC,GAAG;AAC7C,UAAM,WAAW,EAAE,MAAM,kBAAkB;AAC3C,QAAI,SAAU,QAAO,gEAAgE,4BAA4B,SAAS,CAAC,CAAC,CAAC;AAC7H,WAAO;AAAA,EACT;AAGA,MAAI,4CAA4C,KAAK,CAAC,GAAG;AACvD,UAAM,YAAY,EAAE,MAAM,kBAAkB;AAC5C,QAAI,UAAW,QAAO,gCAAgC,4BAA4B,UAAU,CAAC,CAAC,CAAC;AAC/F,UAAM,QAAQ,EAAE,QAAQ,kEAAkE,EAAE,EAAE,KAAK;AACnG,WAAO,gCAAgC,2BAA2B,KAAK,CAAC;AAAA,EAC1E;AAGA,QAAM,UAAU,EAAE,QAAQ,SAAS,EAAE,EAAE,KAAK;AAC5C,SAAO,gCAAgC,2BAA2B,OAAO,CAAC;AAC5E;AAIA,SAAS,aAAa,OAAe,WAAW,WAAmB;AACjE,QAAM,SAAS,MACZ,UAAU,MAAM,EAChB,QAAQ,oBAAoB,EAAE,EAC9B,MAAM,gBAAgB,EACtB,OAAO,OAAO;AAEjB,QAAM,aAAa,OAChB,IAAI,CAAC,UAAU,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC,CAAC,EAC7D,KAAK,EAAE;AAEV,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,SAAS,KAAK,UAAU,IAAI,OAAO,UAAU,KAAK;AAC3D;AAEA,SAAS,mBAAmB,MAA8B;AACxD,QAAM,MAAM,KAAK,MAAM,KAAK,SAAS;AACrC,QAAM,aAAa,IAAI,QAAQ,SAAS,EAAE;AAC1C,SAAO,GAAG,aAAa,UAAU,CAAC;AACpC;AAIA,SAAS,cAAc,WAAmB,MAAsB,MAA2B;AACzF,QAAM,UAAU,KAAK,OAAO;AAC5B,QAAM,OAAO,SAAS;AAEtB,SAAO,OACH;AAAA;AAAA,eAES,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAQI,4BAA4B,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQ1D;AAAA,eACS,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAQI,4BAA4B,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQhE;AAIA,SAAS,eAAe,MAA8B;AACpD,QAAM,SAAS,KAAK,MAAM;AAI1B,MAAI,aAAa,KAAK,SAAS;AAC/B,MAAI,UAAU,WAAW,WAAW,MAAM,GAAG;AAC3C,iBAAa,WAAW,MAAM,OAAO,MAAM,EAAE,QAAQ,kBAAkB,EAAE,EAAE,KAAK;AAAA,EAClF;AAEA,QAAM,aAAa,KAAK,QAAQ,CAAC,GAAG,IAAI,OAAK,IAAI,CAAC,EAAE,EAAE,KAAK,GAAG;AAC9D,SAAO,CAAC,QAAQ,YAAY,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,UAAK,EAAE,KAAK;AAC1E;AAEA,SAAS,cACP,MACA,cACA,eACA,WACQ;AACR,QAAM,QAAQ,eAAe,IAAI;AACjC,QAAM,QAAQ,KAAK,SAAS,CAAC;AAC7B,QAAM,WAAW,KAAK,YAAY,CAAC;AACnC,QAAM,WAAW,gBAAgB,OAAO,SAAS;AAEjD,MAAI,aAAa,cAAc,QAAQ,OAAO,GAAG;AACjD,MAAI,CAAC,WAAW,WAAW,GAAG,EAAG,cAAa,KAAK,UAAU;AAE7D,eAAa,WAAW,QAAQ,SAAS,EAAE;AAE3C,QAAM,YAAY,MAAM,SACpB,MACG,IAAI,CAAC,GAAG,UAAU,KAAK,iBAAiB,GAAG,KAAK,KAAK,KAAK,aAAa,KAAK,GAAG,SAAS,YAAY,IAAI,KAAK,CAAC,CAAC,EAAE,EACjH,KAAK,IAAI,IACZ;AAEJ,QAAM,iBAAiB,SAAS,SAC5B,SAAS,IAAI,CAAC,GAAG,UAAU,KAAK,oBAAoB,GAAG,MAAM,KAAK,kBAAkB,KAAK,CAAC,CAAC,EAAE,EAAE,KAAK,IAAI,IACxG;AAEJ,QAAM,aAAa,KAAK,eACpB;AAAA;AAAA,IACA;AACJ,QAAM,iBAAiB,2BAA2B,SAAS,YAAY;AACvE,QAAM,uBAAuB,0BAA0B,SAAS,YAAY;AAE5E,SAAO;AAAA,WACE,YAAY,YAAY,UAAU;AAAA;AAAA,EAE3C,cAAc,SAAS,KAAK,2BAA2B,UAAU;AAAA,wBAC3C,YAAY;AAAA;AAAA,EAElC,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKpB,SAAS;AAAA;AAAA;AAAA,EAGT,cAAc;AAAA;AAAA;AAGhB;AAIA,eAAsB,IAAI,MAAoD;AAC5E,MAAI,KAAK,SAAS,QAAQ,KAAK,SAAS,MAAM;AAC5C,YAAQ,MAAM,gCAA2B;AACzC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,OAAO,KAAK;AAClB,QAAM,UAAU,SAAS,OAAO,YAAY;AAC5C,QAAM,SAAU,SAAS,OAAO,QAAQ;AACxC,QAAM,eAAe,kBAAkB,KAAK,IAAI;AAEhD,QAAM,aAAa,MAAM,GAAG;AAAA,IAC1B;AAAA,IACA;AAAA,EACF,CAAC;AAED,MAAI,WAAW,WAAW,GAAG;AAC3B,YAAQ,KAAK,kEAAwD;AACrE,YAAQ,KAAK,+BAA+B;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,cAAc,QAAQ,aAAa,KAAK,GAAG;AACjD,QAAM,cAAc,QAAQ,aAAa,OAAO;AAEhD,YAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAC1C,YAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAE1C,MAAI,YAAY;AAChB,MAAI,WAAY;AAEhB,aAAW,KAAK,YAAY;AAC1B,UAAM,OAAO,KAAK,MAAM,aAAa,GAAG,MAAM,CAAC;AAE/C,UAAM,eAAe,mBAAmB,IAAI;AAC5C,UAAM,cAAe,eAAe;AACpC,UAAM,cAAe,KAAK,aAAa,WAAW;AAGlD,QAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,oBAAc,aAAa,cAAc,cAAc,MAAM,IAAI,CAAC;AAClE;AAAA,IACF;AAGA,UAAM,aAAe,SAAS,aAAa,WAAW;AACtD,UAAM,gBAAgB,KAAK,YAAY,WAAW;AAElD,UAAM,OAAW,SAAS,CAAC,EAAE,QAAQ,WAAW,EAAE,EAAE,QAAQ,YAAY,GAAG;AAC3E,UAAM,WAAW,KAAK,aAAa,GAAG,IAAI,IAAI,OAAO,EAAE;AACvD,kBAAc,UAAU,cAAc,MAAM,cAAc,eAAe,YAAY,CAAC;AACtF;AAAA,EACF;AAEA,UAAQ,IAAI,oBAAe,SAAS,yBAAoB,KAAK,GAAG,GAAG;AACnE,MAAI,WAAW,GAAG;AAChB,YAAQ,IAAI,oBAAe,QAAQ,kCAA6B;AAAA,EAClE;AACA,MAAI,aAAa,KAAK,YAAY,GAAG;AACnC,YAAQ,IAAI,qEAAsD;AAAA,EACpE;AACF;AAEO,SAAS,SAAS;AACvB,QAAM,MAAM,IAAI,QAAQ,KAAK,EAC1B,YAAY,wEAAwE,EACpF,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,CAKzB,EACI,OAAO,iBAAiB,2BAA2B,IAAI,EACvD,OAAO,eAAgB,mCAAmC,iBAAiB,EAC3E,OAAO,iBAAiB,iEAAiE,EACzF,OAAO,OAAM,SAAQ;AACpB,UAAM,IAAI,EAAE,MAAM,KAAK,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,EAC/D,CAAC;AACH,SAAO;AACT;","names":[]}
|
|
File without changes
|