@detiq/crawler 1.1.9 → 1.1.11

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 (83) hide show
  1. package/dist/accessibility-auditor.js +54 -74
  2. package/dist/ai-login.js +227 -249
  3. package/dist/auth-token-rotator.js +57 -67
  4. package/dist/auth.js +299 -336
  5. package/dist/browser-extension.js +104 -142
  6. package/dist/captcha-solver.js +98 -125
  7. package/dist/color-contrast.js +77 -89
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +54 -71
  10. package/dist/cookie-consent.js +68 -83
  11. package/dist/cookie-jar.js +53 -60
  12. package/dist/crawler.js +4494 -5353
  13. package/dist/custom-js-injector.js +45 -61
  14. package/dist/dark-mode-capture.js +67 -81
  15. package/dist/dead-link-detector.js +47 -55
  16. package/dist/element-hash.js +8 -12
  17. package/dist/email-otp.js +154 -195
  18. package/dist/engine-router.js +87 -98
  19. package/dist/export/bulk-export.js +70 -55
  20. package/dist/export/vector-export.js +77 -105
  21. package/dist/extractors/advanced.js +249 -266
  22. package/dist/extractors/artifact-envelope.js +59 -58
  23. package/dist/failure-analysis-agent.js +37 -33
  24. package/dist/feature-flag-detector.js +51 -66
  25. package/dist/form-detector.js +55 -75
  26. package/dist/frontier.js +76 -101
  27. package/dist/gap-pass.js +68 -89
  28. package/dist/graphql-detector.js +62 -72
  29. package/dist/http-engine.js +282 -330
  30. package/dist/incremental-diff.js +54 -87
  31. package/dist/index.js +67 -28
  32. package/dist/input-fuzzer.js +93 -97
  33. package/dist/interactive-probe.js +715 -827
  34. package/dist/job-queue.js +2151 -2169
  35. package/dist/js-coverage.js +52 -56
  36. package/dist/js-router-detector.js +86 -117
  37. package/dist/language-detector.js +51 -63
  38. package/dist/link-classifier.js +32 -50
  39. package/dist/live-view.js +91 -108
  40. package/dist/locator-extractor.js +151 -187
  41. package/dist/logger.js +9 -7
  42. package/dist/markdown-extract.js +48 -61
  43. package/dist/native-crawler.js +780 -946
  44. package/dist/network-observer.js +135 -156
  45. package/dist/network-throttle.js +29 -40
  46. package/dist/openapi-generator.js +77 -92
  47. package/dist/pdf-extractor.js +72 -90
  48. package/dist/performance-metrics.js +80 -102
  49. package/dist/phash.js +125 -144
  50. package/dist/pii-scrubber.js +65 -68
  51. package/dist/playwright-recorder.js +70 -58
  52. package/dist/policies/crawl-safety.js +31 -34
  53. package/dist/profiles/domain-profile.js +130 -125
  54. package/dist/proxy.js +146 -172
  55. package/dist/rate-limiter.js +58 -65
  56. package/dist/redirect-tracker.js +60 -64
  57. package/dist/redis-frontier.js +116 -136
  58. package/dist/redis-queue.js +157 -144
  59. package/dist/response-cache.js +127 -140
  60. package/dist/responsive-detector.js +42 -46
  61. package/dist/retry-policy.js +46 -50
  62. package/dist/robots-txt.js +69 -83
  63. package/dist/screen-reader.js +45 -70
  64. package/dist/screenshot-masker.js +59 -77
  65. package/dist/seo-checker.js +88 -107
  66. package/dist/session-capture.js +192 -212
  67. package/dist/session-export.js +107 -133
  68. package/dist/sitemap-writer.js +38 -41
  69. package/dist/sitemap.js +141 -202
  70. package/dist/skyvern.js +123 -148
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +74 -90
  73. package/dist/stealth/human-behavior.js +156 -173
  74. package/dist/telemetry.js +68 -82
  75. package/dist/test-executor.js +309 -335
  76. package/dist/tls-fingerprint.js +77 -88
  77. package/dist/url-pattern-completer.js +71 -90
  78. package/dist/visual-diff-verdict.js +23 -38
  79. package/dist/webhook-notifier.js +71 -69
  80. package/dist/websocket-detector.js +56 -68
  81. package/dist/worker.js +29 -38
  82. package/package.json +1 -1
  83. package/src/job-queue.ts +136 -0
@@ -1,170 +1,149 @@
1
- import { redactJson } from './policies/crawl-safety.js';
1
+ import { redactJson } from "./policies/crawl-safety.js";
2
2
  const API_PATTERN = /\/(api|graphql|gql|rest|v\d+)\//i;
3
3
  const ASSET_EXT = /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map|json\.map)(\?|$)/i;
4
4
  function normalizeApiUrl(url) {
5
- const u = new URL(url);
6
- return u.pathname
7
- .replace(/\/[a-f0-9-]{8,}(?=\/|$)/gi, '/{id}')
8
- .replace(/\/c[a-z0-9]{20,}(?=\/|$)/g, '/{id}')
9
- .replace(/\/\d+(?=\/|$)/g, '/{id}');
5
+ const u = new URL(url);
6
+ return u.pathname.replace(/\/[a-f0-9-]{8,}(?=\/|$)/gi, "/{id}").replace(/\/c[a-z0-9]{20,}(?=\/|$)/g, "/{id}").replace(/\/\d+(?=\/|$)/g, "/{id}");
10
7
  }
11
8
  function inferShape(value) {
12
- if (Array.isArray(value))
13
- return value.length > 0 ? [inferShape(value[0])] : [];
14
- if (value && typeof value === 'object') {
15
- const out = {};
16
- for (const [key, item] of Object.entries(value).slice(0, 40))
17
- out[key] = inferShape(item);
18
- return out;
19
- }
20
- return typeof value;
9
+ if (Array.isArray(value)) return value.length > 0 ? [inferShape(value[0])] : [];
10
+ if (value && typeof value === "object") {
11
+ const out = {};
12
+ for (const [key, item] of Object.entries(value).slice(0, 40)) out[key] = inferShape(item);
13
+ return out;
14
+ }
15
+ return typeof value;
21
16
  }
22
- /** P4: Extract page-URL candidates from a JSON response body string. */
23
17
  function extractPageUrls(jsonStr) {
24
- const PAGE_URL = /"(?:url|href|path|link|route|to|src|redirect)"\s*:\s*"(\/[\w\-\/\.~@%]+)"/g;
25
- const ASSET = /\.(png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|css|js|map|json)$/i;
26
- const API_PATH = /^\/(api|v\d+|graphql|gql|rest|rpc|static|_next|assets)\//i;
27
- const found = new Set();
28
- let m;
29
- PAGE_URL.lastIndex = 0;
30
- while ((m = PAGE_URL.exec(jsonStr)) !== null) {
31
- const u = m[1];
32
- if (u && u.length > 1 && u.length < 120 && !ASSET.test(u) && !API_PATH.test(u))
33
- found.add(u);
34
- }
35
- return [...found];
18
+ const PAGE_URL = /"(?:url|href|path|link|route|to|src|redirect)"\s*:\s*"(\/[\w\-\/\.~@%]+)"/g;
19
+ const ASSET = /\.(png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|css|js|map|json)$/i;
20
+ const API_PATH = /^\/(api|v\d+|graphql|gql|rest|rpc|static|_next|assets)\//i;
21
+ const found = /* @__PURE__ */ new Set();
22
+ let m;
23
+ PAGE_URL.lastIndex = 0;
24
+ while ((m = PAGE_URL.exec(jsonStr)) !== null) {
25
+ const u = m[1];
26
+ if (u && u.length > 1 && u.length < 120 && !ASSET.test(u) && !API_PATH.test(u)) found.add(u);
27
+ }
28
+ return [...found];
36
29
  }
37
- export function attachNetworkObserver(context, sink, onPageUrlDiscovered) {
38
- const seen = new Map();
39
- const requestTimes = new Map();
40
- context.on('request', (request) => {
41
- try {
42
- const url = request.url();
43
- const rtype = request.resourceType();
44
- if ((rtype !== 'xhr' && rtype !== 'fetch') || !API_PATTERN.test(url) || ASSET_EXT.test(url))
45
- return;
46
- const method = request.method();
47
- const normalized = normalizeApiUrl(url);
48
- const key = `${method}:${normalized}`;
49
- requestTimes.set(url, Date.now());
50
- if (!seen.has(key)) {
51
- const row = { method, urlPattern: normalized, requestKind: rtype };
52
- seen.set(key, row);
53
- sink.push(row);
54
- }
55
- }
56
- catch {
57
- // non-fatal
30
+ function attachNetworkObserver(context, sink, onPageUrlDiscovered) {
31
+ const seen = /* @__PURE__ */ new Map();
32
+ const requestTimes = /* @__PURE__ */ new Map();
33
+ context.on("request", (request) => {
34
+ try {
35
+ const url = request.url();
36
+ const rtype = request.resourceType();
37
+ if (rtype !== "xhr" && rtype !== "fetch" || !API_PATTERN.test(url) || ASSET_EXT.test(url)) return;
38
+ const method = request.method();
39
+ const normalized = normalizeApiUrl(url);
40
+ const key = `${method}:${normalized}`;
41
+ requestTimes.set(url, Date.now());
42
+ if (!seen.has(key)) {
43
+ const row = { method, urlPattern: normalized, requestKind: rtype };
44
+ seen.set(key, row);
45
+ sink.push(row);
46
+ }
47
+ } catch {
48
+ }
49
+ });
50
+ context.on("response", async (response) => {
51
+ try {
52
+ const request = response.request();
53
+ const url = request.url();
54
+ const rtype = request.resourceType();
55
+ if (rtype !== "xhr" && rtype !== "fetch" || !API_PATTERN.test(url) || ASSET_EXT.test(url)) return;
56
+ const normalized = normalizeApiUrl(url);
57
+ const key = `${request.method()}:${normalized}`;
58
+ const durationMs = Date.now() - (requestTimes.get(url) ?? Date.now());
59
+ let row = seen.get(key);
60
+ if (!row) {
61
+ row = { method: request.method(), urlPattern: normalized, requestKind: rtype };
62
+ seen.set(key, row);
63
+ sink.push(row);
64
+ }
65
+ row.statusCode = response.status();
66
+ row.durationMs = durationMs;
67
+ const contentType = response.headers()["content-type"] ?? "";
68
+ if (contentType.includes("application/json")) {
69
+ const body = await response.json().catch(() => void 0);
70
+ if (body !== void 0) {
71
+ row.responseShape = redactJson(inferShape(body));
72
+ if (onPageUrlDiscovered) {
73
+ for (const u of extractPageUrls(JSON.stringify(body))) onPageUrlDiscovered(u);
74
+ }
58
75
  }
59
- });
60
- context.on('response', async (response) => {
76
+ }
77
+ if (response.status() >= 400) {
61
78
  try {
62
- const request = response.request();
63
- const url = request.url();
64
- const rtype = request.resourceType();
65
- if ((rtype !== 'xhr' && rtype !== 'fetch') || !API_PATTERN.test(url) || ASSET_EXT.test(url))
66
- return;
67
- const normalized = normalizeApiUrl(url);
68
- const key = `${request.method()}:${normalized}`;
69
- const durationMs = Date.now() - (requestTimes.get(url) ?? Date.now());
70
- let row = seen.get(key);
71
- if (!row) {
72
- row = { method: request.method(), urlPattern: normalized, requestKind: rtype };
73
- seen.set(key, row);
74
- sink.push(row);
75
- }
76
- row.statusCode = response.status();
77
- row.durationMs = durationMs;
78
- const contentType = response.headers()['content-type'] ?? '';
79
- if (contentType.includes('application/json')) {
80
- const body = await response.json().catch(() => undefined);
81
- if (body !== undefined) {
82
- row.responseShape = redactJson(inferShape(body));
83
- // P4: scan JSON body for page-URL candidates (non-API paths)
84
- if (onPageUrlDiscovered) {
85
- for (const u of extractPageUrls(JSON.stringify(body)))
86
- onPageUrlDiscovered(u);
87
- }
88
- }
89
- }
90
- if (response.status() >= 400) {
91
- try {
92
- const errText = await response.text();
93
- row.errorBody = errText.slice(0, 300);
94
- }
95
- catch { /* non-fatal */ }
96
- }
97
- }
98
- catch {
99
- // non-fatal
79
+ const errText = await response.text();
80
+ row.errorBody = errText.slice(0, 300);
81
+ } catch {
100
82
  }
101
- });
83
+ }
84
+ } catch {
85
+ }
86
+ });
102
87
  }
103
- /**
104
- * Full HAR capture via CDP — intercepts ALL network traffic, not just API endpoints.
105
- * Returns a controller with getHar() and detach().
106
- */
107
- export function attachHarCapture(cdpSession, pageTitle = 'Page') {
108
- const entries = [];
109
- const pending = new Map();
110
- const pageId = `page_${String(Date.now())}`;
111
- const pageStarted = new Date().toISOString();
112
- const onRequest = (evt) => {
113
- const { requestId, request } = evt ?? {};
114
- if (!requestId || !request)
115
- return;
116
- const headers = {};
117
- for (const h of (request.headers ?? []))
118
- if (h.name)
119
- headers[String(h.name).toLowerCase()] = String(h.value ?? '');
120
- pending.set(requestId, { method: request.method ?? 'GET', url: request.url ?? '', headers, startMs: Date.now() });
121
- };
122
- const onResponse = (evt) => {
123
- const { requestId, response } = evt ?? {};
124
- if (!requestId || !response)
125
- return;
126
- const req = pending.get(requestId);
127
- if (!req)
128
- return;
129
- pending.delete(requestId);
130
- const durationMs = Date.now() - req.startMs;
131
- const responseHeaders = {};
132
- for (const h of (response.headers ?? []))
133
- if (h.name)
134
- responseHeaders[String(h.name).toLowerCase()] = String(h.value ?? '');
135
- entries.push({
136
- startedAt: new Date(req.startMs).toISOString(),
137
- method: req.method,
138
- url: req.url,
139
- status: response.status ?? 0,
140
- statusText: response.statusText ?? '',
141
- requestHeaders: req.headers,
142
- responseHeaders,
143
- contentType: responseHeaders['content-type'] ?? '',
144
- bodySize: response.encodedDataLength ?? -1,
145
- durationMs,
146
- timings: { send: 0, wait: Math.round(durationMs * 0.9), receive: Math.round(durationMs * 0.1) },
147
- });
148
- };
149
- if (cdpSession?.on) {
150
- cdpSession.on('Network.requestWillBeSent', onRequest);
151
- cdpSession.on('Network.responseReceived', onResponse);
88
+ function attachHarCapture(cdpSession, pageTitle = "Page") {
89
+ const entries = [];
90
+ const pending = /* @__PURE__ */ new Map();
91
+ const pageId = `page_${String(Date.now())}`;
92
+ const pageStarted = (/* @__PURE__ */ new Date()).toISOString();
93
+ const onRequest = (evt) => {
94
+ const { requestId, request } = evt ?? {};
95
+ if (!requestId || !request) return;
96
+ const headers = {};
97
+ for (const h of request.headers ?? []) if (h.name) headers[String(h.name).toLowerCase()] = String(h.value ?? "");
98
+ pending.set(requestId, { method: request.method ?? "GET", url: request.url ?? "", headers, startMs: Date.now() });
99
+ };
100
+ const onResponse = (evt) => {
101
+ const { requestId, response } = evt ?? {};
102
+ if (!requestId || !response) return;
103
+ const req = pending.get(requestId);
104
+ if (!req) return;
105
+ pending.delete(requestId);
106
+ const durationMs = Date.now() - req.startMs;
107
+ const responseHeaders = {};
108
+ for (const h of response.headers ?? []) if (h.name) responseHeaders[String(h.name).toLowerCase()] = String(h.value ?? "");
109
+ entries.push({
110
+ startedAt: new Date(req.startMs).toISOString(),
111
+ method: req.method,
112
+ url: req.url,
113
+ status: response.status ?? 0,
114
+ statusText: response.statusText ?? "",
115
+ requestHeaders: req.headers,
116
+ responseHeaders,
117
+ contentType: responseHeaders["content-type"] ?? "",
118
+ bodySize: response.encodedDataLength ?? -1,
119
+ durationMs,
120
+ timings: { send: 0, wait: Math.round(durationMs * 0.9), receive: Math.round(durationMs * 0.1) }
121
+ });
122
+ };
123
+ if (cdpSession?.on) {
124
+ cdpSession.on("Network.requestWillBeSent", onRequest);
125
+ cdpSession.on("Network.responseReceived", onResponse);
126
+ }
127
+ return {
128
+ getHar() {
129
+ return {
130
+ version: "1.2",
131
+ creator: { name: "ZeTa-Crawler", version: "1.0" },
132
+ pages: [{ id: pageId, title: pageTitle, startedAt: pageStarted }],
133
+ entries: [...entries]
134
+ };
135
+ },
136
+ detach() {
137
+ if (cdpSession?.off) {
138
+ cdpSession.off("Network.requestWillBeSent", onRequest);
139
+ cdpSession.off("Network.responseReceived", onResponse);
140
+ }
141
+ pending.clear();
152
142
  }
153
- return {
154
- getHar() {
155
- return {
156
- version: '1.2',
157
- creator: { name: 'ZeTa-Crawler', version: '1.0' },
158
- pages: [{ id: pageId, title: pageTitle, startedAt: pageStarted }],
159
- entries: [...entries],
160
- };
161
- },
162
- detach() {
163
- if (cdpSession?.off) {
164
- cdpSession.off('Network.requestWillBeSent', onRequest);
165
- cdpSession.off('Network.responseReceived', onResponse);
166
- }
167
- pending.clear();
168
- },
169
- };
143
+ };
170
144
  }
145
+ export {
146
+ attachHarCapture,
147
+ attachNetworkObserver
148
+ };
149
+ //# sourceMappingURL=network-observer.js.map
@@ -1,45 +1,34 @@
1
- /**
2
- * Network throttle simulation via CDP.
3
- *
4
- * Simulates different network conditions during crawl to test how the app
5
- * behaves under slow connections. Useful for detecting loading states, error
6
- * handling, and performance degradation on poor networks.
7
- *
8
- * Conditions mirror Chrome DevTools presets:
9
- * - slow3g: ~500kbps download, high latency
10
- * - fast3g: ~1.6Mbps download, medium latency
11
- * - 4g: ~9Mbps download, low latency
12
- * - offline: no connectivity
13
- * - none: no throttle (default)
14
- */
15
1
  const PRESETS = {
16
- none: { offline: false, downloadThroughput: -1, uploadThroughput: -1, latency: 0 },
17
- slow3g: { offline: false, downloadThroughput: 500 * 1024 / 8, uploadThroughput: 500 * 1024 / 8, latency: 2_000 },
18
- fast3g: { offline: false, downloadThroughput: 1.6 * 1024 * 1024 / 8, uploadThroughput: 750 * 1024 / 8, latency: 562.5 },
19
- '4g': { offline: false, downloadThroughput: 9 * 1024 * 1024 / 8, uploadThroughput: 2.67 * 1024 * 1024 / 8, latency: 85 },
20
- offline: { offline: true, downloadThroughput: 0, uploadThroughput: 0, latency: 0 },
2
+ none: { offline: false, downloadThroughput: -1, uploadThroughput: -1, latency: 0 },
3
+ slow3g: { offline: false, downloadThroughput: 500 * 1024 / 8, uploadThroughput: 500 * 1024 / 8, latency: 2e3 },
4
+ fast3g: { offline: false, downloadThroughput: 1.6 * 1024 * 1024 / 8, uploadThroughput: 750 * 1024 / 8, latency: 562.5 },
5
+ "4g": { offline: false, downloadThroughput: 9 * 1024 * 1024 / 8, uploadThroughput: 2.67 * 1024 * 1024 / 8, latency: 85 },
6
+ offline: { offline: true, downloadThroughput: 0, uploadThroughput: 0, latency: 0 }
21
7
  };
22
- export async function applyNetworkThrottle(cdpSession, preset) {
23
- if (!cdpSession?.send)
24
- return;
25
- const condition = PRESETS[preset];
26
- try {
27
- await cdpSession.send('Network.enable');
28
- await cdpSession.send('Network.emulateNetworkConditions', condition);
29
- }
30
- catch { /* non-fatal — CDP may not be available */ }
8
+ async function applyNetworkThrottle(cdpSession, preset) {
9
+ if (!cdpSession?.send) return;
10
+ const condition = PRESETS[preset];
11
+ try {
12
+ await cdpSession.send("Network.enable");
13
+ await cdpSession.send("Network.emulateNetworkConditions", condition);
14
+ } catch {
15
+ }
31
16
  }
32
- export async function clearNetworkThrottle(cdpSession) {
33
- await applyNetworkThrottle(cdpSession, 'none');
17
+ async function clearNetworkThrottle(cdpSession) {
18
+ await applyNetworkThrottle(cdpSession, "none");
34
19
  }
35
- /** Apply throttle, run fn, restore. Returns fn's result. */
36
- export async function withNetworkThrottle(cdpSession, preset, fn, restoreAfter = true) {
37
- await applyNetworkThrottle(cdpSession, preset);
38
- try {
39
- return await fn();
40
- }
41
- finally {
42
- if (restoreAfter)
43
- await clearNetworkThrottle(cdpSession).catch(() => { });
44
- }
20
+ async function withNetworkThrottle(cdpSession, preset, fn, restoreAfter = true) {
21
+ await applyNetworkThrottle(cdpSession, preset);
22
+ try {
23
+ return await fn();
24
+ } finally {
25
+ if (restoreAfter) await clearNetworkThrottle(cdpSession).catch(() => {
26
+ });
27
+ }
45
28
  }
29
+ export {
30
+ applyNetworkThrottle,
31
+ clearNetworkThrottle,
32
+ withNetworkThrottle
33
+ };
34
+ //# sourceMappingURL=network-throttle.js.map
@@ -1,104 +1,89 @@
1
- /**
2
- * OpenAPI 3.0 spec generator from observed network traffic.
3
- *
4
- * Takes the ApiEndpointObservationInput[] collected by the network observer
5
- * during crawl and generates a minimal but valid OpenAPI 3.0 spec.
6
- *
7
- * The spec captures: endpoints, methods, observed response shapes, status codes.
8
- * It does NOT capture request bodies (we'd need to intercept requests too) — that's
9
- * marked as a TODO. But it's enough for generating smoke-test suites.
10
- */
11
1
  function extractPathParams(urlPattern) {
12
- const params = [];
13
- const matches = urlPattern.matchAll(/\{(\w+)\}/g);
14
- for (const m of matches) {
15
- params.push({ name: m[1], in: 'path', required: true, schema: { type: 'string' } });
16
- }
17
- return params;
2
+ const params = [];
3
+ const matches = urlPattern.matchAll(/\{(\w+)\}/g);
4
+ for (const m of matches) {
5
+ params.push({ name: m[1], in: "path", required: true, schema: { type: "string" } });
6
+ }
7
+ return params;
18
8
  }
19
9
  function methodSummary(method, path) {
20
- const resource = path.split('/').filter(s => !s.startsWith('{') && s.length > 0).pop() ?? 'resource';
21
- const summaries = {
22
- GET: `Get ${resource}`,
23
- POST: `Create ${resource}`,
24
- PUT: `Update ${resource}`,
25
- PATCH: `Partially update ${resource}`,
26
- DELETE: `Delete ${resource}`,
27
- };
28
- return summaries[method.toUpperCase()] ?? `${method} ${resource}`;
10
+ const resource = path.split("/").filter((s) => !s.startsWith("{") && s.length > 0).pop() ?? "resource";
11
+ const summaries = {
12
+ GET: `Get ${resource}`,
13
+ POST: `Create ${resource}`,
14
+ PUT: `Update ${resource}`,
15
+ PATCH: `Partially update ${resource}`,
16
+ DELETE: `Delete ${resource}`
17
+ };
18
+ return summaries[method.toUpperCase()] ?? `${method} ${resource}`;
29
19
  }
30
20
  function inferTag(path) {
31
- const parts = path.split('/').filter(s => s.length > 0 && !s.startsWith('{'));
32
- return parts[0] ?? 'default';
21
+ const parts = path.split("/").filter((s) => s.length > 0 && !s.startsWith("{"));
22
+ return parts[0] ?? "default";
33
23
  }
34
- export function generateOpenApiSpec(observations, opts = {}) {
35
- const { title = 'ZeTa Discovered API', baseUrl = '/', version = '1.0.0' } = opts;
36
- const spec = {
37
- openapi: '3.0.3',
38
- info: { title, version, description: 'Auto-generated by ZeTa crawler from observed network traffic.' },
39
- servers: [{ url: baseUrl }],
40
- paths: {},
24
+ function generateOpenApiSpec(observations, opts = {}) {
25
+ const { title = "ZeTa Discovered API", baseUrl = "/", version = "1.0.0" } = opts;
26
+ const spec = {
27
+ openapi: "3.0.3",
28
+ info: { title, version, description: "Auto-generated by ZeTa crawler from observed network traffic." },
29
+ servers: [{ url: baseUrl }],
30
+ paths: {}
31
+ };
32
+ for (const obs of observations) {
33
+ const path = obs.urlPattern;
34
+ const method = obs.method.toLowerCase();
35
+ if (!spec.paths[path]) spec.paths[path] = {};
36
+ const params = extractPathParams(path);
37
+ const statusCode = obs.statusCode ?? 200;
38
+ const responses = {
39
+ [String(statusCode)]: {
40
+ description: statusCode < 400 ? "Successful response" : "Error response",
41
+ content: obs.responseShape ? { "application/json": { schema: shapeToJsonSchema(obs.responseShape) } } : void 0
42
+ }
41
43
  };
42
- for (const obs of observations) {
43
- const path = obs.urlPattern;
44
- const method = obs.method.toLowerCase();
45
- if (!spec.paths[path])
46
- spec.paths[path] = {};
47
- const params = extractPathParams(path);
48
- const statusCode = obs.statusCode ?? 200;
49
- const responses = {
50
- [String(statusCode)]: {
51
- description: statusCode < 400 ? 'Successful response' : 'Error response',
52
- content: obs.responseShape
53
- ? { 'application/json': { schema: shapeToJsonSchema(obs.responseShape) } }
54
- : undefined,
55
- },
56
- };
57
- const op = {
58
- summary: methodSummary(obs.method, path),
59
- parameters: params.length > 0 ? params : undefined,
60
- responses,
61
- tags: [inferTag(path)],
62
- };
63
- spec.paths[path][method] = op;
64
- }
65
- return spec;
44
+ const op = {
45
+ summary: methodSummary(obs.method, path),
46
+ parameters: params.length > 0 ? params : void 0,
47
+ responses,
48
+ tags: [inferTag(path)]
49
+ };
50
+ spec.paths[path][method] = op;
51
+ }
52
+ return spec;
66
53
  }
67
54
  function shapeToJsonSchema(shape) {
68
- if (Array.isArray(shape))
69
- return { type: 'array', items: shape.length > 0 ? shapeToJsonSchema(shape[0]) : {} };
70
- if (shape && typeof shape === 'object') {
71
- const props = {};
72
- for (const [k, v] of Object.entries(shape))
73
- props[k] = shapeToJsonSchema(v);
74
- return { type: 'object', properties: props };
75
- }
76
- return { type: String(shape) };
55
+ if (Array.isArray(shape)) return { type: "array", items: shape.length > 0 ? shapeToJsonSchema(shape[0]) : {} };
56
+ if (shape && typeof shape === "object") {
57
+ const props = {};
58
+ for (const [k, v] of Object.entries(shape)) props[k] = shapeToJsonSchema(v);
59
+ return { type: "object", properties: props };
60
+ }
61
+ return { type: String(shape) };
77
62
  }
78
- export function specToYaml(spec) {
79
- // Minimal YAML serializer — avoids yaml dep
80
- function toYaml(val, indent = 0) {
81
- const pad = ' '.repeat(indent);
82
- if (val === null || val === undefined)
83
- return 'null';
84
- if (typeof val === 'string')
85
- return val.includes('\n') || val.includes(':') ? `"${val.replace(/"/g, '\\"')}"` : val;
86
- if (typeof val === 'number' || typeof val === 'boolean')
87
- return String(val);
88
- if (Array.isArray(val))
89
- return val.map(v => `\n${pad}- ${toYaml(v, indent + 1).trimStart()}`).join('') || '[]';
90
- if (typeof val === 'object') {
91
- const entries = Object.entries(val);
92
- if (entries.length === 0)
93
- return '{}';
94
- return entries.map(([k, v]) => {
95
- const vStr = toYaml(v, indent + 1);
96
- return typeof v === 'object' && v !== null && !Array.isArray(v)
97
- ? `\n${pad}${k}:${vStr}`
98
- : `\n${pad}${k}: ${vStr.trimStart()}`;
99
- }).join('');
100
- }
101
- return String(val);
63
+ function specToYaml(spec) {
64
+ function toYaml(val, indent = 0) {
65
+ const pad = " ".repeat(indent);
66
+ if (val === null || val === void 0) return "null";
67
+ if (typeof val === "string") return val.includes("\n") || val.includes(":") ? `"${val.replace(/"/g, '\\"')}"` : val;
68
+ if (typeof val === "number" || typeof val === "boolean") return String(val);
69
+ if (Array.isArray(val)) return val.map((v) => `
70
+ ${pad}- ${toYaml(v, indent + 1).trimStart()}`).join("") || "[]";
71
+ if (typeof val === "object") {
72
+ const entries = Object.entries(val);
73
+ if (entries.length === 0) return "{}";
74
+ return entries.map(([k, v]) => {
75
+ const vStr = toYaml(v, indent + 1);
76
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? `
77
+ ${pad}${k}:${vStr}` : `
78
+ ${pad}${k}: ${vStr.trimStart()}`;
79
+ }).join("");
102
80
  }
103
- return `openapi: "${spec.openapi}"${toYaml(spec, 0)}`;
81
+ return String(val);
82
+ }
83
+ return `openapi: "${spec.openapi}"${toYaml(spec, 0)}`;
104
84
  }
85
+ export {
86
+ generateOpenApiSpec,
87
+ specToYaml
88
+ };
89
+ //# sourceMappingURL=openapi-generator.js.map