@cleaver/breakcheck-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +152 -0
  2. package/dist/__tests__/compare/index.test.js +322 -0
  3. package/dist/__tests__/compare/index.test.js.map +1 -0
  4. package/dist/__tests__/rules/RulesDsl.test.js +190 -0
  5. package/dist/__tests__/rules/RulesDsl.test.js.map +1 -0
  6. package/dist/__tests__/rules/RulesEngine.test.js +142 -0
  7. package/dist/__tests__/rules/RulesEngine.test.js.map +1 -0
  8. package/dist/__tests__/snapshot/index.test.js +238 -0
  9. package/dist/__tests__/snapshot/index.test.js.map +1 -0
  10. package/dist/__tests__/view/index.test.js +102 -0
  11. package/dist/__tests__/view/index.test.js.map +1 -0
  12. package/dist/api/index.js +69 -0
  13. package/dist/api/index.js.map +1 -0
  14. package/dist/core/compare/classes/ComparisonRepository.js +77 -0
  15. package/dist/core/compare/classes/ComparisonRepository.js.map +1 -0
  16. package/dist/core/compare/index.js +81 -0
  17. package/dist/core/compare/index.js.map +1 -0
  18. package/dist/core/crawler/implementations/cheerio.js +46 -0
  19. package/dist/core/crawler/implementations/cheerio.js.map +1 -0
  20. package/dist/core/crawler/implementations/playwright.js +44 -0
  21. package/dist/core/crawler/implementations/playwright.js.map +1 -0
  22. package/dist/core/crawler/index.js +61 -0
  23. package/dist/core/crawler/index.js.map +1 -0
  24. package/dist/core/rules/RulesDsl.js +251 -0
  25. package/dist/core/rules/RulesDsl.js.map +1 -0
  26. package/dist/core/rules/RulesEngine.js +91 -0
  27. package/dist/core/rules/RulesEngine.js.map +1 -0
  28. package/dist/core/snapshot/classes/LoadedSnapshot.js +37 -0
  29. package/dist/core/snapshot/classes/LoadedSnapshot.js.map +1 -0
  30. package/dist/core/snapshot/classes/SnapshotRepository.js +192 -0
  31. package/dist/core/snapshot/classes/SnapshotRepository.js.map +1 -0
  32. package/dist/core/snapshot/index.js +67 -0
  33. package/dist/core/snapshot/index.js.map +1 -0
  34. package/dist/core/view/index.handlers.js +70 -0
  35. package/dist/core/view/index.handlers.js.map +1 -0
  36. package/dist/core/view/index.js +50 -0
  37. package/dist/core/view/index.js.map +1 -0
  38. package/dist/index.d.ts +14 -0
  39. package/dist/index.js +13 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/lib/logger.js +26 -0
  42. package/dist/lib/logger.js.map +1 -0
  43. package/dist/lib/root.js +25 -0
  44. package/dist/lib/root.js.map +1 -0
  45. package/dist/public/css/diff2html.min.css +1 -0
  46. package/dist/public/js/diff2html.min.js +1 -0
  47. package/dist/types/api.js +3 -0
  48. package/dist/types/api.js.map +1 -0
  49. package/dist/types/compare.js +3 -0
  50. package/dist/types/compare.js.map +1 -0
  51. package/dist/types/crawler.js +2 -0
  52. package/dist/types/crawler.js.map +1 -0
  53. package/dist/types/rules.js +6 -0
  54. package/dist/types/rules.js.map +1 -0
  55. package/dist/types/snapshot.js +3 -0
  56. package/dist/types/snapshot.js.map +1 -0
  57. package/dist/views/diff.ejs +59 -0
  58. package/dist/views/index.ejs +83 -0
  59. package/package.json +41 -0
@@ -0,0 +1,77 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { promisify } from "util";
4
+ import * as zlib from "zlib";
5
+ import { findRootDir } from "../../../lib/root";
6
+ const gzip = promisify(zlib.gzip);
7
+ export class ComparisonRepository {
8
+ comparisonsDir;
9
+ comparisonDir;
10
+ diffsDir;
11
+ index = { urls: {}, metadata: {} };
12
+ constructor(comparisonsDir) {
13
+ this.comparisonsDir = comparisonsDir;
14
+ }
15
+ /**
16
+ * Creates a new comparison repository instance and initializes it with the given name and metadata.
17
+ */
18
+ static async create(name, metadata, comparisonsDir) {
19
+ const rootDir = await findRootDir();
20
+ const defaultComparisonsDir = path.join(rootDir, "comparisons");
21
+ const instance = new ComparisonRepository(comparisonsDir || defaultComparisonsDir);
22
+ await instance.initialize(name, metadata);
23
+ return instance;
24
+ }
25
+ /**
26
+ * Initializes a new comparison, creating directories and saving initial metadata.
27
+ */
28
+ async initialize(name, metadata) {
29
+ await fs.mkdir(this.comparisonsDir, { recursive: true });
30
+ this.comparisonDir = path.join(this.comparisonsDir, name);
31
+ this.diffsDir = path.join(this.comparisonDir, "diffs");
32
+ // Clean up previous results
33
+ await fs.rm(this.comparisonDir, { recursive: true, force: true });
34
+ // Create fresh directories
35
+ await fs.mkdir(this.comparisonDir, { recursive: true });
36
+ await fs.mkdir(this.diffsDir, { recursive: true });
37
+ // Save metadata and initialize index
38
+ const metadataPath = path.join(this.comparisonDir, "metadata.json");
39
+ await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
40
+ this.index.metadata = {
41
+ ...metadata,
42
+ timestamp: new Date().toISOString(),
43
+ totalPages: 0,
44
+ pagesWithDifferences: 0,
45
+ };
46
+ }
47
+ /**
48
+ * Saves a single page diff to a compressed file and updates the index.
49
+ */
50
+ async savePageDiff(pageDiff) {
51
+ const diffData = JSON.stringify(pageDiff);
52
+ const compressed = await gzip(diffData);
53
+ const filename = Buffer.from(pageDiff.url).toString("base64url") + ".json.gz";
54
+ await fs.writeFile(path.join(this.diffsDir, filename), compressed);
55
+ // Add to index
56
+ this.index.urls[pageDiff.url] = {
57
+ filename,
58
+ hasDifferences: pageDiff.hasDifferences,
59
+ };
60
+ this.index.metadata.totalPages++;
61
+ if (pageDiff.hasDifferences) {
62
+ this.index.metadata.pagesWithDifferences++;
63
+ }
64
+ }
65
+ /**
66
+ * Writes the final index file to disk.
67
+ */
68
+ async finalizeComparison() {
69
+ const indexPath = path.join(this.comparisonDir, "index.json");
70
+ await fs.writeFile(indexPath, JSON.stringify(this.index, null, 2));
71
+ return this.comparisonDir;
72
+ }
73
+ getIndex() {
74
+ return this.index;
75
+ }
76
+ }
77
+ //# sourceMappingURL=ComparisonRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComparisonRepository.js","sourceRoot":"","sources":["../../../../src/core/compare/classes/ComparisonRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { createPatch, diffLines } from "diff";
2
+ import jsbeautify from "js-beautify";
3
+ import path from "path";
4
+ /**
5
+ * Compares two page snapshots and returns the differences found.
6
+ */
7
+ export async function comparePage(before, after) {
8
+ const url = before.url;
9
+ const beautify = jsbeautify.html;
10
+ const beautifyOptions = {
11
+ indent_size: 2,
12
+ preserve_newlines: true,
13
+ unformatted: ["a", "span", "b", "strong", "i", "em"],
14
+ };
15
+ const beforeContent = beautify(before.content, beautifyOptions);
16
+ const afterContent = beautify(after.content, beautifyOptions);
17
+ const patch = createPatch(url, beforeContent, afterContent);
18
+ const differences = diffLines(beforeContent, afterContent);
19
+ const hasDifferences = differences.some((diff) => diff.added || diff.removed);
20
+ return { url, differences, hasDifferences, patch };
21
+ }
22
+ /**
23
+ * Compares two snapshots iteratively, saving each diff to disk.
24
+ */
25
+ export async function compareSnapshots(config, snapshotRepository, comparisonRepository, rulesEngine) {
26
+ const startTime = Date.now();
27
+ // Load both snapshots
28
+ const beforeSnapshot = await snapshotRepository.loadSnapshot(config.beforeSnapshotId);
29
+ const afterSnapshot = await snapshotRepository.loadSnapshot(config.afterSnapshotId);
30
+ // Determine URLs to compare
31
+ const beforeUrls = Object.keys(beforeSnapshot.index.urls);
32
+ const afterUrls = new Set(Object.keys(afterSnapshot.index.urls));
33
+ const urlsToCompare = config.urls || beforeUrls;
34
+ const rulesUsedIdentifier = typeof config.ruleset === "string" ? config.ruleset : config.ruleset.name;
35
+ // Find new and removed URLs
36
+ const newUrls = [...afterUrls].filter((url) => !beforeUrls.includes(url));
37
+ const removedUrls = beforeUrls.filter((url) => !afterUrls.has(url));
38
+ let pagesWithErrors = 0;
39
+ // Compare pages one by one
40
+ for (const url of urlsToCompare) {
41
+ if (!afterUrls.has(url))
42
+ continue; // Skip URLs not in the "after" snapshot
43
+ const beforePage = await beforeSnapshot.getPage(url);
44
+ const afterPage = await afterSnapshot.getPage(url);
45
+ if (!beforePage || !afterPage) {
46
+ pagesWithErrors++;
47
+ continue;
48
+ }
49
+ const beforeContentWithRules = rulesEngine.process(beforePage.content);
50
+ const afterContentWithRules = rulesEngine.process(afterPage.content);
51
+ if (beforeContentWithRules && afterContentWithRules) {
52
+ const diff = await comparePage({ ...beforePage, content: beforeContentWithRules }, { ...afterPage, content: afterContentWithRules });
53
+ await comparisonRepository.savePageDiff(diff);
54
+ }
55
+ else {
56
+ pagesWithErrors++;
57
+ }
58
+ }
59
+ const finalIndex = comparisonRepository.getIndex();
60
+ const resultsPath = await comparisonRepository.finalizeComparison();
61
+ // Return the summary data
62
+ return {
63
+ status: "completed",
64
+ overallResult: finalIndex.metadata.pagesWithDifferences > 0 ? "fail" : "pass",
65
+ beforeSnapshotId: config.beforeSnapshotId,
66
+ afterSnapshotId: config.afterSnapshotId,
67
+ rulesUsedIdentifier,
68
+ timestamp: finalIndex.metadata.timestamp,
69
+ totalPagesCompared: finalIndex.metadata.totalPages,
70
+ pagesWithDifferences: finalIndex.metadata.pagesWithDifferences,
71
+ pagesWithErrors,
72
+ newUrls,
73
+ removedUrls,
74
+ comparisonProcessErrors: [],
75
+ comparisonId: config.comparisonName,
76
+ durationMs: Date.now() - startTime,
77
+ summaryFilePath: path.join(resultsPath, "index.json"),
78
+ resultsPath,
79
+ };
80
+ }
81
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/compare/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { CheerioCrawler, Dataset } from "crawlee";
2
+ export function createCheerioCrawler(config) {
3
+ const { maxDepth, includePatterns, excludePatterns } = config;
4
+ let datasetPromise = Dataset.open();
5
+ return new CheerioCrawler({
6
+ maxRequestsPerCrawl: config.maxRequests,
7
+ maxConcurrency: config.maxConcurrency,
8
+ maxRequestRetries: 3,
9
+ requestHandlerTimeoutSecs: 30,
10
+ async requestHandler({ request, response, body, $, enqueueLinks }) {
11
+ const title = $("title").first().text() || undefined;
12
+ const pageSnapshot = {
13
+ url: request.url,
14
+ finalUrl: response.url || request.url,
15
+ content: body.toString(),
16
+ statusCode: response.statusCode || 0,
17
+ headers: Object.fromEntries(Object.entries(response.headers).map(([key, value]) => [
18
+ key,
19
+ Array.isArray(value) ? value.join(", ") : value || "",
20
+ ])),
21
+ title,
22
+ };
23
+ const dataset = await datasetPromise;
24
+ await dataset.pushData(pageSnapshot);
25
+ const depth = request.userData.depth || 0;
26
+ if (depth < (maxDepth || Infinity)) {
27
+ await enqueueLinks({
28
+ globs: includePatterns || [],
29
+ exclude: excludePatterns || [],
30
+ });
31
+ }
32
+ },
33
+ failedRequestHandler: async ({ request, error }) => {
34
+ const crawlError = {
35
+ url: request.url,
36
+ message: error instanceof Error ? error.message : String(error),
37
+ statusCode: error instanceof Error && "statusCode" in error
38
+ ? error.statusCode
39
+ : undefined,
40
+ };
41
+ const dataset = await datasetPromise;
42
+ await dataset.pushData({ type: "error", ...crawlError });
43
+ },
44
+ });
45
+ }
46
+ //# sourceMappingURL=cheerio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cheerio.js","sourceRoot":"","sources":["../../../../src/core/crawler/implementations/cheerio.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAQlD,MAAM,UAAU,oBAAoB,CAAC,MAAqB;IACxD,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC;IAE9D,IAAI,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAEpC,OAAO,IAAI,cAAc,CAAC;QACxB,mBAAmB,EAAE,MAAM,CAAC,WAAW;QACvC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,iBAAiB,EAAE,CAAC;QACpB,yBAAyB,EAAE,EAAE;QAC7B,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE;YAC/D,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;YACrD,MAAM,YAAY,GAAiB;gBACjC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,QAAQ,EAAE,QAAQ,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG;gBACrC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE;gBACxB,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,CAAC;gBACpC,OAAO,EAAE,MAAM,CAAC,WAAW,CACzB,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;oBACrD,GAAG;oBACH,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE;iBACtD,CAAC,CACH;gBACD,KAAK;aACN,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACrC,MAAM,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAErC,MAAM,KAAK,GAAI,OAAO,CAAC,QAAQ,CAAC,KAAgB,IAAI,CAAC,CAAC;YACtD,IAAI,KAAK,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,CAAC;gBACnC,MAAM,YAAY,CAAC;oBACjB,KAAK,EAAE,eAAe,IAAI,EAAE;oBAC5B,OAAO,EAAE,eAAe,IAAI,EAAE;iBAC/B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,oBAAoB,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;YACjD,MAAM,UAAU,GAAe;gBAC7B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC/D,UAAU,EACR,KAAK,YAAY,KAAK,IAAI,YAAY,IAAI,KAAK;oBAC7C,CAAC,CAAE,KAAK,CAAC,UAAqB;oBAC9B,CAAC,CAAC,SAAS;aAChB,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACrC,MAAM,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;QAC3D,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { Dataset, PlaywrightCrawler } from "crawlee";
2
+ export function createPlaywrightCrawler(config) {
3
+ const { maxDepth, includePatterns, excludePatterns } = config;
4
+ let datasetPromise = Dataset.open();
5
+ return new PlaywrightCrawler({
6
+ maxRequestsPerCrawl: config.maxRequests,
7
+ maxConcurrency: config.maxConcurrency,
8
+ maxRequestRetries: 3,
9
+ requestHandlerTimeoutSecs: 30,
10
+ async requestHandler({ request, response, page, enqueueLinks }) {
11
+ const content = await page.content();
12
+ const title = await page.title();
13
+ const pageSnapshot = {
14
+ url: request.url,
15
+ finalUrl: response?.url() || request.url,
16
+ content,
17
+ title,
18
+ statusCode: response?.status() || 0,
19
+ headers: response?.headers() || {},
20
+ };
21
+ const dataset = await datasetPromise;
22
+ await dataset.pushData(pageSnapshot);
23
+ const depth = request.userData.depth || 0;
24
+ if (depth < (maxDepth || Infinity)) {
25
+ await enqueueLinks({
26
+ globs: includePatterns || [],
27
+ exclude: excludePatterns || [],
28
+ });
29
+ }
30
+ },
31
+ failedRequestHandler: async ({ request, error }) => {
32
+ const crawlError = {
33
+ url: request.url,
34
+ message: error instanceof Error ? error.message : String(error),
35
+ statusCode: error instanceof Error && "statusCode" in error
36
+ ? error.statusCode
37
+ : undefined,
38
+ };
39
+ const dataset = await datasetPromise;
40
+ await dataset.pushData({ type: "error", ...crawlError });
41
+ },
42
+ });
43
+ }
44
+ //# sourceMappingURL=playwright.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright.js","sourceRoot":"","sources":["../../../../src/core/crawler/implementations/playwright.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAQrD,MAAM,UAAU,uBAAuB,CACrC,MAAqB;IAErB,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC;IAE9D,IAAI,cAAc,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAEpC,OAAO,IAAI,iBAAiB,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC,WAAW;QACvC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,iBAAiB,EAAE,CAAC;QACpB,yBAAyB,EAAE,EAAE;QAC7B,KAAK,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE;YAC5D,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACjC,MAAM,YAAY,GAAiB;gBACjC,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,OAAO,CAAC,GAAG;gBACxC,OAAO;gBACP,KAAK;gBACL,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC;gBACnC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;aACnC,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACrC,MAAM,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAErC,MAAM,KAAK,GAAI,OAAO,CAAC,QAAQ,CAAC,KAAgB,IAAI,CAAC,CAAC;YACtD,IAAI,KAAK,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,EAAE,CAAC;gBACnC,MAAM,YAAY,CAAC;oBACjB,KAAK,EAAE,eAAe,IAAI,EAAE;oBAC5B,OAAO,EAAE,eAAe,IAAI,EAAE;iBAC/B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,oBAAoB,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;YACjD,MAAM,UAAU,GAAe;gBAC7B,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC/D,UAAU,EACR,KAAK,YAAY,KAAK,IAAI,YAAY,IAAI,KAAK;oBAC7C,CAAC,CAAE,KAAK,CAAC,UAAqB;oBAC9B,CAAC,CAAC,SAAS;aAChB,CAAC;YACF,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC;YACrC,MAAM,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;QAC3D,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { Configuration, Dataset, purgeDefaultStorages } from "crawlee";
2
+ import { logger } from "../../lib/logger";
3
+ import { createCheerioCrawler } from "./implementations/cheerio";
4
+ import { createPlaywrightCrawler } from "./implementations/playwright";
5
+ export class BreakcheckCrawler {
6
+ config;
7
+ crawler;
8
+ errors = [];
9
+ constructor(config) {
10
+ this.config = config;
11
+ this.crawler = this.createCrawler();
12
+ }
13
+ createCrawler() {
14
+ switch (this.config.crawlerType) {
15
+ case "cheerio":
16
+ return createCheerioCrawler(this.config);
17
+ case "playwright":
18
+ return createPlaywrightCrawler(this.config);
19
+ default:
20
+ const _exhaustiveCheck = this.config.crawlerType;
21
+ throw new Error(`Unsupported crawler type: ${this.config.crawlerType}`);
22
+ }
23
+ }
24
+ async crawl() {
25
+ try {
26
+ await purgeDefaultStorages();
27
+ this.errors = [];
28
+ await this.crawler.run([this.config.baseUrl]);
29
+ // Open the dataset and scan for error items only
30
+ const dataset = await Dataset.open();
31
+ await dataset.forEach((item) => {
32
+ if (item.type === "error") {
33
+ this.errors.push({
34
+ url: item.url,
35
+ message: item.error,
36
+ statusCode: item.statusCode,
37
+ });
38
+ }
39
+ });
40
+ const datasetInfo = await dataset.getInfo();
41
+ let actualDatasetName = datasetInfo?.name ||
42
+ Configuration.getGlobalConfig().get("defaultDatasetId");
43
+ if (!actualDatasetName)
44
+ actualDatasetName = "";
45
+ return {
46
+ datasetName: actualDatasetName,
47
+ errors: this.errors,
48
+ };
49
+ }
50
+ catch (error) {
51
+ logger.error({
52
+ error,
53
+ baseUrl: this.config.baseUrl,
54
+ crawlerType: this.config.crawlerType,
55
+ errors: this.errors,
56
+ }, "Crawling failed");
57
+ throw new Error(`Crawling failed: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/crawler/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAO1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,MAAM,OAAO,iBAAiB;IACpB,MAAM,CAAgB;IACtB,OAAO,CAAkB;IACzB,MAAM,GAAiB,EAAE,CAAC;IAElC,YAAY,MAAqB;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;IACtC,CAAC;IAEO,aAAa;QACnB,QAAQ,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAChC,KAAK,SAAS;gBACZ,OAAO,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC3C,KAAK,YAAY;gBACf,OAAO,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9C;gBACE,MAAM,gBAAgB,GAAU,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC;gBACxD,MAAM,IAAI,KAAK,CAAC,6BAA6B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,oBAAoB,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC9C,iDAAiD;YACjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACrC,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,IAAS,EAAE,EAAE;gBAClC,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC1B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;wBACf,GAAG,EAAE,IAAI,CAAC,GAAG;wBACb,OAAO,EAAE,IAAI,CAAC,KAAK;wBACnB,UAAU,EAAE,IAAI,CAAC,UAAU;qBAC5B,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YAC5C,IAAI,iBAAiB,GACnB,WAAW,EAAE,IAAI;gBACjB,aAAa,CAAC,eAAe,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YAC1D,IAAI,CAAC,iBAAiB;gBAAE,iBAAiB,GAAG,EAAE,CAAC;YAC/C,OAAO;gBACL,WAAW,EAAE,iBAAiB;gBAC9B,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CACV;gBACE,KAAK;gBACL,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;gBAC5B,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;gBACpC,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,EACD,iBAAiB,CAClB,CAAC;YACF,MAAM,IAAI,KAAK,CACb,oBACE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE,CACH,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,251 @@
1
+ import { createToken, CstParser, Lexer } from "chevrotain";
2
+ import { readFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { logger } from "../../lib/logger";
5
+ import { findRootDir } from "../../lib/root";
6
+ // --- LEXER ---
7
+ const Css = createToken({ name: "Css", pattern: /css:/i });
8
+ const Do = createToken({ name: "Do", pattern: /do:/i });
9
+ const DoBlock = createToken({ name: "DoBlock", pattern: /do/i });
10
+ const End = createToken({ name: "End", pattern: /end/i });
11
+ const Include = createToken({ name: "Include", pattern: /include/i });
12
+ const Exclude = createToken({ name: "Exclude", pattern: /exclude/i });
13
+ const RemoveAttr = createToken({ name: "RemoveAttr", pattern: /remove_attr/i });
14
+ const RewriteAttr = createToken({
15
+ name: "RewriteAttr",
16
+ pattern: /rewrite_attr/i,
17
+ });
18
+ const RewriteContent = createToken({
19
+ name: "RewriteContent",
20
+ pattern: /rewrite_content/i,
21
+ });
22
+ const Attr = createToken({ name: "Attr", pattern: /attr:/i });
23
+ const Regex = createToken({ name: "Regex", pattern: /regex:/i });
24
+ const Replace = createToken({ name: "Replace", pattern: /replace:/i });
25
+ const ContentRegex = createToken({
26
+ name: "ContentRegex",
27
+ pattern: /content_regex:/i,
28
+ });
29
+ const StringLiteral = createToken({
30
+ name: "StringLiteral",
31
+ pattern: /"[^"]*"/,
32
+ });
33
+ const Selector = createToken({
34
+ name: "Selector",
35
+ pattern: /[\w.#\[\]*='"-]+/,
36
+ });
37
+ const Comment = createToken({
38
+ name: "Comment",
39
+ pattern: /--[^\n\r]*/,
40
+ group: Lexer.SKIPPED,
41
+ });
42
+ const WhiteSpace = createToken({
43
+ name: "WhiteSpace",
44
+ pattern: /\s+/,
45
+ group: Lexer.SKIPPED,
46
+ });
47
+ const allTokens = [
48
+ WhiteSpace,
49
+ Comment,
50
+ Css,
51
+ Do,
52
+ DoBlock,
53
+ End,
54
+ Include,
55
+ Exclude,
56
+ RemoveAttr,
57
+ RewriteAttr,
58
+ RewriteContent,
59
+ Attr,
60
+ Regex,
61
+ Replace,
62
+ ContentRegex,
63
+ StringLiteral,
64
+ Selector,
65
+ ];
66
+ const RulesLexer = new Lexer(allTokens, {
67
+ // Longer matches should be preferred over shorter ones
68
+ ensureOptimizations: true,
69
+ });
70
+ // --- PARSER ---
71
+ class RulesParser extends CstParser {
72
+ constructor() {
73
+ super(allTokens);
74
+ this.performSelfAnalysis();
75
+ }
76
+ ruleset = this.RULE("ruleset", () => {
77
+ this.MANY(() => {
78
+ this.SUBRULE(this.rule);
79
+ });
80
+ });
81
+ rule = this.RULE("rule", () => {
82
+ this.CONSUME(Css);
83
+ this.CONSUME(Selector);
84
+ this.OR([
85
+ { ALT: () => this.SUBRULE(this.singleAction) },
86
+ { ALT: () => this.SUBRULE(this.actionBlock) },
87
+ ]);
88
+ });
89
+ singleAction = this.RULE("singleAction", () => {
90
+ this.CONSUME(Do);
91
+ this.SUBRULE(this.action);
92
+ });
93
+ actionBlock = this.RULE("actionBlock", () => {
94
+ this.CONSUME(DoBlock);
95
+ this.MANY(() => {
96
+ this.SUBRULE(this.action);
97
+ });
98
+ this.CONSUME(End);
99
+ });
100
+ action = this.RULE("action", () => {
101
+ this.OR([
102
+ { ALT: () => this.SUBRULE(this.includeAction) },
103
+ { ALT: () => this.SUBRULE(this.excludeAction) },
104
+ { ALT: () => this.SUBRULE(this.removeAttrAction) },
105
+ { ALT: () => this.SUBRULE(this.rewriteAttrAction) },
106
+ { ALT: () => this.SUBRULE(this.rewriteContentAction) },
107
+ ]);
108
+ });
109
+ includeAction = this.RULE("includeAction", () => {
110
+ this.CONSUME(Include);
111
+ this.OPTION(() => this.SUBRULE(this.contentRegexModifier));
112
+ });
113
+ excludeAction = this.RULE("excludeAction", () => {
114
+ this.CONSUME(Exclude);
115
+ this.OPTION(() => this.SUBRULE(this.contentRegexModifier));
116
+ });
117
+ removeAttrAction = this.RULE("removeAttrAction", () => {
118
+ this.CONSUME(RemoveAttr);
119
+ this.CONSUME(Attr);
120
+ this.CONSUME(StringLiteral);
121
+ });
122
+ rewriteAttrAction = this.RULE("rewriteAttrAction", () => {
123
+ this.CONSUME(RewriteAttr);
124
+ this.CONSUME(Attr);
125
+ this.CONSUME1(StringLiteral, { LABEL: "attr" });
126
+ this.CONSUME(Regex);
127
+ this.CONSUME2(StringLiteral, { LABEL: "regex" });
128
+ this.CONSUME(Replace);
129
+ this.CONSUME3(StringLiteral, { LABEL: "replace" });
130
+ });
131
+ rewriteContentAction = this.RULE("rewriteContentAction", () => {
132
+ this.CONSUME(RewriteContent);
133
+ this.CONSUME(Regex);
134
+ this.CONSUME1(StringLiteral, { LABEL: "regex" });
135
+ this.CONSUME(Replace);
136
+ this.CONSUME2(StringLiteral, { LABEL: "replace" });
137
+ });
138
+ contentRegexModifier = this.RULE("contentRegexModifier", () => {
139
+ this.CONSUME(ContentRegex);
140
+ this.CONSUME(StringLiteral);
141
+ });
142
+ }
143
+ const parser = new RulesParser();
144
+ // --- VISITOR ---
145
+ class CstToAstVisitor extends parser.getBaseCstVisitorConstructor() {
146
+ constructor() {
147
+ super();
148
+ this.validateVisitor();
149
+ }
150
+ ruleset(ctx) {
151
+ const rules = ctx.rule?.map((r) => this.visit(r)) || [];
152
+ return { name: "ruleset", rules };
153
+ }
154
+ rule(ctx) {
155
+ const selector = ctx.Selector[0].image.trim();
156
+ const actionNode = ctx.singleAction || ctx.actionBlock;
157
+ if (!actionNode) {
158
+ throw new Error("Invalid 'rule' node in CST: No action node found.");
159
+ }
160
+ const actions = this.visit(actionNode[0]);
161
+ return { selector, actions };
162
+ }
163
+ singleAction(ctx) {
164
+ return [this.visit(ctx.action[0])];
165
+ }
166
+ actionBlock(ctx) {
167
+ return ctx.action?.map((a) => this.visit(a)) || [];
168
+ }
169
+ action(ctx) {
170
+ // Find which specific action was parsed (e.g., includeAction, excludeAction)
171
+ const actionAlternative = ctx.includeAction ||
172
+ ctx.excludeAction ||
173
+ ctx.removeAttrAction ||
174
+ ctx.rewriteAttrAction ||
175
+ ctx.rewriteContentAction;
176
+ if (!actionAlternative) {
177
+ logger.error({ context: ctx }, "Invalid 'action' node in CST: No action alternative found");
178
+ throw new Error("Invalid 'action' node in CST: No action alternative found.");
179
+ }
180
+ const actionNode = actionAlternative[0];
181
+ // Visit that specific action's node
182
+ return this.visit(actionNode);
183
+ }
184
+ includeAction(ctx) {
185
+ const modifiers = ctx.contentRegexModifier
186
+ ? this.visit(ctx.contentRegexModifier[0])
187
+ : {};
188
+ return { action: "include", modifiers };
189
+ }
190
+ excludeAction(ctx) {
191
+ const modifiers = ctx.contentRegexModifier
192
+ ? this.visit(ctx.contentRegexModifier[0])
193
+ : {};
194
+ return { action: "exclude", modifiers };
195
+ }
196
+ removeAttrAction(ctx) {
197
+ const attr = ctx.StringLiteral[0].image.slice(1, -1);
198
+ return { action: "remove_attr", modifiers: { attr } };
199
+ }
200
+ rewriteAttrAction(ctx) {
201
+ const attr = ctx.attr[0].image.slice(1, -1);
202
+ const regex = ctx.regex[0].image.slice(1, -1);
203
+ const replace = ctx.replace[0].image.slice(1, -1);
204
+ return { action: "rewrite_attr", modifiers: { attr, regex, replace } };
205
+ }
206
+ rewriteContentAction(ctx) {
207
+ const regex = ctx.regex[0].image.slice(1, -1);
208
+ const replace = ctx.replace[0].image.slice(1, -1);
209
+ return { action: "rewrite_content", modifiers: { regex, replace } };
210
+ }
211
+ contentRegexModifier(ctx) {
212
+ const content_regex = ctx.StringLiteral[0].image.slice(1, -1);
213
+ return { content_regex };
214
+ }
215
+ }
216
+ const visitor = new CstToAstVisitor();
217
+ // --- Main processing function ---
218
+ export async function processRulesDsl(rulesetName) {
219
+ const rootDir = await findRootDir();
220
+ const rulesPath = join(rootDir, "rules", rulesetName, "rules.breakcheck");
221
+ let rulesContent;
222
+ try {
223
+ rulesContent = readFileSync(rulesPath, "utf-8");
224
+ }
225
+ catch (error) {
226
+ const shortPath = rulesPath.replace(rootDir, "").replace(/^\//, "");
227
+ logger.error({ error, rulesPath: shortPath }, "Rules file not found");
228
+ throw new Error(`Rules file not found: ${shortPath}`);
229
+ }
230
+ const lexResult = RulesLexer.tokenize(rulesContent);
231
+ if (lexResult.errors.length > 0) {
232
+ const errors = lexResult.errors
233
+ .map((err) => `Lexical error at line ${err.line}, column ${err.column}: ${err.message}`)
234
+ .join("\n");
235
+ logger.error({ errors: lexResult.errors, rulesetName }, "Lexical errors in rules file");
236
+ throw new Error(`Lexical errors in rules file:\n${errors}`);
237
+ }
238
+ parser.input = lexResult.tokens;
239
+ const cst = parser.ruleset();
240
+ if (parser.errors.length > 0) {
241
+ const errors = parser.errors
242
+ .map((err) => `Syntax error at line ${err.token.startLine}, column ${err.token.startColumn}: ${err.message}`)
243
+ .join("\n");
244
+ logger.error({ errors: parser.errors, rulesetName }, "Syntax errors in rules file");
245
+ throw new Error(`Syntax errors in rules file:\n${errors}`);
246
+ }
247
+ // Use the visitor to transform the CST to the final AST
248
+ const result = visitor.visit(cst);
249
+ return { ...result, name: rulesetName };
250
+ }
251
+ //# sourceMappingURL=RulesDsl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RulesDsl.js","sourceRoot":"","sources":["../../../src/core/rules/RulesDsl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAW,SAAS,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,gBAAgB;AAChB,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AAC3D,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AACxD,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AACjE,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC1D,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;AACtE,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;AACtE,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;AAChF,MAAM,WAAW,GAAG,WAAW,CAAC;IAC9B,IAAI,EAAE,aAAa;IACnB,OAAO,EAAE,eAAe;CACzB,CAAC,CAAC;AACH,MAAM,cAAc,GAAG,WAAW,CAAC;IACjC,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,kBAAkB;CAC5B,CAAC,CAAC;AACH,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC9D,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;AACjE,MAAM,OAAO,GAAG,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;AACvE,MAAM,YAAY,GAAG,WAAW,CAAC;IAC/B,IAAI,EAAE,cAAc;IACpB,OAAO,EAAE,iBAAiB;CAC3B,CAAC,CAAC;AACH,MAAM,aAAa,GAAG,WAAW,CAAC;IAChC,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,SAAS;CACnB,CAAC,CAAC;AACH,MAAM,QAAQ,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,kBAAkB;CAC5B,CAAC,CAAC;AACH,MAAM,OAAO,GAAG,WAAW,CAAC;IAC1B,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,YAAY;IACrB,KAAK,EAAE,KAAK,CAAC,OAAO;CACrB,CAAC,CAAC;AACH,MAAM,UAAU,GAAG,WAAW,CAAC;IAC7B,IAAI,EAAE,YAAY;IAClB,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,KAAK,CAAC,OAAO;CACrB,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG;IAChB,UAAU;IACV,OAAO;IACP,GAAG;IACH,EAAE;IACF,OAAO;IACP,GAAG;IACH,OAAO;IACP,OAAO;IACP,UAAU;IACV,WAAW;IACX,cAAc;IACd,IAAI;IACJ,KAAK;IACL,OAAO;IACP,YAAY;IACZ,aAAa;IACb,QAAQ;CACT,CAAC;AACF,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE;IACtC,uDAAuD;IACvD,mBAAmB,EAAE,IAAI;CAC1B,CAAC,CAAC;AAEH,iBAAiB;AACjB,MAAM,WAAY,SAAQ,SAAS;IACjC;QACE,KAAK,CAAC,SAAS,CAAC,CAAC;QACjB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAEM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;QACzC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACb,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEK,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;QACpC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,CAAC,EAAE,CAAC;YACN,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE;YAC9C,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;SAC9C,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEK,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,EAAE;QACpD,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACjB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEK,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE;QAClD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACb,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEK,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxC,IAAI,CAAC,EAAE,CAAC;YACN,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;YAC/C,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE;YAC/C,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE;YAClD,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,EAAE;YACnD,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE;SACvD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEK,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE;QACtD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEK,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE;QACtD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEK,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAC5D,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEK,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC9D,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEK,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpE,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEK,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC3B,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;CACJ;AAED,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;AAEjC,kBAAkB;AAClB,MAAM,eAAgB,SAAQ,MAAM,CAAC,4BAA4B,EAAE;IACjE;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAED,OAAO,CAAC,GAAyB;QAC/B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,GAIJ;QACC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC9C,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,WAAW,CAAC;QACvD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAC/B,CAAC;IAED,YAAY,CAAC,GAA0B;QACrC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,WAAW,CAAC,GAA2B;QACrC,OAAO,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,CAAC,GAMN;QACC,6EAA6E;QAC7E,MAAM,iBAAiB,GACrB,GAAG,CAAC,aAAa;YACjB,GAAG,CAAC,aAAa;YACjB,GAAG,CAAC,gBAAgB;YACpB,GAAG,CAAC,iBAAiB;YACrB,GAAG,CAAC,oBAAoB,CAAC;QAE3B,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,MAAM,CAAC,KAAK,CACV,EAAE,OAAO,EAAE,GAAG,EAAE,EAChB,2DAA2D,CAC5D,CAAC;YACF,MAAM,IAAI,KAAK,CACb,4DAA4D,CAC7D,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACxC,oCAAoC;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAChC,CAAC;IAED,aAAa,CAAC,GAAyC;QACrD,MAAM,SAAS,GAAG,GAAG,CAAC,oBAAoB;YACxC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;YACzC,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IAED,aAAa,CAAC,GAAyC;QACrD,MAAM,SAAS,GAAG,GAAG,CAAC,oBAAoB;YACxC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;YACzC,CAAC,CAAC,EAAE,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC1C,CAAC;IAED,gBAAgB,CAAC,GAA6B;QAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC;IACxD,CAAC;IAED,iBAAiB,CAAC,GAIjB;QACC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;IACzE,CAAC;IAED,oBAAoB,CAAC,GAAqC;QACxD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAClD,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;IACtE,CAAC;IAED,oBAAoB,CAAC,GAA6B;QAGhD,MAAM,aAAa,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC9D,OAAO,EAAE,aAAa,EAAE,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;AAEtC,mCAAmC;AACnC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAAmB;IACvD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC;IAC1E,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpE,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,sBAAsB,CAAC,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,yBAAyB,SAAS,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IACpD,IAAI,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM;aAC5B,GAAG,CACF,CAAC,GAAG,EAAE,EAAE,CACN,yBAAyB,GAAG,CAAC,IAAI,YAAY,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,CAC5E;aACA,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,CAAC,KAAK,CACV,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,WAAW,EAAE,EACzC,8BAA8B,CAC/B,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,kCAAkC,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,CAAC,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC;IAChC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAC7B,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM;aACzB,GAAG,CACF,CAAC,GAAG,EAAE,EAAE,CACN,wBAAwB,GAAG,CAAC,KAAK,CAAC,SAAS,YAAY,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,GAAG,CAAC,OAAO,EAAE,CACjG;aACA,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,CAAC,KAAK,CACV,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,EACtC,6BAA6B,CAC9B,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,iCAAiC,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,wDAAwD;IACxD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;IAC7C,OAAO,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAC1C,CAAC"}