@taodigital/crawler 0.0.0-stage → 1.2.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.
- package/Dockerfile +82 -0
- package/README.md +10 -2
- package/dist/accessibility-auditor.d.ts +29 -0
- package/dist/accessibility-auditor.js +57 -0
- package/dist/accessibility-auditor.js.map +7 -0
- package/dist/accessibility-tree-crawler.d.ts +44 -0
- package/dist/accessibility-tree-crawler.js +156 -0
- package/dist/accessibility-tree-crawler.js.map +7 -0
- package/dist/advanced-ui-probes.d.ts +47 -0
- package/dist/advanced-ui-probes.js +310 -0
- package/dist/advanced-ui-probes.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +250 -0
- package/dist/ai-login.js.map +7 -0
- package/dist/auth-token-rotator.d.ts +47 -0
- package/dist/auth-token-rotator.js +58 -0
- package/dist/auth-token-rotator.js.map +7 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +348 -0
- package/dist/auth.js.map +7 -0
- package/dist/browser-extension.d.ts +37 -0
- package/dist/browser-extension.js +113 -0
- package/dist/browser-extension.js.map +7 -0
- package/dist/captcha-solver.d.ts +16 -0
- package/dist/captcha-solver.js +105 -0
- package/dist/captcha-solver.js.map +7 -0
- package/dist/color-contrast.d.ts +30 -0
- package/dist/color-contrast.js +78 -0
- package/dist/color-contrast.js.map +7 -0
- package/dist/content-chunker.d.ts +18 -0
- package/dist/content-chunker.js +152 -0
- package/dist/content-chunker.js.map +7 -0
- package/dist/content-dedup.d.ts +23 -0
- package/dist/content-dedup.js +60 -0
- package/dist/content-dedup.js.map +7 -0
- package/dist/cookie-consent.d.ts +25 -0
- package/dist/cookie-consent.js +78 -0
- package/dist/cookie-consent.js.map +7 -0
- package/dist/cookie-jar.d.ts +20 -0
- package/dist/cookie-jar.js +61 -0
- package/dist/cookie-jar.js.map +7 -0
- package/dist/crawl-session-manager.d.ts +44 -0
- package/dist/crawl-session-manager.js +145 -0
- package/dist/crawl-session-manager.js.map +7 -0
- package/dist/crawler.d.ts +233 -0
- package/dist/crawler.js +6239 -0
- package/dist/crawler.js.map +7 -0
- package/dist/crud-interaction.d.ts +31 -0
- package/dist/crud-interaction.js +264 -0
- package/dist/crud-interaction.js.map +7 -0
- package/dist/custom-js-injector.d.ts +42 -0
- package/dist/custom-js-injector.js +50 -0
- package/dist/custom-js-injector.js.map +7 -0
- package/dist/dark-mode-capture.d.ts +23 -0
- package/dist/dark-mode-capture.js +75 -0
- package/dist/dark-mode-capture.js.map +7 -0
- package/dist/dead-link-detector.d.ts +37 -0
- package/dist/dead-link-detector.js +50 -0
- package/dist/dead-link-detector.js.map +7 -0
- package/dist/dropdown-enumerator.d.ts +20 -0
- package/dist/dropdown-enumerator.js +230 -0
- package/dist/dropdown-enumerator.js.map +7 -0
- package/dist/element-hash.d.ts +9 -0
- package/dist/element-hash.js +9 -0
- package/dist/element-hash.js.map +7 -0
- package/dist/email-otp.d.ts +42 -0
- package/dist/email-otp.js +177 -0
- package/dist/email-otp.js.map +7 -0
- package/dist/engine-router.d.ts +47 -0
- package/dist/engine-router.js +94 -0
- package/dist/engine-router.js.map +7 -0
- package/dist/export/bulk-export.d.ts +26 -0
- package/dist/export/bulk-export.js +76 -0
- package/dist/export/bulk-export.js.map +7 -0
- package/dist/export/vector-export.d.ts +76 -0
- package/dist/export/vector-export.js +85 -0
- package/dist/export/vector-export.js.map +7 -0
- package/dist/extractors/advanced.d.ts +64 -0
- package/dist/extractors/advanced.js +265 -0
- package/dist/extractors/advanced.js.map +7 -0
- package/dist/extractors/artifact-envelope.d.ts +63 -0
- package/dist/extractors/artifact-envelope.js +63 -0
- package/dist/extractors/artifact-envelope.js.map +7 -0
- package/dist/extractors/branding-extractor.d.ts +46 -0
- package/dist/extractors/branding-extractor.js +104 -0
- package/dist/extractors/branding-extractor.js.map +7 -0
- package/dist/extractors/summary-extractor.d.ts +10 -0
- package/dist/extractors/summary-extractor.js +36 -0
- package/dist/extractors/summary-extractor.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +42 -0
- package/dist/failure-analysis-agent.js.map +7 -0
- package/dist/feature-flag-detector.d.ts +30 -0
- package/dist/feature-flag-detector.js +55 -0
- package/dist/feature-flag-detector.js.map +7 -0
- package/dist/filter-pagination-crawler.d.ts +52 -0
- package/dist/filter-pagination-crawler.js +299 -0
- package/dist/filter-pagination-crawler.js.map +7 -0
- package/dist/form-detector.d.ts +34 -0
- package/dist/form-detector.js +58 -0
- package/dist/form-detector.js.map +7 -0
- package/dist/frontier.d.ts +40 -0
- package/dist/frontier.js +103 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +36 -0
- package/dist/gap-pass.js +115 -0
- package/dist/gap-pass.js.map +7 -0
- package/dist/graphql-detector.d.ts +39 -0
- package/dist/graphql-detector.js +86 -0
- package/dist/graphql-detector.js.map +7 -0
- package/dist/http-engine.d.ts +80 -0
- package/dist/http-engine.js +318 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +116 -0
- package/dist/incremental-diff.js +137 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +7 -0
- package/dist/input-fuzzer.d.ts +30 -0
- package/dist/input-fuzzer.js +105 -0
- package/dist/input-fuzzer.js.map +7 -0
- package/dist/interactive-probe.d.ts +144 -0
- package/dist/interactive-probe.js +865 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +297 -0
- package/dist/job-queue.js +3812 -0
- package/dist/job-queue.js.map +7 -0
- package/dist/js-coverage.d.ts +29 -0
- package/dist/js-coverage.js +56 -0
- package/dist/js-coverage.js.map +7 -0
- package/dist/js-router-detector.d.ts +17 -0
- package/dist/js-router-detector.js +90 -0
- package/dist/js-router-detector.js.map +7 -0
- package/dist/language-detector.d.ts +22 -0
- package/dist/language-detector.js +57 -0
- package/dist/language-detector.js.map +7 -0
- package/dist/link-classifier.d.ts +23 -0
- package/dist/link-classifier.js +40 -0
- package/dist/link-classifier.js.map +7 -0
- package/dist/live-view.d.ts +36 -0
- package/dist/live-view.js +105 -0
- package/dist/live-view.js.map +7 -0
- package/dist/locator-extractor.d.ts +46 -0
- package/dist/locator-extractor.js +220 -0
- package/dist/locator-extractor.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +7 -0
- package/dist/markdown-extract.d.ts +8 -0
- package/dist/markdown-extract.js +54 -0
- package/dist/markdown-extract.js.map +7 -0
- package/dist/multi-context-handler.d.ts +55 -0
- package/dist/multi-context-handler.js +182 -0
- package/dist/multi-context-handler.js.map +7 -0
- package/dist/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.js.map +7 -0
- package/dist/nested-window-crawler.d.ts +41 -0
- package/dist/nested-window-crawler.js +172 -0
- package/dist/nested-window-crawler.js.map +7 -0
- package/dist/network-action-correlator.d.ts +47 -0
- package/dist/network-action-correlator.js +209 -0
- package/dist/network-action-correlator.js.map +7 -0
- package/dist/network-observer.d.ts +49 -0
- package/dist/network-observer.js +149 -0
- package/dist/network-observer.js.map +7 -0
- package/dist/network-throttle.d.ts +25 -0
- package/dist/network-throttle.js +34 -0
- package/dist/network-throttle.js.map +7 -0
- package/dist/openapi-generator.d.ts +49 -0
- package/dist/openapi-generator.js +89 -0
- package/dist/openapi-generator.js.map +7 -0
- package/dist/pdf-extractor.d.ts +28 -0
- package/dist/pdf-extractor.js +81 -0
- package/dist/pdf-extractor.js.map +7 -0
- package/dist/performance-metrics-collector.d.ts +50 -0
- package/dist/performance-metrics-collector.js +155 -0
- package/dist/performance-metrics-collector.js.map +7 -0
- package/dist/performance-metrics.d.ts +52 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +50 -0
- package/dist/phash.js +136 -0
- package/dist/phash.js.map +7 -0
- package/dist/pii-scrubber.d.ts +21 -0
- package/dist/pii-scrubber.js +75 -0
- package/dist/pii-scrubber.js.map +7 -0
- package/dist/playwright-recorder.d.ts +39 -0
- package/dist/playwright-recorder.js +123 -0
- package/dist/playwright-recorder.js.map +7 -0
- package/dist/policies/crawl-safety.d.ts +9 -0
- package/dist/policies/crawl-safety.js +41 -0
- package/dist/policies/crawl-safety.js.map +7 -0
- package/dist/profiles/domain-profile.d.ts +19 -0
- package/dist/profiles/domain-profile.js +160 -0
- package/dist/profiles/domain-profile.js.map +7 -0
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +165 -0
- package/dist/proxy.js.map +7 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.js +64 -0
- package/dist/rate-limiter.js.map +7 -0
- package/dist/redirect-tracker.d.ts +37 -0
- package/dist/redirect-tracker.js +65 -0
- package/dist/redirect-tracker.js.map +7 -0
- package/dist/redis-frontier.d.ts +48 -0
- package/dist/redis-frontier.js +126 -0
- package/dist/redis-frontier.js.map +7 -0
- package/dist/redis-queue.d.ts +48 -0
- package/dist/redis-queue.js +165 -0
- package/dist/redis-queue.js.map +7 -0
- package/dist/response-cache.d.ts +38 -0
- package/dist/response-cache.js +140 -0
- package/dist/response-cache.js.map +7 -0
- package/dist/responsive-detector.d.ts +29 -0
- package/dist/responsive-detector.js +46 -0
- package/dist/responsive-detector.js.map +7 -0
- package/dist/retry-policy.d.ts +20 -0
- package/dist/retry-policy.js +54 -0
- package/dist/retry-policy.js.map +7 -0
- package/dist/robots-txt.d.ts +9 -0
- package/dist/robots-txt.js +77 -0
- package/dist/robots-txt.js.map +7 -0
- package/dist/screen-reader.d.ts +15 -0
- package/dist/screen-reader.js +48 -0
- package/dist/screen-reader.js.map +7 -0
- package/dist/screenshot-masker.d.ts +28 -0
- package/dist/screenshot-masker.js +62 -0
- package/dist/screenshot-masker.js.map +7 -0
- package/dist/seo-checker.d.ts +33 -0
- package/dist/seo-checker.js +91 -0
- package/dist/seo-checker.js.map +7 -0
- package/dist/service-worker-interceptor.d.ts +36 -0
- package/dist/service-worker-interceptor.js +136 -0
- package/dist/service-worker-interceptor.js.map +7 -0
- package/dist/session-capture.d.ts +39 -0
- package/dist/session-capture.js +274 -0
- package/dist/session-capture.js.map +7 -0
- package/dist/session-export.d.ts +49 -0
- package/dist/session-export.js +117 -0
- package/dist/session-export.js.map +7 -0
- package/dist/shadow-dom-crawler.d.ts +35 -0
- package/dist/shadow-dom-crawler.js +132 -0
- package/dist/shadow-dom-crawler.js.map +7 -0
- package/dist/sitemap-writer.d.ts +21 -0
- package/dist/sitemap-writer.js +47 -0
- package/dist/sitemap-writer.js.map +7 -0
- package/dist/sitemap.d.ts +51 -0
- package/dist/sitemap.js +160 -0
- package/dist/sitemap.js.map +7 -0
- package/dist/skyvern.d.ts +56 -0
- package/dist/skyvern.js +129 -0
- package/dist/skyvern.js.map +7 -0
- package/dist/sqs-queue.d.ts +13 -0
- package/dist/sqs-queue.js +115 -0
- package/dist/sqs-queue.js.map +7 -0
- package/dist/sso-token.d.ts +29 -0
- package/dist/sso-token.js +76 -0
- package/dist/sso-token.js.map +7 -0
- package/dist/state-graph.d.ts +59 -0
- package/dist/state-graph.js +193 -0
- package/dist/state-graph.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +202 -0
- package/dist/stealth/human-behavior.js.map +7 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +77 -0
- package/dist/telemetry.js.map +7 -0
- package/dist/test-executor.d.ts +81 -0
- package/dist/test-executor.js +443 -0
- package/dist/test-executor.js.map +7 -0
- package/dist/tls-fingerprint.d.ts +50 -0
- package/dist/tls-fingerprint.js +81 -0
- package/dist/tls-fingerprint.js.map +7 -0
- package/dist/url-pattern-completer.d.ts +28 -0
- package/dist/url-pattern-completer.js +122 -0
- package/dist/url-pattern-completer.js.map +7 -0
- package/dist/visual-diff-verdict.d.ts +40 -0
- package/dist/visual-diff-verdict.js +27 -0
- package/dist/visual-diff-verdict.js.map +7 -0
- package/dist/webhook-notifier.d.ts +30 -0
- package/dist/webhook-notifier.js +76 -0
- package/dist/webhook-notifier.js.map +7 -0
- package/dist/websocket-detector.d.ts +24 -0
- package/dist/websocket-detector.js +60 -0
- package/dist/websocket-detector.js.map +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +33 -0
- package/dist/worker.js.map +7 -0
- package/package.json +49 -4
- package/src/accessibility-auditor.ts +102 -0
- package/src/accessibility-tree-crawler.ts +246 -0
- package/src/advanced-ui-probes.ts +470 -0
- package/src/ai-login.ts +298 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +418 -0
- package/src/browser-extension.ts +186 -0
- package/src/captcha-solver.ts +143 -0
- package/src/color-contrast.ts +116 -0
- package/src/content-chunker.ts +170 -0
- package/src/content-dedup.ts +99 -0
- package/src/cookie-consent.ts +106 -0
- package/src/cookie-jar.ts +91 -0
- package/src/crawl-session-manager.ts +231 -0
- package/src/crawler.ts +7123 -0
- package/src/crawler.ts.rej +12 -0
- package/src/crud-interaction.ts +386 -0
- package/src/custom-js-injector.ts +93 -0
- package/src/dark-mode-capture.ts +110 -0
- package/src/dead-link-detector.ts +76 -0
- package/src/dropdown-enumerator.ts +312 -0
- package/src/element-hash.ts +16 -0
- package/src/email-otp.ts +252 -0
- package/src/engine-router.ts +141 -0
- package/src/export/bulk-export.ts +85 -0
- package/src/export/vector-export.ts +160 -0
- package/src/extractors/advanced.ts +309 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/extractors/branding-extractor.ts +159 -0
- package/src/extractors/summary-extractor.ts +49 -0
- package/src/failure-analysis-agent.ts +51 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/filter-pagination-crawler.ts +500 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +144 -0
- package/src/gap-pass.ts +165 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +453 -0
- package/src/incremental-diff.ts +259 -0
- package/src/index.ts +52 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +1136 -0
- package/src/job-queue.test.ts +187 -0
- package/src/job-queue.ts +4504 -0
- package/src/js-coverage.ts +87 -0
- package/src/js-router-detector.ts +112 -0
- package/src/language-detector.ts +82 -0
- package/src/link-classifier.ts +65 -0
- package/src/live-view.ts +146 -0
- package/src/locator-extractor.ts +309 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/multi-context-handler.ts +296 -0
- package/src/native-crawler.ts +1108 -0
- package/src/nested-window-crawler.ts +310 -0
- package/src/network-action-correlator.ts +320 -0
- package/src/network-observer.ts +204 -0
- package/src/network-throttle.ts +69 -0
- package/src/openapi-generator.ts +135 -0
- package/src/pdf-extractor.ts +123 -0
- package/src/performance-metrics-collector.ts +243 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +184 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +52 -0
- package/src/profiles/domain-profile.ts +190 -0
- package/src/proxy.ts +238 -0
- package/src/rate-limiter.ts +87 -0
- package/src/redirect-tracker.ts +97 -0
- package/src/redis-frontier.ts +143 -0
- package/src/redis-queue.ts +208 -0
- package/src/response-cache.ts +150 -0
- package/src/responsive-detector.ts +92 -0
- package/src/retry-policy.ts +87 -0
- package/src/robots-txt.ts +96 -0
- package/src/screen-reader.ts +98 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/service-worker-interceptor.ts +185 -0
- package/src/session-capture.ts +335 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/shadow-dom-crawler.ts +198 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +127 -0
- package/src/sitemap.ts +233 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +129 -0
- package/src/sso-token.ts +120 -0
- package/src/state-graph.ts +280 -0
- package/src/stealth/human-behavior.ts +227 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +578 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +161 -0
- package/src/vendor.d.ts +20 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +59 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +50 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/accessibility-tree-crawler.d.ts +44 -0
- package/types/advanced-ui-probes.d.ts +47 -0
- package/types/ai-login.d.ts +32 -0
- package/types/auth-token-rotator.d.ts +47 -0
- package/types/auth.d.ts +20 -0
- package/types/browser-extension.d.ts +37 -0
- package/types/bull-worker.d.ts +6 -0
- package/types/captcha-solver.d.ts +16 -0
- package/types/color-contrast.d.ts +30 -0
- package/types/content-chunker.d.ts +18 -0
- package/types/content-dedup.d.ts +23 -0
- package/types/cookie-consent.d.ts +25 -0
- package/types/cookie-jar.d.ts +20 -0
- package/types/crawl-session-manager.d.ts +44 -0
- package/types/crawler.d.ts +233 -0
- package/types/crud-interaction.d.ts +31 -0
- package/types/custom-js-injector.d.ts +42 -0
- package/types/dark-mode-capture.d.ts +23 -0
- package/types/dead-link-detector.d.ts +37 -0
- package/types/dropdown-enumerator.d.ts +20 -0
- package/types/element-hash.d.ts +9 -0
- package/types/email-otp.d.ts +42 -0
- package/types/engine-router.d.ts +47 -0
- package/types/export/bulk-export.d.ts +26 -0
- package/types/export/vector-export.d.ts +76 -0
- package/types/extractors/advanced.d.ts +64 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/extractors/branding-extractor.d.ts +46 -0
- package/types/extractors/summary-extractor.d.ts +10 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/filter-pagination-crawler.d.ts +52 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +40 -0
- package/types/gap-pass.d.ts +36 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +116 -0
- package/types/index.d.ts +52 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +144 -0
- package/types/job-queue.d.ts +308 -0
- package/types/js-coverage.d.ts +29 -0
- package/types/js-router-detector.d.ts +17 -0
- package/types/language-detector.d.ts +22 -0
- package/types/link-classifier.d.ts +23 -0
- package/types/live-view.d.ts +36 -0
- package/types/locator-extractor.d.ts +46 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/multi-context-handler.d.ts +55 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/nested-window-crawler.d.ts +41 -0
- package/types/network-action-correlator.d.ts +47 -0
- package/types/network-observer.d.ts +49 -0
- package/types/network-throttle.d.ts +25 -0
- package/types/openapi-generator.d.ts +49 -0
- package/types/pdf-extractor.d.ts +28 -0
- package/types/performance-metrics-collector.d.ts +50 -0
- package/types/performance-metrics.d.ts +52 -0
- package/types/phash.d.ts +50 -0
- package/types/pii-scrubber.d.ts +21 -0
- package/types/playwright-recorder.d.ts +39 -0
- package/types/policies/crawl-safety.d.ts +9 -0
- package/types/profiles/domain-profile.d.ts +19 -0
- package/types/proxy.d.ts +50 -0
- package/types/rate-limiter.d.ts +31 -0
- package/types/redirect-tracker.d.ts +37 -0
- package/types/redis-frontier.d.ts +48 -0
- package/types/redis-queue.d.ts +48 -0
- package/types/response-cache.d.ts +38 -0
- package/types/responsive-detector.d.ts +29 -0
- package/types/retry-policy.d.ts +20 -0
- package/types/robots-txt.d.ts +9 -0
- package/types/screen-reader.d.ts +15 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/service-worker-interceptor.d.ts +36 -0
- package/types/session-capture.d.ts +39 -0
- package/types/session-export.d.ts +49 -0
- package/types/shadow-dom-crawler.d.ts +35 -0
- package/types/sitemap-writer.d.ts +21 -0
- package/types/sitemap.d.ts +51 -0
- package/types/skyvern.d.ts +56 -0
- package/types/sqs-queue.d.ts +13 -0
- package/types/sso-token.d.ts +29 -0
- package/types/state-graph.d.ts +59 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +84 -0
- package/types/tls-fingerprint.d.ts +50 -0
- package/types/url-pattern-completer.d.ts +28 -0
- package/types/visual-diff-verdict.d.ts +40 -0
- package/types/webhook-notifier.d.ts +30 -0
- package/types/websocket-detector.d.ts +24 -0
- package/types/worker.d.ts +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
async function extractSummary(markdown, options) {
|
|
2
|
+
const input = markdown.slice(0, 8e3);
|
|
3
|
+
try {
|
|
4
|
+
const { ask } = await import("@taodigital/agents").catch(() => ({ ask: null }));
|
|
5
|
+
if (!ask) {
|
|
6
|
+
return makeTruncated(input);
|
|
7
|
+
}
|
|
8
|
+
const summary = await ask(
|
|
9
|
+
"Summarize the following web page content in 2-4 concise sentences. Focus on the main purpose, key features, and primary value proposition. Return only the summary text, no preamble.",
|
|
10
|
+
input,
|
|
11
|
+
options?.model ? { provider: "anthropic", model: options.model } : void 0,
|
|
12
|
+
options?.maxTokens ?? 300
|
|
13
|
+
);
|
|
14
|
+
return {
|
|
15
|
+
summary: summary.trim(),
|
|
16
|
+
wordCount: markdown.split(/\s+/).length,
|
|
17
|
+
method: "llm",
|
|
18
|
+
model: options?.model
|
|
19
|
+
};
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.warn("[summary-extractor] LLM call failed, using truncated fallback:", err);
|
|
22
|
+
return makeTruncated(input);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function makeTruncated(input) {
|
|
26
|
+
const excerpt = input.slice(0, 500).trim();
|
|
27
|
+
return {
|
|
28
|
+
summary: excerpt + (input.length > 500 ? "..." : ""),
|
|
29
|
+
wordCount: excerpt.split(/\s+/).length,
|
|
30
|
+
method: "truncated"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
extractSummary
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=summary-extractor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/extractors/summary-extractor.ts"],
|
|
4
|
+
"sourcesContent": ["export interface SummaryResult {\n summary: string;\n wordCount: number;\n method: 'llm' | 'truncated';\n model?: string;\n}\n\nexport async function extractSummary(\n markdown: string,\n options?: { model?: string; maxTokens?: number },\n): Promise<SummaryResult> {\n // Truncate to 8000 chars to stay within token budget\n const input = markdown.slice(0, 8000);\n\n try {\n // Dynamic import to avoid hard dependency if agents package unavailable\n const { ask } = await import('@detiq/agents').catch(() => ({ ask: null as any }));\n\n if (!ask) {\n return makeTruncated(input);\n }\n\n const summary: string = await ask(\n 'Summarize the following web page content in 2-4 concise sentences. Focus on the main purpose, key features, and primary value proposition. Return only the summary text, no preamble.',\n input,\n options?.model ? { provider: 'anthropic', model: options.model } : undefined,\n options?.maxTokens ?? 300,\n );\n\n return {\n summary: summary.trim(),\n wordCount: markdown.split(/\\s+/).length,\n method: 'llm',\n model: options?.model,\n };\n } catch (err) {\n console.warn('[summary-extractor] LLM call failed, using truncated fallback:', err);\n return makeTruncated(input);\n }\n}\n\nfunction makeTruncated(input: string): SummaryResult {\n const excerpt = input.slice(0, 500).trim();\n return {\n summary: excerpt + (input.length > 500 ? '...' : ''),\n wordCount: excerpt.split(/\\s+/).length,\n method: 'truncated',\n };\n}\n"],
|
|
5
|
+
"mappings": "AAOA,eAAsB,eACpB,UACA,SACwB;AAExB,QAAM,QAAQ,SAAS,MAAM,GAAG,GAAI;AAEpC,MAAI;AAEF,UAAM,EAAE,IAAI,IAAI,MAAM,OAAO,eAAe,EAAE,MAAM,OAAO,EAAE,KAAK,KAAY,EAAE;AAEhF,QAAI,CAAC,KAAK;AACR,aAAO,cAAc,KAAK;AAAA,IAC5B;AAEA,UAAM,UAAkB,MAAM;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,SAAS,QAAQ,EAAE,UAAU,aAAa,OAAO,QAAQ,MAAM,IAAI;AAAA,MACnE,SAAS,aAAa;AAAA,IACxB;AAEA,WAAO;AAAA,MACL,SAAS,QAAQ,KAAK;AAAA,MACtB,WAAW,SAAS,MAAM,KAAK,EAAE;AAAA,MACjC,QAAQ;AAAA,MACR,OAAO,SAAS;AAAA,IAClB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,KAAK,kEAAkE,GAAG;AAClF,WAAO,cAAc,KAAK;AAAA,EAC5B;AACF;AAEA,SAAS,cAAc,OAA8B;AACnD,QAAM,UAAU,MAAM,MAAM,GAAG,GAAG,EAAE,KAAK;AACzC,SAAO;AAAA,IACL,SAAS,WAAW,MAAM,SAAS,MAAM,QAAQ;AAAA,IACjD,WAAW,QAAQ,MAAM,KAAK,EAAE;AAAA,IAChC,QAAQ;AAAA,EACV;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface FailureContext {
|
|
2
|
+
tenantId?: string;
|
|
3
|
+
error: string;
|
|
4
|
+
stepDescription?: string;
|
|
5
|
+
consoleLogs?: Array<{
|
|
6
|
+
type: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}>;
|
|
9
|
+
networkErrors?: Array<{
|
|
10
|
+
url: string;
|
|
11
|
+
status: number;
|
|
12
|
+
method: string;
|
|
13
|
+
}>;
|
|
14
|
+
screenshotPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function analyzeFailure(ctx: FailureContext): Promise<string | null>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { askTrackedWithImages, askTracked, deterministicSeed } from "@taodigital/agents";
|
|
3
|
+
import { AppBrain } from "@taodigital/app-brain";
|
|
4
|
+
async function analyzeFailure(ctx) {
|
|
5
|
+
try {
|
|
6
|
+
let llmCfg;
|
|
7
|
+
if (ctx.tenantId) {
|
|
8
|
+
llmCfg = await AppBrain.getTaskLLMConfig(ctx.tenantId, "testRunner").catch(() => void 0) ?? void 0;
|
|
9
|
+
}
|
|
10
|
+
if (!llmCfg) return null;
|
|
11
|
+
const consoleSection = ctx.consoleLogs?.length ? `
|
|
12
|
+
|
|
13
|
+
Browser console (last ${ctx.consoleLogs.length} entries):
|
|
14
|
+
${ctx.consoleLogs.map((l) => `[${l.type}] ${l.text}`).join("\n")}` : "";
|
|
15
|
+
const networkSection = ctx.networkErrors?.length ? `
|
|
16
|
+
|
|
17
|
+
Failed network requests:
|
|
18
|
+
${ctx.networkErrors.map((n) => `${n.method} ${n.url} \u2192 ${n.status}`).join("\n")}` : "";
|
|
19
|
+
const userText = `A Playwright automated test failed. Identify the root cause in 2-3 sentences. Be specific \u2014 name the failing element, API endpoint, or error type. Do not suggest fixes.
|
|
20
|
+
|
|
21
|
+
Test step: ${ctx.stepDescription ?? "unknown"}
|
|
22
|
+
Error: ${ctx.error}${consoleSection}${networkSection}`;
|
|
23
|
+
if (ctx.screenshotPath) {
|
|
24
|
+
try {
|
|
25
|
+
const img = readFileSync(ctx.screenshotPath);
|
|
26
|
+
const imgSeed = deterministicSeed(`failure-img:${ctx.tenantId ?? ""}:${(ctx.stepDescription ?? "").slice(0, 100)}:${ctx.error.slice(0, 200)}`);
|
|
27
|
+
const result2 = await askTrackedWithImages("", userText, [{ base64: img.toString("base64"), mediaType: "image/png" }], llmCfg, { temperature: 0, seed: imgSeed });
|
|
28
|
+
return result2.text || null;
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const failureSeed = deterministicSeed(`${ctx.tenantId ?? ""}:failure:${(ctx.stepDescription ?? "").slice(0, 100)}:${ctx.error.slice(0, 200)}`);
|
|
33
|
+
const result = await askTracked("", userText, llmCfg, 256, void 0, { temperature: 0, seed: failureSeed });
|
|
34
|
+
return result.text || null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
analyzeFailure
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=failure-analysis-agent.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/failure-analysis-agent.ts"],
|
|
4
|
+
"sourcesContent": ["import { readFileSync } from 'node:fs';\nimport { askTrackedWithImages, askTracked, deterministicSeed } from '@detiq/agents';\nimport type { LLMConfig } from '@detiq/agents';\nimport { AppBrain } from '@detiq/app-brain';\n\nexport interface FailureContext {\n tenantId?: string;\n error: string;\n stepDescription?: string;\n consoleLogs?: Array<{ type: string; text: string }>;\n networkErrors?: Array<{ url: string; status: number; method: string }>;\n screenshotPath?: string;\n}\n\nexport async function analyzeFailure(ctx: FailureContext): Promise<string | null> {\n try {\n let llmCfg: LLMConfig | undefined;\n if (ctx.tenantId) {\n llmCfg = await AppBrain.getTaskLLMConfig(ctx.tenantId, 'testRunner').catch(() => undefined) as LLMConfig | undefined ?? undefined;\n }\n if (!llmCfg) return null;\n\n const consoleSection = ctx.consoleLogs?.length\n ? `\\n\\nBrowser console (last ${ctx.consoleLogs.length} entries):\\n${ctx.consoleLogs.map(l => `[${l.type}] ${l.text}`).join('\\n')}`\n : '';\n\n const networkSection = ctx.networkErrors?.length\n ? `\\n\\nFailed network requests:\\n${ctx.networkErrors.map(n => `${n.method} ${n.url} \u2192 ${n.status}`).join('\\n')}`\n : '';\n\n const userText = `A Playwright automated test failed. Identify the root cause in 2-3 sentences. Be specific \u2014 name the failing element, API endpoint, or error type. Do not suggest fixes.\n\nTest step: ${ctx.stepDescription ?? 'unknown'}\nError: ${ctx.error}${consoleSection}${networkSection}`;\n\n if (ctx.screenshotPath) {\n try {\n const img = readFileSync(ctx.screenshotPath);\n const imgSeed = deterministicSeed(`failure-img:${ctx.tenantId ?? ''}:${(ctx.stepDescription ?? '').slice(0, 100)}:${ctx.error.slice(0, 200)}`);\n const result = await askTrackedWithImages('', userText, [{ base64: img.toString('base64'), mediaType: 'image/png' }], llmCfg, { temperature: 0, seed: imgSeed });\n return result.text || null;\n } catch { /* fall through to text-only */ }\n }\n\n const failureSeed = deterministicSeed(`${ctx.tenantId ?? ''}:failure:${(ctx.stepDescription ?? '').slice(0, 100)}:${ctx.error.slice(0, 200)}`);\n const result = await askTracked('', userText, llmCfg, 256, undefined, { temperature: 0, seed: failureSeed });\n return result.text || null;\n } catch {\n return null;\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB,YAAY,yBAAyB;AAEpE,SAAS,gBAAgB;AAWzB,eAAsB,eAAe,KAA6C;AAChF,MAAI;AACF,QAAI;AACJ,QAAI,IAAI,UAAU;AAChB,eAAS,MAAM,SAAS,iBAAiB,IAAI,UAAU,YAAY,EAAE,MAAM,MAAM,MAAS,KAA8B;AAAA,IAC1H;AACA,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,iBAAiB,IAAI,aAAa,SACpC;AAAA;AAAA,wBAA6B,IAAI,YAAY,MAAM;AAAA,EAAe,IAAI,YAAY,IAAI,OAAK,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI,CAAC,KAC9H;AAEJ,UAAM,iBAAiB,IAAI,eAAe,SACtC;AAAA;AAAA;AAAA,EAAiC,IAAI,cAAc,IAAI,OAAK,GAAG,EAAE,MAAM,IAAI,EAAE,GAAG,WAAM,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,KAC5G;AAEJ,UAAM,WAAW;AAAA;AAAA,aAER,IAAI,mBAAmB,SAAS;AAAA,SACpC,IAAI,KAAK,GAAG,cAAc,GAAG,cAAc;AAEhD,QAAI,IAAI,gBAAgB;AACtB,UAAI;AACF,cAAM,MAAM,aAAa,IAAI,cAAc;AAC3C,cAAM,UAAU,kBAAkB,eAAe,IAAI,YAAY,EAAE,KAAK,IAAI,mBAAmB,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE;AAC7I,cAAMA,UAAS,MAAM,qBAAqB,IAAI,UAAU,CAAC,EAAE,QAAQ,IAAI,SAAS,QAAQ,GAAG,WAAW,YAAY,CAAC,GAAG,QAAQ,EAAE,aAAa,GAAG,MAAM,QAAQ,CAAC;AAC/J,eAAOA,QAAO,QAAQ;AAAA,MACxB,QAAQ;AAAA,MAAkC;AAAA,IAC5C;AAEA,UAAM,cAAc,kBAAkB,GAAG,IAAI,YAAY,EAAE,aAAa,IAAI,mBAAmB,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE;AAC7I,UAAM,SAAS,MAAM,WAAW,IAAI,UAAU,QAAQ,KAAK,QAAW,EAAE,aAAa,GAAG,MAAM,YAAY,CAAC;AAC3G,WAAO,OAAO,QAAQ;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": ["result"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature flag + A/B test detector.
|
|
3
|
+
*
|
|
4
|
+
* Detects popular feature flag / experiment SDKs loaded on the page:
|
|
5
|
+
* LaunchDarkly, Optimizely, Split.io, Unleash, GrowthBook, Statsig, Flagsmith, Amplitude Experiment.
|
|
6
|
+
*
|
|
7
|
+
* Also detects common A/B test patterns: data-testid attributes, gtm.js experiment layers,
|
|
8
|
+
* Optimizely variation IDs, VWO, etc.
|
|
9
|
+
*
|
|
10
|
+
* Why: ZeTa needs to know when a page shows different content to different users
|
|
11
|
+
* so test generation can account for variants and not generate flaky tests.
|
|
12
|
+
*/
|
|
13
|
+
import type { Page } from 'playwright';
|
|
14
|
+
export interface FeatureFlagInfo {
|
|
15
|
+
sdk: string;
|
|
16
|
+
confidence: 'high' | 'medium' | 'low';
|
|
17
|
+
details?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AbTestInfo {
|
|
20
|
+
tool: string;
|
|
21
|
+
experiments?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface FeatureFlagDetectionResult {
|
|
24
|
+
url: string;
|
|
25
|
+
flagSdks: FeatureFlagInfo[];
|
|
26
|
+
abTools: AbTestInfo[];
|
|
27
|
+
hasAnyFlags: boolean;
|
|
28
|
+
detectedAt: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function detectFeatureFlags(page: Page, url: string): Promise<FeatureFlagDetectionResult>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const FLAG_SDK_CHECKS = [
|
|
2
|
+
{ sdk: "LaunchDarkly", windowKeys: ["LDClient", "LaunchDarkly", "ldClient"], scriptPattern: /launchdarkly/i },
|
|
3
|
+
{ sdk: "Optimizely", windowKeys: ["optimizely", "optimizelySdk"], scriptPattern: /optimizely/i },
|
|
4
|
+
{ sdk: "Split.io", windowKeys: ["splitio", "SplitFactory"], scriptPattern: /split\.io/i },
|
|
5
|
+
{ sdk: "GrowthBook", windowKeys: ["growthbook", "GrowthBook"], scriptPattern: /growthbook/i },
|
|
6
|
+
{ sdk: "Statsig", windowKeys: ["statsig", "StatsigClient"], scriptPattern: /statsig/i },
|
|
7
|
+
{ sdk: "Flagsmith", windowKeys: ["flagsmith"], scriptPattern: /flagsmith/i },
|
|
8
|
+
{ sdk: "Unleash", windowKeys: ["unleash", "UnleashClient"], scriptPattern: /unleash/i },
|
|
9
|
+
{ sdk: "VWO", windowKeys: ["vwo", "_vwo"], scriptPattern: /vwo\.com/i },
|
|
10
|
+
{ sdk: "Google Optimize", windowKeys: ["gaData", "google_optimize"], scriptPattern: /googleoptimize/i },
|
|
11
|
+
{ sdk: "AB Tasty", windowKeys: ["ABTasty"], scriptPattern: /abtasty/i }
|
|
12
|
+
];
|
|
13
|
+
async function detectFeatureFlags(page, url) {
|
|
14
|
+
const detectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
15
|
+
const result = await page.evaluate((checks) => {
|
|
16
|
+
const flagSdks = [];
|
|
17
|
+
const scripts = Array.from(document.querySelectorAll("script[src]")).map((s) => s.src ?? "");
|
|
18
|
+
for (const check of checks) {
|
|
19
|
+
let found = false;
|
|
20
|
+
let confidence = "low";
|
|
21
|
+
if (check.windowKeys?.some((k) => k in window)) {
|
|
22
|
+
found = true;
|
|
23
|
+
confidence = "high";
|
|
24
|
+
}
|
|
25
|
+
if (!found && check.scriptPattern) {
|
|
26
|
+
const re = new RegExp(check.scriptPattern.source, "i");
|
|
27
|
+
const matchingSrc = scripts.find((s) => re.test(s));
|
|
28
|
+
if (matchingSrc) {
|
|
29
|
+
found = true;
|
|
30
|
+
confidence = "medium";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (found) flagSdks.push({ sdk: check.sdk, confidence, details: void 0 });
|
|
34
|
+
}
|
|
35
|
+
const abTools = [];
|
|
36
|
+
if ("_vwo_code" in window || "_vis_opt_queue" in window) abTools.push({ tool: "VWO", experiments: [] });
|
|
37
|
+
if ("dataLayer" in window) {
|
|
38
|
+
const dl = window.dataLayer ?? [];
|
|
39
|
+
const expEvents = dl.filter((e) => e?.event?.includes("experiment") || e?.experimentId);
|
|
40
|
+
if (expEvents.length > 0) abTools.push({ tool: "Google Tag Manager Experiments", experiments: expEvents.slice(0, 5).map((e) => e.experimentId ?? "") });
|
|
41
|
+
}
|
|
42
|
+
return { flagSdks, abTools };
|
|
43
|
+
}, FLAG_SDK_CHECKS).catch(() => ({ flagSdks: [], abTools: [] }));
|
|
44
|
+
return {
|
|
45
|
+
url,
|
|
46
|
+
flagSdks: result.flagSdks,
|
|
47
|
+
abTools: result.abTools,
|
|
48
|
+
hasAnyFlags: result.flagSdks.length > 0 || result.abTools.length > 0,
|
|
49
|
+
detectedAt
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
detectFeatureFlags
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=feature-flag-detector.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/feature-flag-detector.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Feature flag + A/B test detector.\n *\n * Detects popular feature flag / experiment SDKs loaded on the page:\n * LaunchDarkly, Optimizely, Split.io, Unleash, GrowthBook, Statsig, Flagsmith, Amplitude Experiment.\n *\n * Also detects common A/B test patterns: data-testid attributes, gtm.js experiment layers,\n * Optimizely variation IDs, VWO, etc.\n *\n * Why: ZeTa needs to know when a page shows different content to different users\n * so test generation can account for variants and not generate flaky tests.\n */\n\nimport type { Page } from 'playwright';\n\nexport interface FeatureFlagInfo {\n sdk: string;\n confidence: 'high' | 'medium' | 'low';\n details?: string;\n}\n\nexport interface AbTestInfo {\n tool: string;\n experiments?: string[];\n}\n\nexport interface FeatureFlagDetectionResult {\n url: string;\n flagSdks: FeatureFlagInfo[];\n abTools: AbTestInfo[];\n hasAnyFlags: boolean;\n detectedAt: string;\n}\n\n// SDK detectors \u2014 checked against window object + script URLs\nconst FLAG_SDK_CHECKS: Array<{\n sdk: string;\n windowKeys?: string[];\n scriptPattern?: RegExp;\n}> = [\n { sdk: 'LaunchDarkly', windowKeys: ['LDClient', 'LaunchDarkly', 'ldClient'], scriptPattern: /launchdarkly/i },\n { sdk: 'Optimizely', windowKeys: ['optimizely', 'optimizelySdk'], scriptPattern: /optimizely/i },\n { sdk: 'Split.io', windowKeys: ['splitio', 'SplitFactory'], scriptPattern: /split\\.io/i },\n { sdk: 'GrowthBook', windowKeys: ['growthbook', 'GrowthBook'], scriptPattern: /growthbook/i },\n { sdk: 'Statsig', windowKeys: ['statsig', 'StatsigClient'], scriptPattern: /statsig/i },\n { sdk: 'Flagsmith', windowKeys: ['flagsmith'], scriptPattern: /flagsmith/i },\n { sdk: 'Unleash', windowKeys: ['unleash', 'UnleashClient'], scriptPattern: /unleash/i },\n { sdk: 'VWO', windowKeys: ['vwo', '_vwo'], scriptPattern: /vwo\\.com/i },\n { sdk: 'Google Optimize', windowKeys: ['gaData', 'google_optimize'], scriptPattern: /googleoptimize/i },\n { sdk: 'AB Tasty', windowKeys: ['ABTasty'], scriptPattern: /abtasty/i },\n];\n\nexport async function detectFeatureFlags(page: Page, url: string): Promise<FeatureFlagDetectionResult> {\n const detectedAt = new Date().toISOString();\n\n const result = await page.evaluate((checks) => {\n const flagSdks: any[] = [];\n const scripts = Array.from(document.querySelectorAll('script[src]')).map((s: any) => s.src ?? '');\n\n for (const check of checks) {\n let found = false;\n let confidence = 'low';\n\n // Check window keys\n if (check.windowKeys?.some((k: string) => k in (window as any))) {\n found = true;\n confidence = 'high';\n }\n // Check script URLs\n if (!found && check.scriptPattern) {\n const re = new RegExp(check.scriptPattern.source, 'i');\n const matchingSrc = scripts.find(s => re.test(s));\n if (matchingSrc) { found = true; confidence = 'medium'; }\n }\n if (found) flagSdks.push({ sdk: check.sdk, confidence, details: undefined });\n }\n\n // A/B tools\n const abTools: any[] = [];\n if ('_vwo_code' in window || '_vis_opt_queue' in (window as any)) abTools.push({ tool: 'VWO', experiments: [] });\n if ('dataLayer' in window) {\n const dl = (window as any).dataLayer ?? [];\n const expEvents = dl.filter((e: any) => e?.event?.includes('experiment') || e?.experimentId);\n if (expEvents.length > 0) abTools.push({ tool: 'Google Tag Manager Experiments', experiments: expEvents.slice(0, 5).map((e: any) => e.experimentId ?? '') });\n }\n\n return { flagSdks, abTools };\n }, FLAG_SDK_CHECKS as any).catch(() => ({ flagSdks: [], abTools: [] }));\n\n return {\n url,\n flagSdks: result.flagSdks,\n abTools: result.abTools,\n hasAnyFlags: result.flagSdks.length > 0 || result.abTools.length > 0,\n detectedAt,\n };\n}\n"],
|
|
5
|
+
"mappings": "AAmCA,MAAM,kBAID;AAAA,EACH,EAAE,KAAK,gBAAgB,YAAY,CAAC,YAAY,gBAAgB,UAAU,GAAG,eAAe,gBAAgB;AAAA,EAC5G,EAAE,KAAK,cAAc,YAAY,CAAC,cAAc,eAAe,GAAG,eAAe,cAAc;AAAA,EAC/F,EAAE,KAAK,YAAY,YAAY,CAAC,WAAW,cAAc,GAAG,eAAe,aAAa;AAAA,EACxF,EAAE,KAAK,cAAc,YAAY,CAAC,cAAc,YAAY,GAAG,eAAe,cAAc;AAAA,EAC5F,EAAE,KAAK,WAAW,YAAY,CAAC,WAAW,eAAe,GAAG,eAAe,WAAW;AAAA,EACtF,EAAE,KAAK,aAAa,YAAY,CAAC,WAAW,GAAG,eAAe,aAAa;AAAA,EAC3E,EAAE,KAAK,WAAW,YAAY,CAAC,WAAW,eAAe,GAAG,eAAe,WAAW;AAAA,EACtF,EAAE,KAAK,OAAO,YAAY,CAAC,OAAO,MAAM,GAAG,eAAe,YAAY;AAAA,EACtE,EAAE,KAAK,mBAAmB,YAAY,CAAC,UAAU,iBAAiB,GAAG,eAAe,kBAAkB;AAAA,EACtG,EAAE,KAAK,YAAY,YAAY,CAAC,SAAS,GAAG,eAAe,WAAW;AACxE;AAEA,eAAsB,mBAAmB,MAAY,KAAkD;AACrG,QAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAE1C,QAAM,SAAS,MAAM,KAAK,SAAS,CAAC,WAAW;AAC7C,UAAM,WAAkB,CAAC;AACzB,UAAM,UAAU,MAAM,KAAK,SAAS,iBAAiB,aAAa,CAAC,EAAE,IAAI,CAAC,MAAW,EAAE,OAAO,EAAE;AAEhG,eAAW,SAAS,QAAQ;AAC1B,UAAI,QAAQ;AACZ,UAAI,aAAa;AAGjB,UAAI,MAAM,YAAY,KAAK,CAAC,MAAc,KAAM,MAAc,GAAG;AAC/D,gBAAQ;AACR,qBAAa;AAAA,MACf;AAEA,UAAI,CAAC,SAAS,MAAM,eAAe;AACjC,cAAM,KAAK,IAAI,OAAO,MAAM,cAAc,QAAQ,GAAG;AACrD,cAAM,cAAc,QAAQ,KAAK,OAAK,GAAG,KAAK,CAAC,CAAC;AAChD,YAAI,aAAa;AAAE,kBAAQ;AAAM,uBAAa;AAAA,QAAU;AAAA,MAC1D;AACA,UAAI,MAAO,UAAS,KAAK,EAAE,KAAK,MAAM,KAAK,YAAY,SAAS,OAAU,CAAC;AAAA,IAC7E;AAGA,UAAM,UAAiB,CAAC;AACxB,QAAI,eAAe,UAAU,oBAAqB,OAAgB,SAAQ,KAAK,EAAE,MAAM,OAAO,aAAa,CAAC,EAAE,CAAC;AAC/G,QAAI,eAAe,QAAQ;AACzB,YAAM,KAAM,OAAe,aAAa,CAAC;AACzC,YAAM,YAAY,GAAG,OAAO,CAAC,MAAW,GAAG,OAAO,SAAS,YAAY,KAAK,GAAG,YAAY;AAC3F,UAAI,UAAU,SAAS,EAAG,SAAQ,KAAK,EAAE,MAAM,kCAAkC,aAAa,UAAU,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAW,EAAE,gBAAgB,EAAE,EAAE,CAAC;AAAA,IAC7J;AAEA,WAAO,EAAE,UAAU,QAAQ;AAAA,EAC7B,GAAG,eAAsB,EAAE,MAAM,OAAO,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE,EAAE;AAEtE,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO,SAAS,SAAS,KAAK,OAAO,QAAQ,SAAS;AAAA,IACnE;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter state crawling and pagination capture.
|
|
3
|
+
*
|
|
4
|
+
* Discovers filter controls (select, checkbox-group, radio-group, search),
|
|
5
|
+
* applies each meaningful option and records result counts and first-row
|
|
6
|
+
* values, then detects and walks pagination (numbered, load-more, infinite
|
|
7
|
+
* scroll). Also captures sort column behaviour.
|
|
8
|
+
*
|
|
9
|
+
* All per-filter / per-page errors are caught and skipped so one bad control
|
|
10
|
+
* never aborts the full crawl.
|
|
11
|
+
*/
|
|
12
|
+
import type { Page } from 'playwright';
|
|
13
|
+
export interface FilterOption {
|
|
14
|
+
value: string;
|
|
15
|
+
label: string;
|
|
16
|
+
}
|
|
17
|
+
export interface FilterDef {
|
|
18
|
+
kind: 'select' | 'checkbox-group' | 'radio-group' | 'search';
|
|
19
|
+
label: string;
|
|
20
|
+
locatorHint: string;
|
|
21
|
+
options: FilterOption[];
|
|
22
|
+
}
|
|
23
|
+
export interface FilterState {
|
|
24
|
+
filterLabel: string;
|
|
25
|
+
filterValue: string;
|
|
26
|
+
resultCount: number | null;
|
|
27
|
+
firstRowValues: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface PaginationCapture {
|
|
30
|
+
kind: 'numbered' | 'load-more' | 'infinite-scroll' | 'none';
|
|
31
|
+
totalPages?: number;
|
|
32
|
+
totalItems?: number;
|
|
33
|
+
pageSizes?: number[];
|
|
34
|
+
pages: Array<{
|
|
35
|
+
pageNum: number;
|
|
36
|
+
rowValues: string[];
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export interface FilterPaginationResult {
|
|
40
|
+
filters: FilterDef[];
|
|
41
|
+
filterStates: FilterState[];
|
|
42
|
+
pagination: PaginationCapture;
|
|
43
|
+
sortColumns: Array<{
|
|
44
|
+
header: string;
|
|
45
|
+
ariaSortAfterClick: string | null;
|
|
46
|
+
}>;
|
|
47
|
+
searchCapture?: {
|
|
48
|
+
noResultsText: string | null;
|
|
49
|
+
sampleSuggestions: string[];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare function crawlFiltersAndPagination(page: Page): Promise<FilterPaginationResult>;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
async function waitForSettle(page, ms = 1500) {
|
|
2
|
+
await Promise.race([
|
|
3
|
+
page.waitForLoadState("networkidle").catch(() => void 0),
|
|
4
|
+
new Promise((r) => setTimeout(r, ms))
|
|
5
|
+
]);
|
|
6
|
+
}
|
|
7
|
+
async function captureFirstRow(page) {
|
|
8
|
+
try {
|
|
9
|
+
return await page.evaluate(() => {
|
|
10
|
+
const row = document.querySelector("tbody tr");
|
|
11
|
+
if (!row) return [];
|
|
12
|
+
return Array.from(row.querySelectorAll("td")).map((td) => td.innerText.trim());
|
|
13
|
+
});
|
|
14
|
+
} catch {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function readResultCount(page) {
|
|
19
|
+
try {
|
|
20
|
+
const text = await page.evaluate(() => document.body.innerText);
|
|
21
|
+
const match = text.match(/(\d[\d,]*)\s*(result|item|entr|record)/i);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
return parseInt(match[1].replace(/,/g, ""), 10);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function discoverFilters(page) {
|
|
29
|
+
const filters = [];
|
|
30
|
+
try {
|
|
31
|
+
const selectHandles = await page.locator('select:not([role="dialog"] select)').all();
|
|
32
|
+
for (const handle of selectHandles.slice(0, 10)) {
|
|
33
|
+
try {
|
|
34
|
+
const id = await handle.getAttribute("id").catch(() => null);
|
|
35
|
+
const name = await handle.getAttribute("name").catch(() => null);
|
|
36
|
+
const locatorHint = id ? `#${id}` : name ? `select[name="${name}"]` : "select";
|
|
37
|
+
let label = "";
|
|
38
|
+
if (id) {
|
|
39
|
+
label = await page.locator(`label[for="${id}"]`).innerText().catch(() => "");
|
|
40
|
+
}
|
|
41
|
+
if (!label) {
|
|
42
|
+
label = await handle.evaluate((el) => {
|
|
43
|
+
const lbl = el.closest("label") ?? el.previousElementSibling;
|
|
44
|
+
return lbl?.innerText?.trim() ?? "";
|
|
45
|
+
}).catch(() => "");
|
|
46
|
+
}
|
|
47
|
+
if (!label) label = name ?? id ?? "filter";
|
|
48
|
+
const rawOptions = await handle.evaluate(
|
|
49
|
+
(el) => Array.from(el.options).map((o) => ({ value: o.value, label: o.text.trim() }))
|
|
50
|
+
).catch(() => []);
|
|
51
|
+
const options = rawOptions.filter(
|
|
52
|
+
(o) => !(/^all$|^any$|^select|^--/i.test(o.label) || o.value === "")
|
|
53
|
+
);
|
|
54
|
+
if (options.length === 0) continue;
|
|
55
|
+
filters.push({ kind: "select", label, locatorHint, options });
|
|
56
|
+
if (filters.length >= 10) break;
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
if (filters.length < 10) {
|
|
63
|
+
try {
|
|
64
|
+
const searchHandles = await page.locator(
|
|
65
|
+
'input[type="search"]:not([role="dialog"] input), input[placeholder*="search" i]:not([role="dialog"] input)'
|
|
66
|
+
).all();
|
|
67
|
+
for (const handle of searchHandles.slice(0, 10 - filters.length)) {
|
|
68
|
+
try {
|
|
69
|
+
const id = await handle.getAttribute("id").catch(() => null);
|
|
70
|
+
const placeholder = await handle.getAttribute("placeholder").catch(() => null);
|
|
71
|
+
const locatorHint = id ? `#${id}` : placeholder ? `input[placeholder="${placeholder}"]` : 'input[type="search"]';
|
|
72
|
+
let label = "";
|
|
73
|
+
if (id) {
|
|
74
|
+
label = await page.locator(`label[for="${id}"]`).innerText().catch(() => "");
|
|
75
|
+
}
|
|
76
|
+
if (!label) label = placeholder ?? "search";
|
|
77
|
+
filters.push({ kind: "search", label, locatorHint, options: [] });
|
|
78
|
+
if (filters.length >= 10) break;
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return filters;
|
|
86
|
+
}
|
|
87
|
+
async function applyFilterAndCapture(page, filter, option) {
|
|
88
|
+
const state = {
|
|
89
|
+
filterLabel: filter.label,
|
|
90
|
+
filterValue: option.value,
|
|
91
|
+
resultCount: null,
|
|
92
|
+
firstRowValues: []
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
if (filter.kind === "select") {
|
|
96
|
+
await page.locator(filter.locatorHint).selectOption(option.value);
|
|
97
|
+
} else if (filter.kind === "search") {
|
|
98
|
+
await page.locator(filter.locatorHint).fill(option.value);
|
|
99
|
+
await page.locator(filter.locatorHint).press("Enter");
|
|
100
|
+
}
|
|
101
|
+
await waitForSettle(page, 1500);
|
|
102
|
+
state.resultCount = await readResultCount(page);
|
|
103
|
+
state.firstRowValues = await captureFirstRow(page);
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
if (filter.kind === "select") {
|
|
108
|
+
const firstOption = await page.locator(filter.locatorHint).evaluate(
|
|
109
|
+
(el) => el.options[0]?.value ?? ""
|
|
110
|
+
);
|
|
111
|
+
await page.locator(filter.locatorHint).selectOption(firstOption);
|
|
112
|
+
} else if (filter.kind === "search") {
|
|
113
|
+
await page.locator(filter.locatorHint).fill("");
|
|
114
|
+
await page.locator(filter.locatorHint).press("Enter");
|
|
115
|
+
}
|
|
116
|
+
await waitForSettle(page, 800);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
return state;
|
|
120
|
+
}
|
|
121
|
+
async function crawlFilterStates(page, filters) {
|
|
122
|
+
const states = [];
|
|
123
|
+
let totalStates = 0;
|
|
124
|
+
for (const filter of filters) {
|
|
125
|
+
if (totalStates >= 15) break;
|
|
126
|
+
const { options } = filter;
|
|
127
|
+
if (options.length === 0) continue;
|
|
128
|
+
const indices = /* @__PURE__ */ new Set([
|
|
129
|
+
0,
|
|
130
|
+
Math.floor((options.length - 1) / 2),
|
|
131
|
+
options.length - 1
|
|
132
|
+
]);
|
|
133
|
+
const picked = Array.from(indices).slice(0, 3);
|
|
134
|
+
for (const idx of picked) {
|
|
135
|
+
if (totalStates >= 15) break;
|
|
136
|
+
const option = options[idx];
|
|
137
|
+
try {
|
|
138
|
+
const s = await applyFilterAndCapture(page, filter, option);
|
|
139
|
+
states.push(s);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
totalStates++;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return states;
|
|
146
|
+
}
|
|
147
|
+
async function detectPaginationKind(page) {
|
|
148
|
+
try {
|
|
149
|
+
const numberedCount = await page.locator('[aria-label*="page" i], .pagination button, [role="navigation"] button').count().catch(() => 0);
|
|
150
|
+
if (numberedCount > 1) return "numbered";
|
|
151
|
+
const loadMore = await page.locator('button:has-text("Load more"), button:has-text("Show more"), a:has-text("Load more")').count().catch(() => 0);
|
|
152
|
+
if (loadMore > 0) return "load-more";
|
|
153
|
+
const isInfinite = await page.evaluate(() => {
|
|
154
|
+
return document.documentElement.scrollHeight > window.innerHeight * 2;
|
|
155
|
+
}).catch(() => false);
|
|
156
|
+
if (isInfinite) return "infinite-scroll";
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
return "none";
|
|
160
|
+
}
|
|
161
|
+
async function crawlNumberedPagination(page) {
|
|
162
|
+
const pages = [];
|
|
163
|
+
const pageSizes = [];
|
|
164
|
+
try {
|
|
165
|
+
const sizeSelect = page.locator('select[name*="size" i], select[name*="per" i], select[aria-label*="per page" i]');
|
|
166
|
+
const sizeCount = await sizeSelect.count();
|
|
167
|
+
if (sizeCount > 0) {
|
|
168
|
+
const rawSizes = await sizeSelect.first().evaluate(
|
|
169
|
+
(el) => Array.from(el.options).map((o) => parseInt(o.value, 10)).filter((n) => !isNaN(n))
|
|
170
|
+
);
|
|
171
|
+
pageSizes.push(...rawSizes);
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
let totalPages;
|
|
176
|
+
try {
|
|
177
|
+
totalPages = await page.evaluate(() => {
|
|
178
|
+
const buttons = Array.from(
|
|
179
|
+
document.querySelectorAll('[aria-label*="page" i], .pagination button')
|
|
180
|
+
);
|
|
181
|
+
const nums = buttons.map((b) => parseInt(b.innerText.trim(), 10)).filter((n) => !isNaN(n));
|
|
182
|
+
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
183
|
+
});
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
pages.push({ pageNum: 1, rowValues: await captureFirstRow(page) });
|
|
187
|
+
for (let pageNum = 2; pageNum <= 20; pageNum++) {
|
|
188
|
+
try {
|
|
189
|
+
const nextBtn = page.locator(
|
|
190
|
+
'button[aria-label*="next" i], a[aria-label*="next" i], [role="navigation"] button:last-child'
|
|
191
|
+
).first();
|
|
192
|
+
const isDisabled = await nextBtn.isDisabled().catch(() => true);
|
|
193
|
+
if (isDisabled) break;
|
|
194
|
+
await nextBtn.click();
|
|
195
|
+
await waitForSettle(page, 2e3);
|
|
196
|
+
const rowValues = await captureFirstRow(page);
|
|
197
|
+
pages.push({ pageNum, rowValues });
|
|
198
|
+
} catch {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
kind: "numbered",
|
|
204
|
+
totalPages,
|
|
205
|
+
pageSizes: pageSizes.length > 0 ? pageSizes : void 0,
|
|
206
|
+
pages
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async function captureInfiniteScroll(page) {
|
|
210
|
+
const pages = [];
|
|
211
|
+
let prevRowCount = -1;
|
|
212
|
+
let iteration = 0;
|
|
213
|
+
while (iteration < 10) {
|
|
214
|
+
try {
|
|
215
|
+
const rowCount = await page.evaluate(
|
|
216
|
+
() => document.querySelectorAll("tbody tr").length
|
|
217
|
+
).catch(() => 0);
|
|
218
|
+
if (rowCount === prevRowCount) break;
|
|
219
|
+
prevRowCount = rowCount;
|
|
220
|
+
pages.push({ pageNum: iteration + 1, rowValues: await captureFirstRow(page) });
|
|
221
|
+
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
222
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
223
|
+
} catch {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
iteration++;
|
|
227
|
+
}
|
|
228
|
+
const totalItems = await page.evaluate(
|
|
229
|
+
() => document.querySelectorAll("tbody tr").length
|
|
230
|
+
).catch(() => void 0);
|
|
231
|
+
return {
|
|
232
|
+
kind: "infinite-scroll",
|
|
233
|
+
totalItems: typeof totalItems === "number" ? totalItems : void 0,
|
|
234
|
+
pages
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function captureSortColumns(page) {
|
|
238
|
+
const results = [];
|
|
239
|
+
try {
|
|
240
|
+
const sortable = await page.locator("[aria-sort]").all();
|
|
241
|
+
for (const th of sortable.slice(0, 5)) {
|
|
242
|
+
try {
|
|
243
|
+
const header = await th.innerText().catch(() => "");
|
|
244
|
+
await th.click();
|
|
245
|
+
await waitForSettle(page, 800);
|
|
246
|
+
const ariaSortAfterClick = await th.getAttribute("aria-sort").catch(() => null);
|
|
247
|
+
results.push({ header: header.trim(), ariaSortAfterClick });
|
|
248
|
+
await th.click().catch(() => void 0);
|
|
249
|
+
await waitForSettle(page, 600);
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
return results;
|
|
256
|
+
}
|
|
257
|
+
async function crawlFiltersAndPagination(page) {
|
|
258
|
+
let filters = [];
|
|
259
|
+
try {
|
|
260
|
+
filters = await discoverFilters(page);
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
let filterStates = [];
|
|
264
|
+
try {
|
|
265
|
+
filterStates = await crawlFilterStates(page, filters);
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
let pagination = { kind: "none", pages: [] };
|
|
269
|
+
try {
|
|
270
|
+
const kind = await detectPaginationKind(page);
|
|
271
|
+
if (kind === "numbered") {
|
|
272
|
+
pagination = await crawlNumberedPagination(page);
|
|
273
|
+
} else if (kind === "load-more") {
|
|
274
|
+
pagination = await captureInfiniteScroll(page);
|
|
275
|
+
pagination = { ...pagination, kind: "load-more" };
|
|
276
|
+
} else if (kind === "infinite-scroll") {
|
|
277
|
+
pagination = await captureInfiniteScroll(page);
|
|
278
|
+
} else {
|
|
279
|
+
const rowValues = await captureFirstRow(page);
|
|
280
|
+
pagination = { kind: "none", pages: rowValues.length > 0 ? [{ pageNum: 1, rowValues }] : [] };
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
284
|
+
let sortColumns = [];
|
|
285
|
+
try {
|
|
286
|
+
sortColumns = await captureSortColumns(page);
|
|
287
|
+
} catch {
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
filters,
|
|
291
|
+
filterStates,
|
|
292
|
+
pagination,
|
|
293
|
+
sortColumns
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
export {
|
|
297
|
+
crawlFiltersAndPagination
|
|
298
|
+
};
|
|
299
|
+
//# sourceMappingURL=filter-pagination-crawler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/filter-pagination-crawler.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Filter state crawling and pagination capture.\n *\n * Discovers filter controls (select, checkbox-group, radio-group, search),\n * applies each meaningful option and records result counts and first-row\n * values, then detects and walks pagination (numbered, load-more, infinite\n * scroll). Also captures sort column behaviour.\n *\n * All per-filter / per-page errors are caught and skipped so one bad control\n * never aborts the full crawl.\n */\n\nimport type { Page } from 'playwright';\n\n// ---------------------------------------------------------------------------\n// Public interfaces\n// ---------------------------------------------------------------------------\n\nexport interface FilterOption {\n value: string;\n label: string;\n}\n\nexport interface FilterDef {\n kind: 'select' | 'checkbox-group' | 'radio-group' | 'search';\n label: string;\n locatorHint: string;\n options: FilterOption[];\n}\n\nexport interface FilterState {\n filterLabel: string;\n filterValue: string;\n resultCount: number | null;\n firstRowValues: string[];\n}\n\nexport interface PaginationCapture {\n kind: 'numbered' | 'load-more' | 'infinite-scroll' | 'none';\n totalPages?: number;\n totalItems?: number;\n pageSizes?: number[];\n pages: Array<{ pageNum: number; rowValues: string[] }>;\n}\n\nexport interface FilterPaginationResult {\n filters: FilterDef[];\n filterStates: FilterState[];\n pagination: PaginationCapture;\n sortColumns: Array<{ header: string; ariaSortAfterClick: string | null }>;\n searchCapture?: { noResultsText: string | null; sampleSuggestions: string[] };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Pause for up to `ms` ms or until network is idle, whichever comes first. */\nasync function waitForSettle(page: Page, ms = 1500): Promise<void> {\n await Promise.race([\n page.waitForLoadState('networkidle').catch(() => undefined),\n new Promise<void>(r => setTimeout(r, ms)),\n ]);\n}\n\n/** Extract visible cell text from the first tbody row, if any. */\nasync function captureFirstRow(page: Page): Promise<string[]> {\n try {\n return await page.evaluate(() => {\n const row = document.querySelector('tbody tr');\n if (!row) return [];\n return Array.from(row.querySelectorAll('td')).map(td => td.innerText.trim());\n });\n } catch {\n return [];\n }\n}\n\n/** Read a result-count hint from visible page text using common patterns. */\nasync function readResultCount(page: Page): Promise<number | null> {\n try {\n const text = await page.evaluate(() => document.body.innerText);\n const match = text.match(/(\\d[\\d,]*)\\s*(result|item|entr|record)/i);\n if (!match) return null;\n return parseInt(match[1].replace(/,/g, ''), 10);\n } catch {\n return null;\n }\n}\n\n// ---------------------------------------------------------------------------\n// discoverFilters\n// ---------------------------------------------------------------------------\n\nasync function discoverFilters(page: Page): Promise<FilterDef[]> {\n const filters: FilterDef[] = [];\n\n // --- native <select> elements outside dialogs ---------------------------------\n try {\n const selectHandles = await page.locator('select:not([role=\"dialog\"] select)').all();\n for (const handle of selectHandles.slice(0, 10)) {\n try {\n const id = await handle.getAttribute('id').catch(() => null);\n const name = await handle.getAttribute('name').catch(() => null);\n const locatorHint = id ? `#${id}` : name ? `select[name=\"${name}\"]` : 'select';\n\n // Find associated label\n let label = '';\n if (id) {\n label = await page.locator(`label[for=\"${id}\"]`).innerText().catch(() => '');\n }\n if (!label) {\n label = await handle.evaluate((el: HTMLSelectElement) => {\n const lbl = el.closest('label') ?? el.previousElementSibling;\n return (lbl as HTMLElement | null)?.innerText?.trim() ?? '';\n }).catch(() => '');\n }\n if (!label) label = name ?? id ?? 'filter';\n\n // Collect options, skipping default/placeholder entries\n const rawOptions: FilterOption[] = await handle.evaluate((el: HTMLSelectElement) =>\n Array.from(el.options).map(o => ({ value: o.value, label: o.text.trim() })),\n ).catch(() => []);\n\n const options = rawOptions.filter(\n o => !(/^all$|^any$|^select|^--/i.test(o.label) || o.value === ''),\n );\n\n if (options.length === 0) continue;\n\n filters.push({ kind: 'select', label, locatorHint, options });\n if (filters.length >= 10) break;\n } catch {\n // skip this select\n }\n }\n } catch {\n // page.locator failed\n }\n\n // --- search inputs ------------------------------------------------------------\n if (filters.length < 10) {\n try {\n const searchHandles = await page\n .locator(\n 'input[type=\"search\"]:not([role=\"dialog\"] input), ' +\n 'input[placeholder*=\"search\" i]:not([role=\"dialog\"] input)',\n )\n .all();\n\n for (const handle of searchHandles.slice(0, 10 - filters.length)) {\n try {\n const id = await handle.getAttribute('id').catch(() => null);\n const placeholder = await handle.getAttribute('placeholder').catch(() => null);\n const locatorHint = id ? `#${id}` : placeholder ? `input[placeholder=\"${placeholder}\"]` : 'input[type=\"search\"]';\n\n let label = '';\n if (id) {\n label = await page.locator(`label[for=\"${id}\"]`).innerText().catch(() => '');\n }\n if (!label) label = placeholder ?? 'search';\n\n filters.push({ kind: 'search', label, locatorHint, options: [] });\n if (filters.length >= 10) break;\n } catch {\n // skip\n }\n }\n } catch {\n // locator failed\n }\n }\n\n return filters;\n}\n\n// ---------------------------------------------------------------------------\n// applyFilterAndCapture\n// ---------------------------------------------------------------------------\n\nasync function applyFilterAndCapture(\n page: Page,\n filter: FilterDef,\n option: FilterOption,\n): Promise<FilterState> {\n const state: FilterState = {\n filterLabel: filter.label,\n filterValue: option.value,\n resultCount: null,\n firstRowValues: [],\n };\n\n try {\n if (filter.kind === 'select') {\n await page.locator(filter.locatorHint).selectOption(option.value);\n } else if (filter.kind === 'search') {\n await page.locator(filter.locatorHint).fill(option.value);\n await page.locator(filter.locatorHint).press('Enter');\n }\n\n await waitForSettle(page, 1500);\n\n state.resultCount = await readResultCount(page);\n state.firstRowValues = await captureFirstRow(page);\n } catch {\n // non-fatal: return partial state\n }\n\n // Reset filter\n try {\n if (filter.kind === 'select') {\n const firstOption = await page.locator(filter.locatorHint).evaluate(\n (el: HTMLSelectElement) => el.options[0]?.value ?? '',\n );\n await page.locator(filter.locatorHint).selectOption(firstOption);\n } else if (filter.kind === 'search') {\n await page.locator(filter.locatorHint).fill('');\n await page.locator(filter.locatorHint).press('Enter');\n }\n await waitForSettle(page, 800);\n } catch {\n // ignore reset errors\n }\n\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// crawlFilterStates\n// ---------------------------------------------------------------------------\n\nasync function crawlFilterStates(page: Page, filters: FilterDef[]): Promise<FilterState[]> {\n const states: FilterState[] = [];\n let totalStates = 0;\n\n for (const filter of filters) {\n if (totalStates >= 15) break;\n\n const { options } = filter;\n if (options.length === 0) continue;\n\n // Pick first, middle, last \u2014 deduplicated, max 3\n const indices = new Set<number>([\n 0,\n Math.floor((options.length - 1) / 2),\n options.length - 1,\n ]);\n const picked = Array.from(indices).slice(0, 3);\n\n for (const idx of picked) {\n if (totalStates >= 15) break;\n const option = options[idx];\n try {\n const s = await applyFilterAndCapture(page, filter, option);\n states.push(s);\n } catch {\n // skip\n }\n totalStates++;\n }\n }\n\n return states;\n}\n\n// ---------------------------------------------------------------------------\n// detectPaginationKind\n// ---------------------------------------------------------------------------\n\nasync function detectPaginationKind(\n page: Page,\n): Promise<'numbered' | 'load-more' | 'infinite-scroll' | 'none'> {\n try {\n // Numbered pagination: ARIA page buttons or .pagination\n const numberedCount = await page\n .locator('[aria-label*=\"page\" i], .pagination button, [role=\"navigation\"] button')\n .count()\n .catch(() => 0);\n if (numberedCount > 1) return 'numbered';\n\n // Load-more button\n const loadMore = await page\n .locator('button:has-text(\"Load more\"), button:has-text(\"Show more\"), a:has-text(\"Load more\")')\n .count()\n .catch(() => 0);\n if (loadMore > 0) return 'load-more';\n\n // Infinite scroll: scrollable content significantly taller than viewport\n const isInfinite = await page.evaluate(() => {\n return document.documentElement.scrollHeight > window.innerHeight * 2;\n }).catch(() => false);\n if (isInfinite) return 'infinite-scroll';\n } catch {\n // fall through\n }\n\n return 'none';\n}\n\n// ---------------------------------------------------------------------------\n// crawlNumberedPagination\n// ---------------------------------------------------------------------------\n\nasync function crawlNumberedPagination(page: Page): Promise<PaginationCapture> {\n const pages: Array<{ pageNum: number; rowValues: string[] }> = [];\n const pageSizes: number[] = [];\n\n // Read page size selector if present\n try {\n const sizeSelect = page.locator('select[name*=\"size\" i], select[name*=\"per\" i], select[aria-label*=\"per page\" i]');\n const sizeCount = await sizeSelect.count();\n if (sizeCount > 0) {\n const rawSizes = await sizeSelect.first().evaluate((el: HTMLSelectElement) =>\n Array.from(el.options).map(o => parseInt(o.value, 10)).filter(n => !isNaN(n)),\n );\n pageSizes.push(...rawSizes);\n }\n } catch {\n // ignore\n }\n\n // Determine total pages from pagination widget\n let totalPages: number | undefined;\n try {\n totalPages = await page.evaluate(() => {\n const buttons = Array.from(\n document.querySelectorAll('[aria-label*=\"page\" i], .pagination button'),\n );\n const nums = buttons\n .map(b => parseInt((b as HTMLElement).innerText.trim(), 10))\n .filter(n => !isNaN(n));\n return nums.length > 0 ? Math.max(...nums) : undefined;\n });\n } catch {\n // leave undefined\n }\n\n // Capture first page immediately\n pages.push({ pageNum: 1, rowValues: await captureFirstRow(page) });\n\n // Walk pages by clicking Next\n for (let pageNum = 2; pageNum <= 20; pageNum++) {\n try {\n const nextBtn = page.locator(\n 'button[aria-label*=\"next\" i], a[aria-label*=\"next\" i], [role=\"navigation\"] button:last-child',\n ).first();\n\n const isDisabled = await nextBtn.isDisabled().catch(() => true);\n if (isDisabled) break;\n\n await nextBtn.click();\n await waitForSettle(page, 2000);\n\n const rowValues = await captureFirstRow(page);\n pages.push({ pageNum, rowValues });\n } catch {\n break;\n }\n }\n\n return {\n kind: 'numbered',\n totalPages,\n pageSizes: pageSizes.length > 0 ? pageSizes : undefined,\n pages,\n };\n}\n\n// ---------------------------------------------------------------------------\n// captureInfiniteScroll\n// ---------------------------------------------------------------------------\n\nasync function captureInfiniteScroll(page: Page): Promise<PaginationCapture> {\n const pages: Array<{ pageNum: number; rowValues: string[] }> = [];\n\n let prevRowCount = -1;\n let iteration = 0;\n\n while (iteration < 10) {\n try {\n const rowCount = await page.evaluate(() =>\n document.querySelectorAll('tbody tr').length,\n ).catch(() => 0);\n\n if (rowCount === prevRowCount) break; // stable\n prevRowCount = rowCount;\n\n // Record current first-row snapshot per scroll step\n pages.push({ pageNum: iteration + 1, rowValues: await captureFirstRow(page) });\n\n await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));\n await new Promise<void>(r => setTimeout(r, 800));\n } catch {\n break;\n }\n iteration++;\n }\n\n const totalItems = await page.evaluate(() =>\n document.querySelectorAll('tbody tr').length,\n ).catch(() => undefined);\n\n return {\n kind: 'infinite-scroll',\n totalItems: typeof totalItems === 'number' ? totalItems : undefined,\n pages,\n };\n}\n\n// ---------------------------------------------------------------------------\n// captureSortColumns\n// ---------------------------------------------------------------------------\n\nasync function captureSortColumns(\n page: Page,\n): Promise<Array<{ header: string; ariaSortAfterClick: string | null }>> {\n const results: Array<{ header: string; ariaSortAfterClick: string | null }> = [];\n\n try {\n const sortable = await page.locator('[aria-sort]').all();\n for (const th of sortable.slice(0, 5)) {\n try {\n const header = await th.innerText().catch(() => '');\n await th.click();\n await waitForSettle(page, 800);\n const ariaSortAfterClick = await th.getAttribute('aria-sort').catch(() => null);\n results.push({ header: header.trim(), ariaSortAfterClick });\n\n // Reset by clicking again (most tables toggle back)\n await th.click().catch(() => undefined);\n await waitForSettle(page, 600);\n } catch {\n // skip this column\n }\n }\n } catch {\n // no sortable columns found\n }\n\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// crawlFiltersAndPagination \u2014 public orchestrator\n// ---------------------------------------------------------------------------\n\nexport async function crawlFiltersAndPagination(page: Page): Promise<FilterPaginationResult> {\n // 1. Discover filters\n let filters: FilterDef[] = [];\n try {\n filters = await discoverFilters(page);\n } catch {\n // proceed with empty filters\n }\n\n // 2. Apply filters and capture states\n let filterStates: FilterState[] = [];\n try {\n filterStates = await crawlFilterStates(page, filters);\n } catch {\n // proceed with empty states\n }\n\n // 3. Detect and crawl pagination\n let pagination: PaginationCapture = { kind: 'none', pages: [] };\n try {\n const kind = await detectPaginationKind(page);\n\n if (kind === 'numbered') {\n pagination = await crawlNumberedPagination(page);\n } else if (kind === 'load-more') {\n // Treat load-more similarly to infinite scroll capture\n pagination = await captureInfiniteScroll(page);\n pagination = { ...pagination, kind: 'load-more' };\n } else if (kind === 'infinite-scroll') {\n pagination = await captureInfiniteScroll(page);\n } else {\n // Capture single-page rows\n const rowValues = await captureFirstRow(page);\n pagination = { kind: 'none', pages: rowValues.length > 0 ? [{ pageNum: 1, rowValues }] : [] };\n }\n } catch {\n // leave pagination as none\n }\n\n // 4. Sort columns\n let sortColumns: Array<{ header: string; ariaSortAfterClick: string | null }> = [];\n try {\n sortColumns = await captureSortColumns(page);\n } catch {\n // proceed with empty sort\n }\n\n return {\n filters,\n filterStates,\n pagination,\n sortColumns,\n };\n}\n"],
|
|
5
|
+
"mappings": "AA0DA,eAAe,cAAc,MAAY,KAAK,MAAqB;AACjE,QAAM,QAAQ,KAAK;AAAA,IACjB,KAAK,iBAAiB,aAAa,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1D,IAAI,QAAc,OAAK,WAAW,GAAG,EAAE,CAAC;AAAA,EAC1C,CAAC;AACH;AAGA,eAAe,gBAAgB,MAA+B;AAC5D,MAAI;AACF,WAAO,MAAM,KAAK,SAAS,MAAM;AAC/B,YAAM,MAAM,SAAS,cAAc,UAAU;AAC7C,UAAI,CAAC,IAAK,QAAO,CAAC;AAClB,aAAO,MAAM,KAAK,IAAI,iBAAiB,IAAI,CAAC,EAAE,IAAI,QAAM,GAAG,UAAU,KAAK,CAAC;AAAA,IAC7E,CAAC;AAAA,EACH,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAe,gBAAgB,MAAoC;AACjE,MAAI;AACF,UAAM,OAAO,MAAM,KAAK,SAAS,MAAM,SAAS,KAAK,SAAS;AAC9D,UAAM,QAAQ,KAAK,MAAM,yCAAyC;AAClE,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,SAAS,MAAM,CAAC,EAAE,QAAQ,MAAM,EAAE,GAAG,EAAE;AAAA,EAChD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,gBAAgB,MAAkC;AAC/D,QAAM,UAAuB,CAAC;AAG9B,MAAI;AACF,UAAM,gBAAgB,MAAM,KAAK,QAAQ,oCAAoC,EAAE,IAAI;AACnF,eAAW,UAAU,cAAc,MAAM,GAAG,EAAE,GAAG;AAC/C,UAAI;AACF,cAAM,KAAK,MAAM,OAAO,aAAa,IAAI,EAAE,MAAM,MAAM,IAAI;AAC3D,cAAM,OAAO,MAAM,OAAO,aAAa,MAAM,EAAE,MAAM,MAAM,IAAI;AAC/D,cAAM,cAAc,KAAK,IAAI,EAAE,KAAK,OAAO,gBAAgB,IAAI,OAAO;AAGtE,YAAI,QAAQ;AACZ,YAAI,IAAI;AACN,kBAAQ,MAAM,KAAK,QAAQ,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,MAAM,EAAE;AAAA,QAC7E;AACA,YAAI,CAAC,OAAO;AACV,kBAAQ,MAAM,OAAO,SAAS,CAAC,OAA0B;AACvD,kBAAM,MAAM,GAAG,QAAQ,OAAO,KAAK,GAAG;AACtC,mBAAQ,KAA4B,WAAW,KAAK,KAAK;AAAA,UAC3D,CAAC,EAAE,MAAM,MAAM,EAAE;AAAA,QACnB;AACA,YAAI,CAAC,MAAO,SAAQ,QAAQ,MAAM;AAGlC,cAAM,aAA6B,MAAM,OAAO;AAAA,UAAS,CAAC,OACxD,MAAM,KAAK,GAAG,OAAO,EAAE,IAAI,QAAM,EAAE,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK,EAAE,EAAE;AAAA,QAC5E,EAAE,MAAM,MAAM,CAAC,CAAC;AAEhB,cAAM,UAAU,WAAW;AAAA,UACzB,OAAK,EAAE,2BAA2B,KAAK,EAAE,KAAK,KAAK,EAAE,UAAU;AAAA,QACjE;AAEA,YAAI,QAAQ,WAAW,EAAG;AAE1B,gBAAQ,KAAK,EAAE,MAAM,UAAU,OAAO,aAAa,QAAQ,CAAC;AAC5D,YAAI,QAAQ,UAAU,GAAI;AAAA,MAC5B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,QAAQ,SAAS,IAAI;AACvB,QAAI;AACF,YAAM,gBAAgB,MAAM,KACzB;AAAA,QACC;AAAA,MAEF,EACC,IAAI;AAEP,iBAAW,UAAU,cAAc,MAAM,GAAG,KAAK,QAAQ,MAAM,GAAG;AAChE,YAAI;AACF,gBAAM,KAAK,MAAM,OAAO,aAAa,IAAI,EAAE,MAAM,MAAM,IAAI;AAC3D,gBAAM,cAAc,MAAM,OAAO,aAAa,aAAa,EAAE,MAAM,MAAM,IAAI;AAC7E,gBAAM,cAAc,KAAK,IAAI,EAAE,KAAK,cAAc,sBAAsB,WAAW,OAAO;AAE1F,cAAI,QAAQ;AACZ,cAAI,IAAI;AACN,oBAAQ,MAAM,KAAK,QAAQ,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,MAAM,EAAE;AAAA,UAC7E;AACA,cAAI,CAAC,MAAO,SAAQ,eAAe;AAEnC,kBAAQ,KAAK,EAAE,MAAM,UAAU,OAAO,aAAa,SAAS,CAAC,EAAE,CAAC;AAChE,cAAI,QAAQ,UAAU,GAAI;AAAA,QAC5B,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAe,sBACb,MACA,QACA,QACsB;AACtB,QAAM,QAAqB;AAAA,IACzB,aAAa,OAAO;AAAA,IACpB,aAAa,OAAO;AAAA,IACpB,aAAa;AAAA,IACb,gBAAgB,CAAC;AAAA,EACnB;AAEA,MAAI;AACF,QAAI,OAAO,SAAS,UAAU;AAC5B,YAAM,KAAK,QAAQ,OAAO,WAAW,EAAE,aAAa,OAAO,KAAK;AAAA,IAClE,WAAW,OAAO,SAAS,UAAU;AACnC,YAAM,KAAK,QAAQ,OAAO,WAAW,EAAE,KAAK,OAAO,KAAK;AACxD,YAAM,KAAK,QAAQ,OAAO,WAAW,EAAE,MAAM,OAAO;AAAA,IACtD;AAEA,UAAM,cAAc,MAAM,IAAI;AAE9B,UAAM,cAAc,MAAM,gBAAgB,IAAI;AAC9C,UAAM,iBAAiB,MAAM,gBAAgB,IAAI;AAAA,EACnD,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,QAAI,OAAO,SAAS,UAAU;AAC5B,YAAM,cAAc,MAAM,KAAK,QAAQ,OAAO,WAAW,EAAE;AAAA,QACzD,CAAC,OAA0B,GAAG,QAAQ,CAAC,GAAG,SAAS;AAAA,MACrD;AACA,YAAM,KAAK,QAAQ,OAAO,WAAW,EAAE,aAAa,WAAW;AAAA,IACjE,WAAW,OAAO,SAAS,UAAU;AACnC,YAAM,KAAK,QAAQ,OAAO,WAAW,EAAE,KAAK,EAAE;AAC9C,YAAM,KAAK,QAAQ,OAAO,WAAW,EAAE,MAAM,OAAO;AAAA,IACtD;AACA,UAAM,cAAc,MAAM,GAAG;AAAA,EAC/B,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAMA,eAAe,kBAAkB,MAAY,SAA8C;AACzF,QAAM,SAAwB,CAAC;AAC/B,MAAI,cAAc;AAElB,aAAW,UAAU,SAAS;AAC5B,QAAI,eAAe,GAAI;AAEvB,UAAM,EAAE,QAAQ,IAAI;AACpB,QAAI,QAAQ,WAAW,EAAG;AAG1B,UAAM,UAAU,oBAAI,IAAY;AAAA,MAC9B;AAAA,MACA,KAAK,OAAO,QAAQ,SAAS,KAAK,CAAC;AAAA,MACnC,QAAQ,SAAS;AAAA,IACnB,CAAC;AACD,UAAM,SAAS,MAAM,KAAK,OAAO,EAAE,MAAM,GAAG,CAAC;AAE7C,eAAW,OAAO,QAAQ;AACxB,UAAI,eAAe,GAAI;AACvB,YAAM,SAAS,QAAQ,GAAG;AAC1B,UAAI;AACF,cAAM,IAAI,MAAM,sBAAsB,MAAM,QAAQ,MAAM;AAC1D,eAAO,KAAK,CAAC;AAAA,MACf,QAAQ;AAAA,MAER;AACA;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAMA,eAAe,qBACb,MACgE;AAChE,MAAI;AAEF,UAAM,gBAAgB,MAAM,KACzB,QAAQ,wEAAwE,EAChF,MAAM,EACN,MAAM,MAAM,CAAC;AAChB,QAAI,gBAAgB,EAAG,QAAO;AAG9B,UAAM,WAAW,MAAM,KACpB,QAAQ,qFAAqF,EAC7F,MAAM,EACN,MAAM,MAAM,CAAC;AAChB,QAAI,WAAW,EAAG,QAAO;AAGzB,UAAM,aAAa,MAAM,KAAK,SAAS,MAAM;AAC3C,aAAO,SAAS,gBAAgB,eAAe,OAAO,cAAc;AAAA,IACtE,CAAC,EAAE,MAAM,MAAM,KAAK;AACpB,QAAI,WAAY,QAAO;AAAA,EACzB,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAMA,eAAe,wBAAwB,MAAwC;AAC7E,QAAM,QAAyD,CAAC;AAChE,QAAM,YAAsB,CAAC;AAG7B,MAAI;AACF,UAAM,aAAa,KAAK,QAAQ,iFAAiF;AACjH,UAAM,YAAY,MAAM,WAAW,MAAM;AACzC,QAAI,YAAY,GAAG;AACjB,YAAM,WAAW,MAAM,WAAW,MAAM,EAAE;AAAA,QAAS,CAAC,OAClD,MAAM,KAAK,GAAG,OAAO,EAAE,IAAI,OAAK,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,OAAK,CAAC,MAAM,CAAC,CAAC;AAAA,MAC9E;AACA,gBAAU,KAAK,GAAG,QAAQ;AAAA,IAC5B;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI;AACJ,MAAI;AACF,iBAAa,MAAM,KAAK,SAAS,MAAM;AACrC,YAAM,UAAU,MAAM;AAAA,QACpB,SAAS,iBAAiB,4CAA4C;AAAA,MACxE;AACA,YAAM,OAAO,QACV,IAAI,OAAK,SAAU,EAAkB,UAAU,KAAK,GAAG,EAAE,CAAC,EAC1D,OAAO,OAAK,CAAC,MAAM,CAAC,CAAC;AACxB,aAAO,KAAK,SAAS,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI;AAAA,IAC/C,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AAGA,QAAM,KAAK,EAAE,SAAS,GAAG,WAAW,MAAM,gBAAgB,IAAI,EAAE,CAAC;AAGjE,WAAS,UAAU,GAAG,WAAW,IAAI,WAAW;AAC9C,QAAI;AACF,YAAM,UAAU,KAAK;AAAA,QACnB;AAAA,MACF,EAAE,MAAM;AAER,YAAM,aAAa,MAAM,QAAQ,WAAW,EAAE,MAAM,MAAM,IAAI;AAC9D,UAAI,WAAY;AAEhB,YAAM,QAAQ,MAAM;AACpB,YAAM,cAAc,MAAM,GAAI;AAE9B,YAAM,YAAY,MAAM,gBAAgB,IAAI;AAC5C,YAAM,KAAK,EAAE,SAAS,UAAU,CAAC;AAAA,IACnC,QAAQ;AACN;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,WAAW,UAAU,SAAS,IAAI,YAAY;AAAA,IAC9C;AAAA,EACF;AACF;AAMA,eAAe,sBAAsB,MAAwC;AAC3E,QAAM,QAAyD,CAAC;AAEhE,MAAI,eAAe;AACnB,MAAI,YAAY;AAEhB,SAAO,YAAY,IAAI;AACrB,QAAI;AACF,YAAM,WAAW,MAAM,KAAK;AAAA,QAAS,MACnC,SAAS,iBAAiB,UAAU,EAAE;AAAA,MACxC,EAAE,MAAM,MAAM,CAAC;AAEf,UAAI,aAAa,aAAc;AAC/B,qBAAe;AAGf,YAAM,KAAK,EAAE,SAAS,YAAY,GAAG,WAAW,MAAM,gBAAgB,IAAI,EAAE,CAAC;AAE7E,YAAM,KAAK,SAAS,MAAM,OAAO,SAAS,GAAG,SAAS,KAAK,YAAY,CAAC;AACxE,YAAM,IAAI,QAAc,OAAK,WAAW,GAAG,GAAG,CAAC;AAAA,IACjD,QAAQ;AACN;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,aAAa,MAAM,KAAK;AAAA,IAAS,MACrC,SAAS,iBAAiB,UAAU,EAAE;AAAA,EACxC,EAAE,MAAM,MAAM,MAAS;AAEvB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,YAAY,OAAO,eAAe,WAAW,aAAa;AAAA,IAC1D;AAAA,EACF;AACF;AAMA,eAAe,mBACb,MACuE;AACvE,QAAM,UAAwE,CAAC;AAE/E,MAAI;AACF,UAAM,WAAW,MAAM,KAAK,QAAQ,aAAa,EAAE,IAAI;AACvD,eAAW,MAAM,SAAS,MAAM,GAAG,CAAC,GAAG;AACrC,UAAI;AACF,cAAM,SAAS,MAAM,GAAG,UAAU,EAAE,MAAM,MAAM,EAAE;AAClD,cAAM,GAAG,MAAM;AACf,cAAM,cAAc,MAAM,GAAG;AAC7B,cAAM,qBAAqB,MAAM,GAAG,aAAa,WAAW,EAAE,MAAM,MAAM,IAAI;AAC9E,gBAAQ,KAAK,EAAE,QAAQ,OAAO,KAAK,GAAG,mBAAmB,CAAC;AAG1D,cAAM,GAAG,MAAM,EAAE,MAAM,MAAM,MAAS;AACtC,cAAM,cAAc,MAAM,GAAG;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AACT;AAMA,eAAsB,0BAA0B,MAA6C;AAE3F,MAAI,UAAuB,CAAC;AAC5B,MAAI;AACF,cAAU,MAAM,gBAAgB,IAAI;AAAA,EACtC,QAAQ;AAAA,EAER;AAGA,MAAI,eAA8B,CAAC;AACnC,MAAI;AACF,mBAAe,MAAM,kBAAkB,MAAM,OAAO;AAAA,EACtD,QAAQ;AAAA,EAER;AAGA,MAAI,aAAgC,EAAE,MAAM,QAAQ,OAAO,CAAC,EAAE;AAC9D,MAAI;AACF,UAAM,OAAO,MAAM,qBAAqB,IAAI;AAE5C,QAAI,SAAS,YAAY;AACvB,mBAAa,MAAM,wBAAwB,IAAI;AAAA,IACjD,WAAW,SAAS,aAAa;AAE/B,mBAAa,MAAM,sBAAsB,IAAI;AAC7C,mBAAa,EAAE,GAAG,YAAY,MAAM,YAAY;AAAA,IAClD,WAAW,SAAS,mBAAmB;AACrC,mBAAa,MAAM,sBAAsB,IAAI;AAAA,IAC/C,OAAO;AAEL,YAAM,YAAY,MAAM,gBAAgB,IAAI;AAC5C,mBAAa,EAAE,MAAM,QAAQ,OAAO,UAAU,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE;AAAA,IAC9F;AAAA,EACF,QAAQ;AAAA,EAER;AAGA,MAAI,cAA4E,CAAC;AACjF,MAAI;AACF,kBAAc,MAAM,mBAAmB,IAAI;AAAA,EAC7C,QAAQ;AAAA,EAER;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|