@kosuke-ai/cli 0.0.30 ā 0.0.32
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 +48 -0
- package/dist/kosuke/commands/ship.d.ts.map +1 -1
- package/dist/kosuke/commands/ship.js +57 -22
- package/dist/kosuke/commands/ship.js.map +1 -1
- package/dist/kosuke/commands/test.d.ts +8 -9
- package/dist/kosuke/commands/test.d.ts.map +1 -1
- package/dist/kosuke/commands/test.js +68 -288
- package/dist/kosuke/commands/test.js.map +1 -1
- package/dist/kosuke/types.d.ts +8 -16
- package/dist/kosuke/types.d.ts.map +1 -1
- package/dist/kosuke/utils/browser-agent.d.ts +33 -0
- package/dist/kosuke/utils/browser-agent.d.ts.map +1 -0
- package/dist/kosuke/utils/browser-agent.js +102 -0
- package/dist/kosuke/utils/browser-agent.js.map +1 -0
- package/dist/kosuke/utils/error-analyzer.d.ts +6 -3
- package/dist/kosuke/utils/error-analyzer.d.ts.map +1 -1
- package/dist/kosuke/utils/error-analyzer.js +35 -57
- package/dist/kosuke/utils/error-analyzer.js.map +1 -1
- package/dist/kosuke/utils/prompt-generator.d.ts +13 -0
- package/dist/kosuke/utils/prompt-generator.d.ts.map +1 -0
- package/dist/kosuke/utils/prompt-generator.js +44 -0
- package/dist/kosuke/utils/prompt-generator.js.map +1 -0
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js.map +1 -1
- package/dist/package.json +3 -6
- package/package.json +3 -6
- package/dist/kosuke/utils/playwright-agent.d.ts +0 -47
- package/dist/kosuke/utils/playwright-agent.d.ts.map +0 -1
- package/dist/kosuke/utils/playwright-agent.js +0 -278
- package/dist/kosuke/utils/playwright-agent.js.map +0 -1
- package/dist/kosuke/utils/test-generator.d.ts +0 -22
- package/dist/kosuke/utils/test-generator.d.ts.map +0 -1
- package/dist/kosuke/utils/test-generator.js +0 -174
- package/dist/kosuke/utils/test-generator.js.map +0 -1
- package/dist/kosuke/utils/visual-tester.d.ts +0 -71
- package/dist/kosuke/utils/visual-tester.d.ts.map +0 -1
- package/dist/kosuke/utils/visual-tester.js +0 -202
- package/dist/kosuke/utils/visual-tester.js.map +0 -1
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Visual Tester - Visual regression testing with screenshot comparison
|
|
3
|
-
*
|
|
4
|
-
* Inspired by Subito.it's approach:
|
|
5
|
-
* - Capture screenshots of pages
|
|
6
|
-
* - Compare against baseline images
|
|
7
|
-
* - Detect pixel differences above threshold
|
|
8
|
-
* - Hide dynamic content (ads, analytics)
|
|
9
|
-
*/
|
|
10
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
import { PNG } from 'pngjs';
|
|
13
|
-
import pixelmatch from 'pixelmatch';
|
|
14
|
-
/**
|
|
15
|
-
* Visual Tester class for screenshot comparison
|
|
16
|
-
*/
|
|
17
|
-
export class VisualTester {
|
|
18
|
-
constructor(cwd = process.cwd()) {
|
|
19
|
-
this.diffs = [];
|
|
20
|
-
this.cwd = cwd;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Hide dynamic content that shouldn't be tested
|
|
24
|
-
*/
|
|
25
|
-
async hideDynamicContent(page) {
|
|
26
|
-
await page.addStyleTag({
|
|
27
|
-
content: `
|
|
28
|
-
/* Hide common dynamic content */
|
|
29
|
-
.advertisement,
|
|
30
|
-
.ad-banner,
|
|
31
|
-
.analytics-widget,
|
|
32
|
-
.third-party-banner,
|
|
33
|
-
[class*="ad-"],
|
|
34
|
-
[id*="ad-"],
|
|
35
|
-
[class*="analytics"],
|
|
36
|
-
[id*="analytics"] {
|
|
37
|
-
display: none !important;
|
|
38
|
-
visibility: hidden !important;
|
|
39
|
-
}
|
|
40
|
-
`,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Take a screenshot and compare with baseline
|
|
45
|
-
*/
|
|
46
|
-
async captureAndCompare(page, options) {
|
|
47
|
-
const { ticketId, testName, threshold = 0.01, updateBaseline = false } = options;
|
|
48
|
-
// Ensure visual test directories exist
|
|
49
|
-
const baselineDir = join(this.cwd, '.kosuke', 'visual-tests', 'baselines', ticketId);
|
|
50
|
-
const currentDir = join(this.cwd, '.kosuke', 'visual-tests', 'current', ticketId);
|
|
51
|
-
const diffDir = join(this.cwd, '.kosuke', 'visual-tests', 'diffs', ticketId);
|
|
52
|
-
this.ensureDir(baselineDir);
|
|
53
|
-
this.ensureDir(currentDir);
|
|
54
|
-
this.ensureDir(diffDir);
|
|
55
|
-
const baselinePath = join(baselineDir, `${testName}.png`);
|
|
56
|
-
const currentPath = join(currentDir, `${testName}.png`);
|
|
57
|
-
const diffPath = join(diffDir, `${testName}.png`);
|
|
58
|
-
// Hide dynamic content before taking screenshot
|
|
59
|
-
await this.hideDynamicContent(page);
|
|
60
|
-
// Wait for page to be stable
|
|
61
|
-
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {
|
|
62
|
-
// Ignore timeout - some pages never reach networkidle
|
|
63
|
-
});
|
|
64
|
-
// Take screenshot
|
|
65
|
-
const screenshot = await page.screenshot({ fullPage: true });
|
|
66
|
-
writeFileSync(currentPath, screenshot);
|
|
67
|
-
// If no baseline exists or updateBaseline flag is set, create/update baseline
|
|
68
|
-
if (!existsSync(baselinePath) || updateBaseline) {
|
|
69
|
-
writeFileSync(baselinePath, screenshot);
|
|
70
|
-
console.log(` šø ${updateBaseline ? 'Updated' : 'Created'} baseline: ${testName}`);
|
|
71
|
-
const diff = {
|
|
72
|
-
testName,
|
|
73
|
-
diffPixels: 0,
|
|
74
|
-
diffPercentage: 0,
|
|
75
|
-
threshold,
|
|
76
|
-
passed: true,
|
|
77
|
-
baselinePath,
|
|
78
|
-
currentPath,
|
|
79
|
-
diffPath,
|
|
80
|
-
};
|
|
81
|
-
this.diffs.push(diff);
|
|
82
|
-
return diff;
|
|
83
|
-
}
|
|
84
|
-
// Compare with baseline
|
|
85
|
-
const result = this.compareImages(baselinePath, currentPath, diffPath, threshold);
|
|
86
|
-
this.diffs.push(result);
|
|
87
|
-
return result;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Compare two images and generate diff
|
|
91
|
-
*/
|
|
92
|
-
compareImages(baselinePath, currentPath, diffPath, threshold) {
|
|
93
|
-
const testName = baselinePath.split('/').pop()?.replace('.png', '') || 'unknown';
|
|
94
|
-
try {
|
|
95
|
-
const baseline = PNG.sync.read(readFileSync(baselinePath));
|
|
96
|
-
const current = PNG.sync.read(readFileSync(currentPath));
|
|
97
|
-
// Check if dimensions match
|
|
98
|
-
if (baseline.width !== current.width || baseline.height !== current.height) {
|
|
99
|
-
return {
|
|
100
|
-
testName,
|
|
101
|
-
diffPixels: baseline.width * baseline.height, // All pixels are different
|
|
102
|
-
diffPercentage: 1,
|
|
103
|
-
threshold,
|
|
104
|
-
passed: false,
|
|
105
|
-
baselinePath,
|
|
106
|
-
currentPath,
|
|
107
|
-
diffPath,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// Create diff image
|
|
111
|
-
const diff = new PNG({ width: baseline.width, height: baseline.height });
|
|
112
|
-
// Compare pixels
|
|
113
|
-
const diffPixels = pixelmatch(baseline.data, current.data, diff.data, baseline.width, baseline.height, { threshold: 0.1 } // Pixel sensitivity
|
|
114
|
-
);
|
|
115
|
-
const totalPixels = baseline.width * baseline.height;
|
|
116
|
-
const diffPercentage = diffPixels / totalPixels;
|
|
117
|
-
// Save diff image if there are differences
|
|
118
|
-
if (diffPixels > 0) {
|
|
119
|
-
writeFileSync(diffPath, PNG.sync.write(diff));
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
testName,
|
|
123
|
-
diffPixels,
|
|
124
|
-
diffPercentage,
|
|
125
|
-
threshold,
|
|
126
|
-
passed: diffPercentage <= threshold,
|
|
127
|
-
baselinePath,
|
|
128
|
-
currentPath,
|
|
129
|
-
diffPath,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
console.error(` ā ļø Failed to compare images: ${error}`);
|
|
134
|
-
return {
|
|
135
|
-
testName,
|
|
136
|
-
diffPixels: 0,
|
|
137
|
-
diffPercentage: 0,
|
|
138
|
-
threshold,
|
|
139
|
-
passed: false,
|
|
140
|
-
baselinePath,
|
|
141
|
-
currentPath,
|
|
142
|
-
diffPath,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Get all visual diffs
|
|
148
|
-
*/
|
|
149
|
-
getDiffs() {
|
|
150
|
-
return this.diffs;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Get failed visual diffs
|
|
154
|
-
*/
|
|
155
|
-
getFailedDiffs() {
|
|
156
|
-
return this.diffs.filter((diff) => !diff.passed);
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Check if there are any visual regressions
|
|
160
|
-
*/
|
|
161
|
-
hasRegressions() {
|
|
162
|
-
return this.getFailedDiffs().length > 0;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Format visual diff report
|
|
166
|
-
*/
|
|
167
|
-
formatReport() {
|
|
168
|
-
if (this.diffs.length === 0) {
|
|
169
|
-
return 'No visual tests run';
|
|
170
|
-
}
|
|
171
|
-
const failed = this.getFailedDiffs();
|
|
172
|
-
const passed = this.diffs.filter((diff) => diff.passed);
|
|
173
|
-
const lines = [];
|
|
174
|
-
lines.push(`\nšø Visual Regression Report:`);
|
|
175
|
-
lines.push(` ā
Passed: ${passed.length}`);
|
|
176
|
-
lines.push(` ā Failed: ${failed.length}`);
|
|
177
|
-
if (failed.length > 0) {
|
|
178
|
-
lines.push('\n Failed tests:');
|
|
179
|
-
for (const diff of failed) {
|
|
180
|
-
const percentage = (diff.diffPercentage * 100).toFixed(2);
|
|
181
|
-
lines.push(` - ${diff.testName}: ${percentage}% difference (threshold: ${diff.threshold * 100}%)`);
|
|
182
|
-
lines.push(` Diff image: ${diff.diffPath}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return lines.join('\n');
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Clear all diffs
|
|
189
|
-
*/
|
|
190
|
-
clear() {
|
|
191
|
-
this.diffs = [];
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* Ensure directory exists
|
|
195
|
-
*/
|
|
196
|
-
ensureDir(dir) {
|
|
197
|
-
if (!existsSync(dir)) {
|
|
198
|
-
mkdirSync(dir, { recursive: true });
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
//# sourceMappingURL=visual-tester.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"visual-tester.js","sourceRoot":"","sources":["../../../kosuke/utils/visual-tester.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,UAAU,MAAM,YAAY,CAAC;AAoBpC;;GAEG;AACH,MAAM,OAAO,YAAY;IAIvB,YAAY,MAAc,OAAO,CAAC,GAAG,EAAE;QAF/B,UAAK,GAAiB,EAAE,CAAC;QAG/B,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,kBAAkB,CAAC,IAAU;QACjC,MAAM,IAAI,CAAC,WAAW,CAAC;YACrB,OAAO,EAAE;;;;;;;;;;;;;OAaR;SACF,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,IAAU,EAAE,OAA0B;QAC5D,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,GAAG,IAAI,EAAE,cAAc,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;QAEjF,uCAAuC;QACvC,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;QACrF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QAClF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAE7E,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC5B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC3B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAExB,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,QAAQ,MAAM,CAAC,CAAC;QAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,GAAG,QAAQ,MAAM,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,QAAQ,MAAM,CAAC,CAAC;QAElD,gDAAgD;QAChD,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAEpC,6BAA6B;QAC7B,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACxE,sDAAsD;QACxD,CAAC,CAAC,CAAC;QAEH,kBAAkB;QAClB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,aAAa,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;QAEvC,8EAA8E;QAC9E,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,cAAc,EAAE,CAAC;YAChD,aAAa,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,SAAS,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,cAAc,QAAQ,EAAE,CAAC,CAAC;YAErF,MAAM,IAAI,GAAe;gBACvB,QAAQ;gBACR,UAAU,EAAE,CAAC;gBACb,cAAc,EAAE,CAAC;gBACjB,SAAS;gBACT,MAAM,EAAE,IAAI;gBACZ,YAAY;gBACZ,WAAW;gBACX,QAAQ;aACT,CAAC;YAEF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,wBAAwB;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAElF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,aAAa,CACnB,YAAoB,EACpB,WAAmB,EACnB,QAAgB,EAChB,SAAiB;QAEjB,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;QAEjF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC;YAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;YAEzD,4BAA4B;YAC5B,IAAI,QAAQ,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;gBAC3E,OAAO;oBACL,QAAQ;oBACR,UAAU,EAAE,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,2BAA2B;oBACzE,cAAc,EAAE,CAAC;oBACjB,SAAS;oBACT,MAAM,EAAE,KAAK;oBACb,YAAY;oBACZ,WAAW;oBACX,QAAQ;iBACT,CAAC;YACJ,CAAC;YAED,oBAAoB;YACpB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAEzE,iBAAiB;YACjB,MAAM,UAAU,GAAG,UAAU,CAC3B,QAAQ,CAAC,IAAI,EACb,OAAO,CAAC,IAAI,EACZ,IAAI,CAAC,IAAI,EACT,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,MAAM,EACf,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,oBAAoB;aACxC,CAAC;YAEF,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;YACrD,MAAM,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;YAEhD,2CAA2C;YAC3C,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;gBACnB,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAChD,CAAC;YAED,OAAO;gBACL,QAAQ;gBACR,UAAU;gBACV,cAAc;gBACd,SAAS;gBACT,MAAM,EAAE,cAAc,IAAI,SAAS;gBACnC,YAAY;gBACZ,WAAW;gBACX,QAAQ;aACT,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,KAAK,EAAE,CAAC,CAAC;YAC3D,OAAO;gBACL,QAAQ;gBACR,UAAU,EAAE,CAAC;gBACb,cAAc,EAAE,CAAC;gBACjB,SAAS;gBACT,MAAM,EAAE,KAAK;gBACb,YAAY;gBACZ,WAAW;gBACX,QAAQ;aACT,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,cAAc;QACZ,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,YAAY;QACV,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,qBAAqB,CAAC;QAC/B,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAExD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAE5C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;YACjC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;gBAC1B,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBAC1D,KAAK,CAAC,IAAI,CACR,QAAQ,IAAI,CAAC,QAAQ,KAAK,UAAU,4BAA4B,IAAI,CAAC,SAAS,GAAG,GAAG,IAAI,CACzF,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;IAED;;OAEG;IACK,SAAS,CAAC,GAAW;QAC3B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;CACF"}
|