@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,75 +1,78 @@
1
+ /**
2
+ * PII scrubber — detects and redacts Personally Identifiable Information
3
+ * from crawled markdown, HTML, and extracted text before storage.
4
+ *
5
+ * GDPR Article 4(1): "any information relating to an identified or identifiable natural person"
6
+ */
7
+ // Patterns — ordered from most-specific to least-specific
1
8
  const PATTERNS = [
2
- // Email — RFC 5321 simplified
3
- { type: "email", re: /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/g },
4
- // US SSN — 9 digits with dashes or spaces
5
- { type: "ssn", re: /\b(?!000|666|9\d\d)\d{3}[-\s](?!00)\d{2}[-\s](?!0000)\d{4}\b/g },
6
- // Credit card — Visa/MC/Amex/Discover with optional spaces/dashes
7
- { type: "credit-card", re: /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{3,4}\b/g },
8
- // IBAN — international bank account (15-34 chars)
9
- { type: "iban", re: /\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b/g },
10
- // Phone — international or US formats
11
- { type: "phone", re: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b|\+\d{1,3}[-.\s]\d{2,4}[-.\s]\d{3,4}[-.\s]\d{3,4}/g },
12
- // Date of birth — common formats
13
- { type: "dob", re: /\b(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[12]\d|3[01])\/(?:19|20)\d{2}\b/g },
14
- // IPv4 private/internal addresses — only flag private-looking ones in content
15
- { type: "ip-address", re: /\b(?:192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b/g }
9
+ // Email — RFC 5321 simplified
10
+ { type: 'email', re: /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/g },
11
+ // US SSN — 9 digits with dashes or spaces
12
+ { type: 'ssn', re: /\b(?!000|666|9\d\d)\d{3}[-\s](?!00)\d{2}[-\s](?!0000)\d{4}\b/g },
13
+ // Credit card — Visa/MC/Amex/Discover with optional spaces/dashes
14
+ { type: 'credit-card', re: /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{3,4}\b/g },
15
+ // IBAN — international bank account (15-34 chars)
16
+ { type: 'iban', re: /\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b/g },
17
+ // Phone — international or US formats
18
+ { type: 'phone', re: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b|\+\d{1,3}[-.\s]\d{2,4}[-.\s]\d{3,4}[-.\s]\d{3,4}/g },
19
+ // Date of birth — common formats
20
+ { type: 'dob', re: /\b(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[12]\d|3[01])\/(?:19|20)\d{2}\b/g },
21
+ // IPv4 private/internal addresses — only flag private-looking ones in content
22
+ { type: 'ip-address', re: /\b(?:192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b/g },
16
23
  ];
17
24
  const REDACT_MAP = {
18
- email: "[EMAIL]",
19
- phone: "[PHONE]",
20
- ssn: "[SSN]",
21
- "credit-card": "[CARD]",
22
- "ip-address": "[IP]",
23
- passport: "[PASSPORT]",
24
- iban: "[IBAN]",
25
- dob: "[DOB]"
25
+ email: '[EMAIL]',
26
+ phone: '[PHONE]',
27
+ ssn: '[SSN]',
28
+ 'credit-card': '[CARD]',
29
+ 'ip-address': '[IP]',
30
+ passport: '[PASSPORT]',
31
+ iban: '[IBAN]',
32
+ dob: '[DOB]',
26
33
  };
27
- function detectPii(text) {
28
- const matches = [];
29
- const seen = /* @__PURE__ */ new Set();
30
- for (const { type, re } of PATTERNS) {
31
- const r = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
32
- let m;
33
- while ((m = r.exec(text)) !== null) {
34
- const key = `${type}:${m.index}`;
35
- if (!seen.has(key)) {
36
- seen.add(key);
37
- matches.push({ type, value: m[0], start: m.index, end: m.index + m[0].length });
38
- }
34
+ export function detectPii(text) {
35
+ const matches = [];
36
+ const seen = new Set();
37
+ for (const { type, re } of PATTERNS) {
38
+ const r = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');
39
+ let m;
40
+ while ((m = r.exec(text)) !== null) {
41
+ const key = `${type}:${m.index}`;
42
+ if (!seen.has(key)) {
43
+ seen.add(key);
44
+ matches.push({ type, value: m[0], start: m.index, end: m.index + m[0].length });
45
+ }
46
+ }
39
47
  }
40
- }
41
- return matches.sort((a, b) => a.start - b.start);
48
+ return matches.sort((a, b) => a.start - b.start);
42
49
  }
43
- function scrubPii(text) {
44
- const matches = detectPii(text);
45
- if (matches.length === 0) return { text, matches: [], scrubbed: false };
46
- let scrubbed = text;
47
- for (let i = matches.length - 1; i >= 0; i--) {
48
- const { type, start, end } = matches[i];
49
- scrubbed = scrubbed.slice(0, start) + REDACT_MAP[type] + scrubbed.slice(end);
50
- }
51
- return { text: scrubbed, matches, scrubbed: true };
50
+ export function scrubPii(text) {
51
+ const matches = detectPii(text);
52
+ if (matches.length === 0)
53
+ return { text, matches: [], scrubbed: false };
54
+ // Replace from end to start so indices stay valid
55
+ let scrubbed = text;
56
+ for (let i = matches.length - 1; i >= 0; i--) {
57
+ const { type, start, end } = matches[i];
58
+ scrubbed = scrubbed.slice(0, start) + REDACT_MAP[type] + scrubbed.slice(end);
59
+ }
60
+ return { text: scrubbed, matches, scrubbed: true };
52
61
  }
53
- function hasPii(text) {
54
- for (const { re } of PATTERNS) {
55
- const r = new RegExp(re.source, re.flags);
56
- if (r.test(text)) return true;
57
- }
58
- return false;
62
+ export function hasPii(text) {
63
+ for (const { re } of PATTERNS) {
64
+ const r = new RegExp(re.source, re.flags);
65
+ if (r.test(text))
66
+ return true;
67
+ }
68
+ return false;
59
69
  }
60
- function scrubObject(obj, fields) {
61
- const result = { ...obj };
62
- for (const field of fields) {
63
- if (typeof result[field] === "string") {
64
- result[field] = scrubPii(result[field]).text;
70
+ export function scrubObject(obj, fields) {
71
+ const result = { ...obj };
72
+ for (const field of fields) {
73
+ if (typeof result[field] === 'string') {
74
+ result[field] = scrubPii(result[field]).text;
75
+ }
65
76
  }
66
- }
67
- return result;
77
+ return result;
68
78
  }
69
- export {
70
- detectPii,
71
- hasPii,
72
- scrubObject,
73
- scrubPii
74
- };
75
- //# sourceMappingURL=pii-scrubber.js.map
@@ -1,6 +1,18 @@
1
+ /**
2
+ * Playwright test recorder — generate Playwright test code from crawl data.
3
+ *
4
+ * Given a list of crawled screens, generates a minimal Playwright test file that:
5
+ * 1. Navigates to each screen URL
6
+ * 2. Verifies the page title
7
+ * 3. Checks key elements visible on the page
8
+ * 4. Verifies no console errors (optional)
9
+ *
10
+ * This is a "smoke test" generator — not a full interaction recorder.
11
+ * Output: valid TypeScript Playwright test file ready to run with npx playwright test.
12
+ */
1
13
  function buildSmartLocatorHelper() {
2
- return `
3
- // smartLocator \u2014 falls back to similarity search when primary selector fails.
14
+ return `
15
+ // smartLocator — falls back to similarity search when primary selector fails.
4
16
  // Generated by ZeTa Crawler. Do not edit manually.
5
17
  async function smartLocator(page: any, primary: string, ref: { tag?: string; text?: string; role?: string; placeholder?: string }) {
6
18
  try {
@@ -27,76 +39,57 @@ async function smartLocator(page: any, primary: string, ref: { tag?: string; tex
27
39
  }`.trim();
28
40
  }
29
41
  function sanitizeTestName(name) {
30
- return name.replace(/[^a-zA-Z0-9 _-]/g, "").trim().slice(0, 80) || "Screen";
42
+ return name.replace(/[^a-zA-Z0-9 _-]/g, '').trim().slice(0, 80) || 'Screen';
31
43
  }
32
44
  function escapeString(s) {
33
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/`/g, "\\`");
45
+ return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/`/g, '\\`');
34
46
  }
35
- function generatePlaywrightTest(screens, opts = {}) {
36
- const {
37
- includeConsoleCheck = false,
38
- testTimeout = 3e4,
39
- projectName = "ZeTa Crawl",
40
- useSmartLocators = true
41
- } = opts;
42
- const needsSmartLocator = useSmartLocators && screens.some((s) => s.smartLocator != null);
43
- const lines = [
44
- `// Generated by ZeTa Crawler \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
45
- `// Project: ${escapeString(projectName)}`,
46
- `// Screens: ${screens.length}`,
47
- `// Run: npx playwright test`,
48
- "",
49
- "import { test, expect } from '@playwright/test';",
50
- ""
51
- ];
52
- if (needsSmartLocator) {
53
- lines.push(buildSmartLocatorHelper(), "");
54
- }
55
- lines.push(`test.setTimeout(${testTimeout});`, "");
56
- if (includeConsoleCheck) {
57
- lines.push(
58
- `test.beforeEach(async ({ page }) => {`,
59
- ` const errors: string[] = [];`,
60
- ` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`,
61
- ` page.on('pageerror', err => errors.push(err.message));`,
62
- ` (page as any).__consoleErrors = errors;`,
63
- `});`,
64
- ""
65
- );
66
- }
67
- for (const screen of screens) {
68
- const testName = sanitizeTestName(screen.name);
69
- lines.push(
70
- `test('${escapeString(testName)}', async ({ page }) => {`,
71
- ` await page.goto('${escapeString(screen.url)}');`,
72
- ` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/);`
73
- );
74
- if (screen.title) {
75
- lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/i);`);
76
- }
77
- if (screen.heading) {
78
- const h = escapeString(screen.heading.slice(0, 60));
79
- if (needsSmartLocator && screen.smartLocator) {
80
- const tag = escapeString(screen.smartLocator.tag);
81
- const text = escapeString(screen.smartLocator.text ?? "");
82
- lines.push(
83
- ` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`,
84
- ` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`
85
- );
86
- } else {
87
- lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);
88
- }
47
+ export function generatePlaywrightTest(screens, opts = {}) {
48
+ const { includeConsoleCheck = false, testTimeout = 30_000, projectName = 'ZeTa Crawl', useSmartLocators = true, } = opts;
49
+ const needsSmartLocator = useSmartLocators && screens.some(s => s.smartLocator != null);
50
+ const lines = [
51
+ `// Generated by ZeTa Crawler — ${new Date().toISOString()}`,
52
+ `// Project: ${escapeString(projectName)}`,
53
+ `// Screens: ${screens.length}`,
54
+ `// Run: npx playwright test`,
55
+ '',
56
+ "import { test, expect } from '@playwright/test';",
57
+ '',
58
+ ];
59
+ if (needsSmartLocator) {
60
+ lines.push(buildSmartLocatorHelper(), '');
89
61
  }
62
+ lines.push(`test.setTimeout(${testTimeout});`, '');
90
63
  if (includeConsoleCheck) {
91
- lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);
64
+ lines.push(`test.beforeEach(async ({ page }) => {`, ` const errors: string[] = [];`, ` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`, ` page.on('pageerror', err => errors.push(err.message));`, ` (page as any).__consoleErrors = errors;`, `});`, '');
92
65
  }
93
- lines.push(`});`, "");
94
- }
95
- return lines.join("\n");
66
+ for (const screen of screens) {
67
+ const testName = sanitizeTestName(screen.name);
68
+ lines.push(`test('${escapeString(testName)}', async ({ page }) => {`, ` await page.goto('${escapeString(screen.url)}');`, ` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))}/);`);
69
+ if (screen.title) {
70
+ lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))}/i);`);
71
+ }
72
+ if (screen.heading) {
73
+ const h = escapeString(screen.heading.slice(0, 60));
74
+ if (needsSmartLocator && screen.smartLocator) {
75
+ const tag = escapeString(screen.smartLocator.tag);
76
+ const text = escapeString(screen.smartLocator.text ?? '');
77
+ lines.push(` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`, ` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`);
78
+ }
79
+ else {
80
+ lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);
81
+ }
82
+ }
83
+ if (includeConsoleCheck) {
84
+ lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);
85
+ }
86
+ lines.push(`});`, '');
87
+ }
88
+ return lines.join('\n');
96
89
  }
97
- function generatePlaywrightConfig(opts = {}) {
98
- const { baseUrl = "http://localhost:3000", reportDir = "playwright-report" } = opts;
99
- return `// playwright.config.ts \u2014 generated by ZeTa Crawler
90
+ export function generatePlaywrightConfig(opts = {}) {
91
+ const { baseUrl = 'http://localhost:3000', reportDir = 'playwright-report' } = opts;
92
+ return `// playwright.config.ts — generated by ZeTa Crawler
100
93
  import { defineConfig } from '@playwright/test';
101
94
 
102
95
  export default defineConfig({
@@ -116,8 +109,3 @@ export default defineConfig({
116
109
  });
117
110
  `;
118
111
  }
119
- export {
120
- generatePlaywrightConfig,
121
- generatePlaywrightTest
122
- };
123
- //# sourceMappingURL=playwright-recorder.js.map
@@ -1,37 +1,40 @@
1
1
  const DESTRUCTIVE_ACTION = /\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop)\b/i;
2
2
  const SECRET_LIKE = /\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\b/i;
3
- function assessActionSafety(label, profile) {
4
- const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));
5
- if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {
6
- return {
7
- allowed: false,
8
- severity: "block",
9
- reason: `Blocked risky action "${label}" for ${profile.industry} profile.`
10
- };
11
- }
12
- return { allowed: true, severity: "info" };
3
+ export function assessActionSafety(label, profile) {
4
+ const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));
5
+ if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {
6
+ return {
7
+ allowed: false,
8
+ severity: 'block',
9
+ reason: `Blocked risky action "${label}" for ${profile.industry} profile.`,
10
+ };
11
+ }
12
+ return { allowed: true, severity: 'info' };
13
13
  }
14
- function redactSensitiveValue(value) {
15
- let redacted = value.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[email]").replace(/\b(?:\+?\d[\d -]{8,}\d)\b/g, "[phone-or-id]").replace(/\b(?:\d[ -]*?){13,19}\b/g, "[card-like-number]").replace(/\b[A-Za-z0-9_-]{24,}\b/g, "[token-like-value]");
16
- if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\s*[^,\s}]+/g, ": [redacted]");
17
- return redacted;
14
+ export function redactSensitiveValue(value) {
15
+ let redacted = value
16
+ .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[email]')
17
+ .replace(/\b(?:\+?\d[\d -]{8,}\d)\b/g, '[phone-or-id]')
18
+ .replace(/\b(?:\d[ -]*?){13,19}\b/g, '[card-like-number]')
19
+ .replace(/\b[A-Za-z0-9_-]{24,}\b/g, '[token-like-value]');
20
+ if (SECRET_LIKE.test(redacted))
21
+ redacted = redacted.replace(/:\s*[^,\s}]+/g, ': [redacted]');
22
+ return redacted;
18
23
  }
19
- function redactJson(value) {
20
- if (typeof value === "string") return redactSensitiveValue(value);
21
- if (Array.isArray(value)) return value.map(redactJson);
22
- if (value && typeof value === "object") {
23
- const out = {};
24
- for (const [key, item] of Object.entries(value)) {
25
- if (SECRET_LIKE.test(key)) out[key] = "[redacted]";
26
- else out[key] = redactJson(item);
24
+ export function redactJson(value) {
25
+ if (typeof value === 'string')
26
+ return redactSensitiveValue(value);
27
+ if (Array.isArray(value))
28
+ return value.map(redactJson);
29
+ if (value && typeof value === 'object') {
30
+ const out = {};
31
+ for (const [key, item] of Object.entries(value)) {
32
+ if (SECRET_LIKE.test(key))
33
+ out[key] = '[redacted]';
34
+ else
35
+ out[key] = redactJson(item);
36
+ }
37
+ return out;
27
38
  }
28
- return out;
29
- }
30
- return value;
39
+ return value;
31
40
  }
32
- export {
33
- assessActionSafety,
34
- redactJson,
35
- redactSensitiveValue
36
- };
37
- //# sourceMappingURL=crawl-safety.js.map