@detiq/crawler 1.1.7 → 1.1.9

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 (92) hide show
  1. package/dist/accessibility-auditor.js +74 -54
  2. package/dist/ai-login.js +249 -227
  3. package/dist/auth-token-rotator.js +67 -57
  4. package/dist/auth.js +336 -299
  5. package/dist/browser-extension.js +142 -104
  6. package/dist/captcha-solver.js +125 -98
  7. package/dist/color-contrast.js +89 -77
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +71 -54
  10. package/dist/cookie-consent.js +83 -68
  11. package/dist/cookie-jar.js +60 -53
  12. package/dist/crawler.js +5345 -4486
  13. package/dist/custom-js-injector.js +61 -45
  14. package/dist/dark-mode-capture.js +81 -67
  15. package/dist/dead-link-detector.js +55 -47
  16. package/dist/element-hash.js +12 -8
  17. package/dist/email-otp.js +195 -154
  18. package/dist/engine-router.js +98 -87
  19. package/dist/export/bulk-export.js +55 -70
  20. package/dist/export/vector-export.js +105 -77
  21. package/dist/extractors/advanced.d.ts +10 -0
  22. package/dist/extractors/advanced.js +266 -249
  23. package/dist/extractors/artifact-envelope.js +58 -59
  24. package/dist/failure-analysis-agent.js +33 -37
  25. package/dist/feature-flag-detector.js +66 -51
  26. package/dist/form-detector.js +75 -55
  27. package/dist/frontier.js +101 -76
  28. package/dist/gap-pass.js +89 -68
  29. package/dist/graphql-detector.js +72 -62
  30. package/dist/http-engine.js +330 -282
  31. package/dist/incremental-diff.js +87 -54
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +28 -63
  34. package/dist/index.js.map +2 -2
  35. package/dist/input-fuzzer.js +97 -93
  36. package/dist/interactive-probe.d.ts +36 -0
  37. package/dist/interactive-probe.js +827 -715
  38. package/dist/job-queue.js +2169 -2151
  39. package/dist/js-coverage.js +56 -52
  40. package/dist/js-router-detector.js +117 -86
  41. package/dist/language-detector.js +63 -51
  42. package/dist/link-classifier.js +50 -32
  43. package/dist/live-view.js +108 -91
  44. package/dist/locator-extractor.d.ts +37 -0
  45. package/dist/locator-extractor.js +199 -0
  46. package/dist/locator-extractor.js.map +7 -0
  47. package/dist/logger.js +7 -9
  48. package/dist/markdown-extract.js +61 -48
  49. package/dist/native-crawler.js +946 -780
  50. package/dist/network-observer.js +156 -135
  51. package/dist/network-throttle.js +40 -29
  52. package/dist/openapi-generator.js +92 -77
  53. package/dist/pdf-extractor.js +90 -72
  54. package/dist/performance-metrics.js +102 -80
  55. package/dist/phash.js +144 -125
  56. package/dist/pii-scrubber.js +68 -65
  57. package/dist/playwright-recorder.js +58 -70
  58. package/dist/policies/crawl-safety.js +34 -31
  59. package/dist/profiles/domain-profile.js +125 -130
  60. package/dist/proxy.js +172 -146
  61. package/dist/rate-limiter.js +65 -58
  62. package/dist/redirect-tracker.js +64 -60
  63. package/dist/redis-frontier.js +136 -116
  64. package/dist/redis-queue.js +144 -157
  65. package/dist/response-cache.js +140 -127
  66. package/dist/responsive-detector.js +46 -42
  67. package/dist/retry-policy.js +50 -46
  68. package/dist/robots-txt.js +83 -69
  69. package/dist/screen-reader.js +70 -45
  70. package/dist/screenshot-masker.js +77 -59
  71. package/dist/seo-checker.js +107 -88
  72. package/dist/session-capture.js +212 -192
  73. package/dist/session-export.js +133 -107
  74. package/dist/sitemap-writer.js +41 -38
  75. package/dist/sitemap.js +202 -141
  76. package/dist/skyvern.js +148 -123
  77. package/dist/sqs-queue.js +42 -42
  78. package/dist/sso-token.js +90 -74
  79. package/dist/stealth/human-behavior.js +173 -156
  80. package/dist/telemetry.js +82 -68
  81. package/dist/test-executor.js +335 -309
  82. package/dist/tls-fingerprint.js +88 -77
  83. package/dist/url-pattern-completer.js +90 -71
  84. package/dist/visual-diff-verdict.js +38 -23
  85. package/dist/webhook-notifier.js +69 -71
  86. package/dist/websocket-detector.js +68 -56
  87. package/dist/worker.js +38 -29
  88. package/package.json +1 -1
  89. package/src/index.ts +2 -0
  90. package/src/locator-extractor.ts +226 -0
  91. package/types/index.d.ts +2 -0
  92. package/types/locator-extractor.d.ts +37 -0
@@ -1,117 +1,143 @@
1
+ /**
2
+ * Converts a Playwright storageState (the format ZeTa already captures via
3
+ * session-capture.ts/live-view.ts and stores per-project) into snippets for
4
+ * other automation frameworks. Playwright's storageState is the de-facto
5
+ * standard for browser session export — see docs/UNIVERSAL-SESSION-CAPTURE-
6
+ * STRATEGY.md — so every other framework's import mechanism is a small,
7
+ * mechanical conversion away, not a new capture flow.
8
+ *
9
+ * Pure, dependency-free functions — no I/O, no network, fully unit-testable.
10
+ */
1
11
  function jsStringLiteral(s) {
2
- return JSON.stringify(s);
12
+ return JSON.stringify(s);
3
13
  }
14
+ /** Python dict-literal repr — JSON.stringify's `true`/`false`/`null` aren't
15
+ * valid Python (Python needs `True`/`False`/`None`), so a plain
16
+ * JSON.stringify would emit syntactically invalid Python for any cookie
17
+ * with a boolean field. */
4
18
  function pythonDictLiteral(obj) {
5
- const parts = Object.entries(obj).map(([k, v]) => {
6
- let repr;
7
- if (typeof v === "boolean") repr = v ? "True" : "False";
8
- else if (v === null || v === void 0) repr = "None";
9
- else if (typeof v === "number") repr = String(v);
10
- else repr = JSON.stringify(v);
11
- return `${JSON.stringify(k)}: ${repr}`;
12
- });
13
- return `{${parts.join(", ")}}`;
19
+ const parts = Object.entries(obj).map(([k, v]) => {
20
+ let repr;
21
+ if (typeof v === 'boolean')
22
+ repr = v ? 'True' : 'False';
23
+ else if (v === null || v === undefined)
24
+ repr = 'None';
25
+ else if (typeof v === 'number')
26
+ repr = String(v);
27
+ else
28
+ repr = JSON.stringify(v);
29
+ return `${JSON.stringify(k)}: ${repr}`;
30
+ });
31
+ return `{${parts.join(', ')}}`;
14
32
  }
15
- function toCypressSnippet(state) {
16
- const cookieLines = state.cookies.map((c) => {
17
- const opts = [`domain: ${jsStringLiteral(c.domain)}`, `path: ${jsStringLiteral(c.path)}`];
18
- if (c.secure) opts.push("secure: true");
19
- if (c.httpOnly) opts.push("httpOnly: true");
20
- if (c.sameSite) opts.push(`sameSite: ${jsStringLiteral(c.sameSite.toLowerCase())}`);
21
- return ` cy.setCookie(${jsStringLiteral(c.name)}, ${jsStringLiteral(c.value)}, { ${opts.join(", ")} });`;
22
- });
23
- const localStorageBlocks = state.origins.filter((o) => o.localStorage.length > 0).map((o) => {
24
- const sets = o.localStorage.map((kv) => ` window.localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`).join("\n");
25
- return ` cy.visit(${jsStringLiteral(o.origin)});
26
- cy.window().then((window) => {
27
- ${sets}
28
- });`;
29
- });
30
- return [
31
- "// Generated by ZeTa-AI \u2014 restores a captured browser session in Cypress.",
32
- "// Usage: import this file and call restoreZetaSession() inside cy.session(), e.g.:",
33
- "// cy.session('zeta-session', restoreZetaSession)",
34
- "Cypress.Commands.add('restoreZetaSession', () => {",
35
- ...cookieLines,
36
- ...localStorageBlocks,
37
- "});"
38
- ].join("\n");
33
+ /** cy.setCookie() + localStorage.setItem() inside a reusable custom command,
34
+ * following Cypress's documented cy.session() pattern (see references in
35
+ * docs/UNIVERSAL-SESSION-CAPTURE-STRATEGY.md). */
36
+ export function toCypressSnippet(state) {
37
+ const cookieLines = state.cookies.map((c) => {
38
+ const opts = [`domain: ${jsStringLiteral(c.domain)}`, `path: ${jsStringLiteral(c.path)}`];
39
+ if (c.secure)
40
+ opts.push('secure: true');
41
+ if (c.httpOnly)
42
+ opts.push('httpOnly: true');
43
+ if (c.sameSite)
44
+ opts.push(`sameSite: ${jsStringLiteral(c.sameSite.toLowerCase())}`);
45
+ return ` cy.setCookie(${jsStringLiteral(c.name)}, ${jsStringLiteral(c.value)}, { ${opts.join(', ')} });`;
46
+ });
47
+ const localStorageBlocks = state.origins
48
+ .filter((o) => o.localStorage.length > 0)
49
+ .map((o) => {
50
+ const sets = o.localStorage
51
+ .map((kv) => ` window.localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`)
52
+ .join('\n');
53
+ return ` cy.visit(${jsStringLiteral(o.origin)});\n cy.window().then((window) => {\n${sets}\n });`;
54
+ });
55
+ return [
56
+ '// Generated by ZeTa-AI — restores a captured browser session in Cypress.',
57
+ '// Usage: import this file and call restoreZetaSession() inside cy.session(), e.g.:',
58
+ "// cy.session('zeta-session', restoreZetaSession)",
59
+ "Cypress.Commands.add('restoreZetaSession', () => {",
60
+ ...cookieLines,
61
+ ...localStorageBlocks,
62
+ '});',
63
+ ].join('\n');
39
64
  }
40
- function toSeleniumPythonDict(state) {
41
- const byDomain = /* @__PURE__ */ new Map();
42
- for (const c of state.cookies) {
43
- const list = byDomain.get(c.domain) ?? [];
44
- list.push(c);
45
- byDomain.set(c.domain, list);
46
- }
47
- const lines = [
48
- "# Generated by ZeTa-AI \u2014 restores a captured browser session in Selenium.",
49
- "# Selenium requires the driver to already be on a matching domain before",
50
- "# add_cookie() will accept a cookie for it \u2014 navigate first, then restore.",
51
- "def restore_zeta_session(driver):"
52
- ];
53
- for (const [domain, cookies] of byDomain) {
54
- lines.push(` driver.get(${JSON.stringify(`https://${domain.replace(/^\./, "")}`)})`);
55
- for (const c of cookies) {
56
- const cookieDict = { name: c.name, value: c.value, path: c.path };
57
- if (c.expires && c.expires > 0) cookieDict.expiry = Math.floor(c.expires);
58
- if (c.secure) cookieDict.secure = true;
59
- lines.push(` driver.add_cookie(${pythonDictLiteral(cookieDict)})`);
65
+ /** driver.add_cookie() (Selenium requires the browser already be on the
66
+ * cookie's domain before adding it) + execute_script() for localStorage,
67
+ * per Selenium's documented Cookie API. */
68
+ export function toSeleniumPythonDict(state) {
69
+ const byDomain = new Map();
70
+ for (const c of state.cookies) {
71
+ const list = byDomain.get(c.domain) ?? [];
72
+ list.push(c);
73
+ byDomain.set(c.domain, list);
60
74
  }
61
- }
62
- for (const o of state.origins) {
63
- if (o.localStorage.length === 0) continue;
64
- lines.push(` driver.get(${JSON.stringify(o.origin)})`);
65
- for (const kv of o.localStorage) {
66
- lines.push(
67
- ` driver.execute_script(${JSON.stringify(`window.localStorage.setItem(arguments[0], arguments[1]);`)}, ${JSON.stringify(kv.name)}, ${JSON.stringify(kv.value)})`
68
- );
75
+ const lines = [
76
+ '# Generated by ZeTa-AI — restores a captured browser session in Selenium.',
77
+ '# Selenium requires the driver to already be on a matching domain before',
78
+ '# add_cookie() will accept a cookie for it — navigate first, then restore.',
79
+ 'def restore_zeta_session(driver):',
80
+ ];
81
+ for (const [domain, cookies] of byDomain) {
82
+ lines.push(` driver.get(${JSON.stringify(`https://${domain.replace(/^\./, '')}`)})`);
83
+ for (const c of cookies) {
84
+ const cookieDict = { name: c.name, value: c.value, path: c.path };
85
+ if (c.expires && c.expires > 0)
86
+ cookieDict.expiry = Math.floor(c.expires);
87
+ if (c.secure)
88
+ cookieDict.secure = true;
89
+ lines.push(` driver.add_cookie(${pythonDictLiteral(cookieDict)})`);
90
+ }
69
91
  }
70
- }
71
- return lines.join("\n");
92
+ for (const o of state.origins) {
93
+ if (o.localStorage.length === 0)
94
+ continue;
95
+ lines.push(` driver.get(${JSON.stringify(o.origin)})`);
96
+ for (const kv of o.localStorage) {
97
+ lines.push(` driver.execute_script(${JSON.stringify(`window.localStorage.setItem(arguments[0], arguments[1]);`)}, ${JSON.stringify(kv.name)}, ${JSON.stringify(kv.value)})`);
98
+ }
99
+ }
100
+ return lines.join('\n');
72
101
  }
73
- function toWebdriverIOSnippet(state) {
74
- const cookies = state.cookies.map((c) => ({
75
- name: c.name,
76
- value: c.value,
77
- domain: c.domain,
78
- path: c.path,
79
- secure: !!c.secure,
80
- httpOnly: !!c.httpOnly,
81
- ...c.expires && c.expires > 0 ? { expiry: Math.floor(c.expires) } : {}
82
- }));
83
- const localStorageSets = state.origins.filter((o) => o.localStorage.length > 0).map((o) => {
84
- const sets = o.localStorage.map((kv) => ` localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`).join("\n");
85
- return ` await browser.url(${jsStringLiteral(o.origin)});
86
- await browser.execute(() => {
87
- ${sets}
88
- });`;
89
- });
90
- return [
91
- "// Generated by ZeTa-AI \u2014 restores a captured browser session in WebdriverIO.",
92
- "async function restoreZetaSession(browser) {",
93
- ` await browser.setCookies(${JSON.stringify(cookies, null, 2).split("\n").join("\n ")});`,
94
- ...localStorageSets,
95
- "}",
96
- "module.exports = { restoreZetaSession };"
97
- ].join("\n");
102
+ /** browser.setCookies() accepts an array shape very close to Playwright's own
103
+ * cookie objects (per WebdriverIO's documented API) — closest 1:1 mapping
104
+ * of the three target formats. */
105
+ export function toWebdriverIOSnippet(state) {
106
+ const cookies = state.cookies.map((c) => ({
107
+ name: c.name,
108
+ value: c.value,
109
+ domain: c.domain,
110
+ path: c.path,
111
+ secure: !!c.secure,
112
+ httpOnly: !!c.httpOnly,
113
+ ...(c.expires && c.expires > 0 ? { expiry: Math.floor(c.expires) } : {}),
114
+ }));
115
+ const localStorageSets = state.origins
116
+ .filter((o) => o.localStorage.length > 0)
117
+ .map((o) => {
118
+ const sets = o.localStorage
119
+ .map((kv) => ` localStorage.setItem(${jsStringLiteral(kv.name)}, ${jsStringLiteral(kv.value)});`)
120
+ .join('\n');
121
+ return ` await browser.url(${jsStringLiteral(o.origin)});\n await browser.execute(() => {\n${sets}\n });`;
122
+ });
123
+ return [
124
+ '// Generated by ZeTa-AI — restores a captured browser session in WebdriverIO.',
125
+ 'async function restoreZetaSession(browser) {',
126
+ ` await browser.setCookies(${JSON.stringify(cookies, null, 2).split('\n').join('\n ')});`,
127
+ ...localStorageSets,
128
+ '}',
129
+ 'module.exports = { restoreZetaSession };',
130
+ ].join('\n');
98
131
  }
99
- function exportSessionAs(format, state) {
100
- switch (format) {
101
- case "cypress":
102
- return { content: toCypressSnippet(state), filename: "zeta-session.cy-commands.js", mimeType: "application/javascript" };
103
- case "selenium-python":
104
- return { content: toSeleniumPythonDict(state), filename: "zeta_session.py", mimeType: "text/x-python" };
105
- case "webdriverio":
106
- return { content: toWebdriverIOSnippet(state), filename: "zeta-session.wdio.js", mimeType: "application/javascript" };
107
- default:
108
- throw new Error(`Unknown session export format: ${format}`);
109
- }
132
+ export function exportSessionAs(format, state) {
133
+ switch (format) {
134
+ case 'cypress':
135
+ return { content: toCypressSnippet(state), filename: 'zeta-session.cy-commands.js', mimeType: 'application/javascript' };
136
+ case 'selenium-python':
137
+ return { content: toSeleniumPythonDict(state), filename: 'zeta_session.py', mimeType: 'text/x-python' };
138
+ case 'webdriverio':
139
+ return { content: toWebdriverIOSnippet(state), filename: 'zeta-session.wdio.js', mimeType: 'application/javascript' };
140
+ default:
141
+ throw new Error(`Unknown session export format: ${format}`);
142
+ }
110
143
  }
111
- export {
112
- exportSessionAs,
113
- toCypressSnippet,
114
- toSeleniumPythonDict,
115
- toWebdriverIOSnippet
116
- };
117
- //# sourceMappingURL=session-export.js.map
@@ -1,47 +1,50 @@
1
- import * as fs from "fs/promises";
2
- import * as path from "path";
1
+ /**
2
+ * Sitemap writer — generate XML sitemap from crawled screens.
3
+ *
4
+ * Compliant with sitemaps.org protocol (sitemap index + individual sitemap files).
5
+ * Splits into 50k-URL files per the spec when needed.
6
+ * Used for: SEO audit export, future crawl seed, and crawler/robots.txt comparison.
7
+ */
8
+ import * as fs from 'fs/promises';
9
+ import * as path from 'path';
3
10
  const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n';
4
11
  const SITEMAP_NS = 'xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"';
5
12
  function escape(s) {
6
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
13
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
7
14
  }
8
15
  function buildSitemapXml(urls) {
9
- const entries = urls.map((u) => [
10
- " <url>",
11
- ` <loc>${escape(u.loc)}</loc>`,
12
- u.lastmod ? ` <lastmod>${u.lastmod}</lastmod>` : "",
13
- u.changefreq ? ` <changefreq>${u.changefreq}</changefreq>` : "",
14
- u.priority !== void 0 ? ` <priority>${u.priority.toFixed(1)}</priority>` : "",
15
- " </url>"
16
- ].filter(Boolean).join("\n")).join("\n");
17
- return `${XML_HEADER}<urlset ${SITEMAP_NS}>
18
- ${entries}
19
- </urlset>`;
16
+ const entries = urls.map(u => [
17
+ ' <url>',
18
+ ` <loc>${escape(u.loc)}</loc>`,
19
+ u.lastmod ? ` <lastmod>${u.lastmod}</lastmod>` : '',
20
+ u.changefreq ? ` <changefreq>${u.changefreq}</changefreq>` : '',
21
+ u.priority !== undefined ? ` <priority>${u.priority.toFixed(1)}</priority>` : '',
22
+ ' </url>',
23
+ ].filter(Boolean).join('\n')).join('\n');
24
+ return `${XML_HEADER}<urlset ${SITEMAP_NS}>\n${entries}\n</urlset>`;
20
25
  }
21
- async function writeSitemap(urls, outputPath) {
22
- await fs.mkdir(path.dirname(outputPath), { recursive: true });
23
- await fs.writeFile(outputPath, buildSitemapXml(urls), "utf8");
26
+ export async function writeSitemap(urls, outputPath) {
27
+ await fs.mkdir(path.dirname(outputPath), { recursive: true });
28
+ await fs.writeFile(outputPath, buildSitemapXml(urls), 'utf8');
24
29
  }
25
- function screensToSitemapUrls(screens) {
26
- return screens.filter((s) => {
27
- try {
28
- return new URL(s.url).protocol.startsWith("http");
29
- } catch {
30
- return false;
31
- }
32
- }).map((s) => ({
33
- loc: s.url,
34
- lastmod: s.updatedAt ? s.updatedAt.toISOString().split("T")[0] : void 0,
35
- changefreq: "weekly",
36
- priority: s.depth === 0 ? 1 : s.depth === 1 ? 0.8 : 0.6
37
- }));
30
+ /** Convert crawled screen records to SitemapUrl objects */
31
+ export function screensToSitemapUrls(screens) {
32
+ return screens
33
+ .filter(s => {
34
+ try {
35
+ return new URL(s.url).protocol.startsWith('http');
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ })
41
+ .map(s => ({
42
+ loc: s.url,
43
+ lastmod: s.updatedAt ? s.updatedAt.toISOString().split('T')[0] : undefined,
44
+ changefreq: 'weekly',
45
+ priority: s.depth === 0 ? 1.0 : s.depth === 1 ? 0.8 : 0.6,
46
+ }));
38
47
  }
39
- function buildSitemapString(urls) {
40
- return buildSitemapXml(urls);
48
+ export function buildSitemapString(urls) {
49
+ return buildSitemapXml(urls);
41
50
  }
42
- export {
43
- buildSitemapString,
44
- screensToSitemapUrls,
45
- writeSitemap
46
- };
47
- //# sourceMappingURL=sitemap-writer.js.map