swagger-digest 0.0.0-stage → 0.1.2

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.
@@ -0,0 +1,2149 @@
1
+ // src/version.ts
2
+ import { createRequire } from "module";
3
+ function readPackageVersion(moduleUrl) {
4
+ try {
5
+ const require2 = createRequire(moduleUrl);
6
+ const pkg = require2("../package.json");
7
+ if (typeof pkg?.version === "string" && pkg.version) return pkg.version;
8
+ return "unknown";
9
+ } catch {
10
+ return "unknown";
11
+ }
12
+ }
13
+ var VERSION = readPackageVersion(import.meta.url);
14
+ var USER_AGENT = `swagger-digest/${VERSION}`;
15
+
16
+ // src/transformer/utils.ts
17
+ function isObject(value) {
18
+ return value !== null && typeof value === "object" && !Array.isArray(value);
19
+ }
20
+ function entries(value) {
21
+ return isObject(value) ? Object.entries(value) : [];
22
+ }
23
+ function cleanText(value) {
24
+ return typeof value === "string" ? value.trim().replace(/\s+/g, " ") : "";
25
+ }
26
+ function markdownCell(value) {
27
+ return cleanText(value).replace(/\|/g, "\\|").replace(/`/g, "\\`") || "\u2014";
28
+ }
29
+ function safeSourceUrl(value) {
30
+ try {
31
+ const url = new URL(value);
32
+ url.username = "";
33
+ url.password = "";
34
+ for (const key of [...url.searchParams.keys()]) {
35
+ if (/(token|secret|password|signature|credential|api[-_]?key|authorization|auth)/i.test(key)) {
36
+ url.searchParams.set(key, "[redacted]");
37
+ }
38
+ }
39
+ return url.toString();
40
+ } catch {
41
+ return value;
42
+ }
43
+ }
44
+ function safeComment(value) {
45
+ return cleanText(value).replace(/\*\//g, "* /").replace(/[\r\n]+/g, " ");
46
+ }
47
+ function propertyKey(value) {
48
+ return /^[$A-Z_a-z][$\w]*$/.test(value) ? value : JSON.stringify(value);
49
+ }
50
+ function toTypeScriptIdentifier(value) {
51
+ let identifier = value.replace(/[^\p{L}\p{N}_$]+/gu, "_").replace(/^([^\p{L}_$])/u, "_$1");
52
+ if (!identifier) identifier = "Unnamed";
53
+ const reserved = /* @__PURE__ */ new Set([
54
+ "abstract",
55
+ "any",
56
+ "as",
57
+ "asserts",
58
+ "async",
59
+ "await",
60
+ "boolean",
61
+ "break",
62
+ "case",
63
+ "catch",
64
+ "class",
65
+ "const",
66
+ "constructor",
67
+ "continue",
68
+ "debugger",
69
+ "declare",
70
+ "default",
71
+ "delete",
72
+ "do",
73
+ "else",
74
+ "enum",
75
+ "export",
76
+ "extends",
77
+ "false",
78
+ "finally",
79
+ "for",
80
+ "from",
81
+ "function",
82
+ "get",
83
+ "if",
84
+ "implements",
85
+ "import",
86
+ "in",
87
+ "infer",
88
+ "instanceof",
89
+ "interface",
90
+ "is",
91
+ "keyof",
92
+ "let",
93
+ "module",
94
+ "namespace",
95
+ "never",
96
+ "new",
97
+ "null",
98
+ "number",
99
+ "object",
100
+ "of",
101
+ "package",
102
+ "private",
103
+ "protected",
104
+ "public",
105
+ "readonly",
106
+ "require",
107
+ "return",
108
+ "set",
109
+ "static",
110
+ "string",
111
+ "super",
112
+ "switch",
113
+ "symbol",
114
+ "this",
115
+ "throw",
116
+ "true",
117
+ "try",
118
+ "type",
119
+ "typeof",
120
+ "undefined",
121
+ "unique",
122
+ "unknown",
123
+ "var",
124
+ "void",
125
+ "while",
126
+ "with",
127
+ "yield"
128
+ ]);
129
+ if (reserved.has(identifier)) identifier = `${identifier}Model`;
130
+ return identifier;
131
+ }
132
+
133
+ // src/fetcher.ts
134
+ import { load } from "cheerio";
135
+ import { parse as parseYaml2 } from "yaml";
136
+
137
+ // src/errors.ts
138
+ var FetchError = class extends Error {
139
+ kind;
140
+ constructor(kind, message) {
141
+ super(message);
142
+ this.name = "FetchError";
143
+ this.kind = kind;
144
+ }
145
+ };
146
+ var FetchSizeError = class extends FetchError {
147
+ constructor(message) {
148
+ super("size", message);
149
+ this.name = "FetchSizeError";
150
+ }
151
+ };
152
+ var BlockedNetworkError = class extends FetchError {
153
+ constructor(message) {
154
+ super("blocked", message);
155
+ this.name = "BlockedNetworkError";
156
+ }
157
+ };
158
+ var FetchNetworkError = class extends FetchError {
159
+ constructor(message) {
160
+ super("network", message);
161
+ this.name = "FetchNetworkError";
162
+ }
163
+ };
164
+ var FetchHttpError = class extends FetchError {
165
+ status;
166
+ constructor(message, status) {
167
+ super("http", message);
168
+ this.name = "FetchHttpError";
169
+ this.status = status;
170
+ }
171
+ };
172
+ function isFailFastFetchError(error) {
173
+ return error instanceof FetchError && (error.kind === "size" || error.kind === "blocked" || error.kind === "network");
174
+ }
175
+
176
+ // src/hardened-fetch.ts
177
+ import { lookup } from "dns/promises";
178
+ import { isIP } from "net";
179
+ var DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
180
+ var DEFAULT_MAX_REDIRECTS = 5;
181
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
182
+ function isPrivateAddress(address) {
183
+ const family = isIP(address);
184
+ if (family === 4) return isPrivateIPv4(address);
185
+ if (family === 6) {
186
+ const normalized = address.toLowerCase();
187
+ const mapped = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
188
+ if (mapped?.[1]) return isPrivateIPv4(mapped[1]);
189
+ if (normalized === "::1" || normalized === "::") return true;
190
+ if (/^f[cd][0-9a-f]{2}(?::|$)/.test(normalized)) return true;
191
+ if (/^fe[89ab][0-9a-f](?::|$)/.test(normalized)) return true;
192
+ return false;
193
+ }
194
+ return true;
195
+ }
196
+ function isPrivateIPv4(address) {
197
+ const parts = address.split(".").map(Number);
198
+ if (parts.length !== 4 || parts.some((part) => Number.isNaN(part))) return true;
199
+ const [a, b] = parts;
200
+ if (a === 0) return true;
201
+ if (a === 10) return true;
202
+ if (a === 127) return true;
203
+ if (a === 169 && b === 254) return true;
204
+ if (a === 172 && b >= 16 && b <= 31) return true;
205
+ if (a === 192 && b === 168) return true;
206
+ return false;
207
+ }
208
+ async function assertPublicHost(hostname) {
209
+ if (isIP(hostname) !== 0) {
210
+ if (isPrivateAddress(hostname)) {
211
+ throw new BlockedNetworkError(
212
+ `Blocked request to private network address (host resolved to a loopback/private/metadata address). Enable this only for hosts you trust; use it against internal corporate APIs without this option.`
213
+ );
214
+ }
215
+ return;
216
+ }
217
+ let addresses;
218
+ try {
219
+ addresses = await lookup(hostname, { all: true, verbatim: true });
220
+ } catch (error) {
221
+ throw new FetchNetworkError(
222
+ `DNS resolution failed for ${hostname}: ${error instanceof Error ? error.message : String(error)}`
223
+ );
224
+ }
225
+ if (!addresses.length) {
226
+ throw new FetchNetworkError(`DNS resolution returned no addresses for ${hostname}`);
227
+ }
228
+ for (const { address } of addresses) {
229
+ if (isPrivateAddress(address)) {
230
+ throw new BlockedNetworkError(
231
+ `Blocked request to ${hostname}: it resolves to a private/loopback/metadata address while blockPrivateNetworks is enabled.`
232
+ );
233
+ }
234
+ }
235
+ }
236
+ function normalizeHeaders(headers) {
237
+ return Object.fromEntries(Object.entries(headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
238
+ }
239
+ async function fetchWithLimits(url, options) {
240
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
241
+ const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
242
+ const defaultHeaders = normalizeHeaders({
243
+ accept: "application/json, application/yaml, text/yaml, text/plain, text/html;q=0.8, */*;q=0.5",
244
+ "user-agent": USER_AGENT
245
+ });
246
+ const userHeaders = normalizeHeaders(options.headers ?? {});
247
+ return await follow(url, defaultHeaders, userHeaders, options, maxBytes, maxRedirects);
248
+ }
249
+ async function follow(url, defaultHeaders, userHeaders, options, maxBytes, redirectsLeft) {
250
+ let target;
251
+ try {
252
+ target = new URL(url);
253
+ } catch {
254
+ throw new FetchNetworkError(`Invalid URL: ${url}`);
255
+ }
256
+ if (target.protocol !== "http:" && target.protocol !== "https:") {
257
+ throw new FetchNetworkError(`Only HTTP and HTTPS URLs are supported: ${url}`);
258
+ }
259
+ if (options.blockPrivateNetworks) {
260
+ await assertPublicHost(target.hostname);
261
+ }
262
+ const sameOrigin = !options.allowedHeaderOrigin || !options.headers || target.origin === options.allowedHeaderOrigin;
263
+ if (!sameOrigin && options.allowedHeaderOrigin && options.headers && Object.keys(options.headers).length > 0) {
264
+ options.onCrossOriginHeadersDropped?.(target.origin);
265
+ }
266
+ const requestHeaders = sameOrigin ? { ...defaultHeaders, ...userHeaders } : defaultHeaders;
267
+ const controller = new AbortController();
268
+ const timeout = AbortSignal.timeout(options.timeoutMs);
269
+ timeout.addEventListener("abort", () => controller.abort(), { once: true });
270
+ let response;
271
+ try {
272
+ response = await fetch(target, {
273
+ headers: requestHeaders,
274
+ redirect: "manual",
275
+ signal: controller.signal
276
+ });
277
+ } catch (error) {
278
+ if (error instanceof FetchError) throw error;
279
+ const reason = error instanceof Error ? error.message : String(error);
280
+ throw new FetchNetworkError(`Request to ${target} failed: ${describeNetworkError(reason)}`);
281
+ }
282
+ if (REDIRECT_STATUSES.has(response.status)) {
283
+ const location = response.headers.get("location");
284
+ if (!location) {
285
+ throw new FetchNetworkError(`Redirect response without a Location header from ${target}`);
286
+ }
287
+ if (redirectsLeft <= 0) {
288
+ throw new FetchNetworkError(`Too many redirects (limit ${options.maxRedirects ?? 5}) while fetching ${url}`);
289
+ }
290
+ try {
291
+ await response.arrayBuffer();
292
+ } catch {
293
+ }
294
+ const next = new URL(response.headers.get("location") ?? "", target);
295
+ return follow(next.toString(), defaultHeaders, userHeaders, options, maxBytes, redirectsLeft - 1);
296
+ }
297
+ if (!response.ok) {
298
+ await discardBodySafely(response);
299
+ throw new FetchHttpError(`HTTP ${response.status} ${response.statusText} for ${url}`, response.status);
300
+ }
301
+ const contentLength = response.headers.get("content-length");
302
+ if (contentLength !== null) {
303
+ const declared = Number(contentLength);
304
+ if (Number.isFinite(declared) && declared > maxBytes) {
305
+ await discardBodySafely(response);
306
+ throw new FetchSizeError(
307
+ `Response from ${target} is ${declared} bytes, exceeding the ${maxBytes} byte limit (content-length). Increase --max-bytes if the spec is genuinely large.`
308
+ );
309
+ }
310
+ }
311
+ const text = await readBodyWithCap(response, target.toString(), maxBytes);
312
+ return {
313
+ text,
314
+ contentType: response.headers.get("content-type") ?? "",
315
+ status: response.status,
316
+ url: target.toString()
317
+ };
318
+ }
319
+ function describeNetworkError(reason) {
320
+ if (/timeouterror|timed out|aborted/i.test(reason)) return "request timed out";
321
+ if (/enotfound|getaddrinfo/i.test(reason)) return "host not found (DNS)";
322
+ if (/econnrefused/i.test(reason)) return "connection refused";
323
+ return reason;
324
+ }
325
+ async function readBodyWithCap(response, url, maxBytes) {
326
+ const body = response.body;
327
+ if (!body) return "";
328
+ const chunks = [];
329
+ let total = 0;
330
+ try {
331
+ for await (const chunk of body) {
332
+ chunks.push(chunk);
333
+ total += chunk.byteLength;
334
+ if (total > maxBytes) {
335
+ try {
336
+ await body.cancel();
337
+ } catch {
338
+ }
339
+ throw new FetchSizeError(
340
+ `Response body from ${url} exceeded the ${maxBytes} byte limit while streaming (content-length was ${response.headers.get("content-length") ?? "not declared"}). Use --max-bytes to raise the cap if the spec is genuinely large.`
341
+ );
342
+ }
343
+ }
344
+ } catch (error) {
345
+ if (error instanceof FetchSizeError) throw error;
346
+ throw new FetchNetworkError(`Failed to read response body from ${url}: ${describeNetworkError(String(error))}`);
347
+ }
348
+ const decoder = new TextDecoder("utf-8");
349
+ const parts = [];
350
+ for (const chunk of chunks) parts.push(decoder.decode(chunk, { stream: true }));
351
+ parts.push(decoder.decode());
352
+ return parts.join("");
353
+ }
354
+ async function discardBodySafely(response) {
355
+ try {
356
+ await response.arrayBuffer();
357
+ } catch {
358
+ }
359
+ }
360
+
361
+ // src/bundler.ts
362
+ import { parse as parseYaml } from "yaml";
363
+ var MAX_EXTERNAL_DOCUMENTS = 5;
364
+ async function bundleExternalRefs(spec, sourceUrl, fetchDocument) {
365
+ if (!sourceUrl) return spec;
366
+ const externalRefs = collectExternalRefs(spec);
367
+ if (externalRefs.size === 0) return spec;
368
+ const schemaBucket = openSchemaBucket(spec);
369
+ const usedNames = new Set(Object.keys(schemaBucket));
370
+ const mapping = /* @__PURE__ */ new Map();
371
+ let fetched = 0;
372
+ for (const ref of externalRefs) {
373
+ if (fetched >= MAX_EXTERNAL_DOCUMENTS) break;
374
+ const { docUrl, pointer } = splitExternalRef(ref, sourceUrl);
375
+ if (!docUrl) continue;
376
+ try {
377
+ const text = await fetchDocument(docUrl);
378
+ const parsed = parseDocumentText(text);
379
+ if (parsed === void 0) continue;
380
+ fetched += 1;
381
+ const target = resolvePointer(parsed, pointer);
382
+ if (target === void 0) continue;
383
+ const localName = uniqueNameFor(localNameFor(docUrl, pointer), usedNames);
384
+ schemaBucket[localName] = target;
385
+ mapping.set(ref, localName);
386
+ } catch {
387
+ }
388
+ }
389
+ if (mapping.size === 0) return spec;
390
+ const rewritten = rewriteRefs(spec, mapping);
391
+ return isObject(rewritten) ? rewritten : spec;
392
+ }
393
+ function collectExternalRefs(node) {
394
+ const found = /* @__PURE__ */ new Set();
395
+ const walk = (value) => {
396
+ if (Array.isArray(value)) {
397
+ for (const item of value) walk(item);
398
+ return;
399
+ }
400
+ if (!isObject(value)) return;
401
+ for (const [key, refValue] of Object.entries(value)) {
402
+ if (key === "$ref" && typeof refValue === "string" && !refValue.startsWith("#")) {
403
+ found.add(refValue);
404
+ continue;
405
+ }
406
+ if (key === "example" || key === "examples" || key === "externalDocs") continue;
407
+ walk(refValue);
408
+ }
409
+ };
410
+ walk(node);
411
+ return found;
412
+ }
413
+ function rewriteRefs(node, mapping) {
414
+ if (Array.isArray(node)) return node.map((item) => rewriteRefs(item, mapping));
415
+ if (!isObject(node)) return node;
416
+ const result = {};
417
+ for (const [key, value] of Object.entries(node)) {
418
+ if (key === "$ref" && typeof value === "string") {
419
+ const localName = mapping.get(value);
420
+ if (localName === void 0) {
421
+ result[key] = value;
422
+ continue;
423
+ }
424
+ result[key] = `#/${bucketPath(bucketStyle(value))}/${localName}`;
425
+ continue;
426
+ }
427
+ result[key] = rewriteRefs(value, mapping);
428
+ }
429
+ return result;
430
+ }
431
+ function bucketStyle(ref) {
432
+ return ref.includes("/components/") ? "openapi" : "swagger";
433
+ }
434
+ function bucketPath(style) {
435
+ return style === "openapi" ? "components/schemas" : "definitions";
436
+ }
437
+ function openSchemaBucket(spec) {
438
+ if (isObject(spec.components)) {
439
+ if (!isObject(spec.components.schemas)) spec.components.schemas = {};
440
+ return spec.components.schemas;
441
+ }
442
+ if (!isObject(spec.definitions)) spec.definitions = {};
443
+ return spec.definitions;
444
+ }
445
+ function splitExternalRef(ref, baseUrl) {
446
+ const hashIndex = ref.indexOf("#");
447
+ if (hashIndex < 0) return { docUrl: void 0, pointer: "" };
448
+ const docPart = ref.slice(0, hashIndex);
449
+ const pointer = ref.slice(hashIndex + 1);
450
+ try {
451
+ return { docUrl: new URL(docPart, baseUrl).toString(), pointer };
452
+ } catch {
453
+ return { docUrl: void 0, pointer };
454
+ }
455
+ }
456
+ function parseDocumentText(text) {
457
+ try {
458
+ return JSON.parse(text);
459
+ } catch {
460
+ try {
461
+ return parseYaml(text);
462
+ } catch {
463
+ return void 0;
464
+ }
465
+ }
466
+ }
467
+ function resolvePointer(root, pointer) {
468
+ if (!pointer.startsWith("/")) return void 0;
469
+ let current = root;
470
+ for (const rawPart of pointer.slice(1).split("/")) {
471
+ const part = rawPart.replace(/~1/g, "/").replace(/~0/g, "~");
472
+ if (!isObject(current)) return void 0;
473
+ current = current[part];
474
+ }
475
+ return current;
476
+ }
477
+ function localNameFor(docUrl, pointer) {
478
+ try {
479
+ const url = new URL(docUrl);
480
+ const file = url.pathname.split("/").pop() ?? "external";
481
+ const stem = file.replace(/\.(json|ya?ml)$/i, "").replace(/[^A-Za-z0-9_$]+/g, "_") || "external";
482
+ const segments = pointer.split("/").filter(Boolean);
483
+ const pointerName = segments.length > 0 ? segments[segments.length - 1] : void 0;
484
+ const cleanedPointer = pointerName?.replace(/[^A-Za-z0-9_$]+/g, "_");
485
+ return [stem, cleanedPointer].filter(Boolean).join("_") || "external";
486
+ } catch {
487
+ return "external";
488
+ }
489
+ }
490
+ function uniqueNameFor(base, used) {
491
+ let candidate = base;
492
+ let suffix = 2;
493
+ while (used.has(candidate)) {
494
+ candidate = `${candidate}${suffix}`;
495
+ suffix += 1;
496
+ }
497
+ used.add(candidate);
498
+ return candidate;
499
+ }
500
+
501
+ // src/fetcher.ts
502
+ var SPEC_FILE_EXTENSION = /\.(?:json|ya?ml)$/i;
503
+ var FALLBACK_PATHS = ["/v3/api-docs", "/swagger/v1/swagger.json", "/swagger.json", "/openapi.json", "/api-docs"];
504
+ function isRecord(value) {
505
+ return value !== null && typeof value === "object" && !Array.isArray(value);
506
+ }
507
+ function isOpenApiSpec(value) {
508
+ if (!isRecord(value)) return false;
509
+ const hasVersion = typeof value.openapi === "string" || typeof value.swagger === "string";
510
+ const hasInfo = isRecord(value.info) && typeof value.info.title === "string";
511
+ const hasPaths = value.paths === void 0 || isRecord(value.paths);
512
+ return hasVersion && hasInfo && hasPaths;
513
+ }
514
+ function parseSpecText(text, source = "response") {
515
+ const parsers = [() => JSON.parse(text), () => parseYaml2(text)];
516
+ const errors = [];
517
+ for (const parser of parsers) {
518
+ try {
519
+ const parsed = parser();
520
+ if (isOpenApiSpec(parsed)) return parsed;
521
+ errors.push("document is not a recognizable OpenAPI/Swagger specification");
522
+ } catch (error) {
523
+ errors.push(error instanceof Error ? error.message : String(error));
524
+ }
525
+ }
526
+ throw new Error(`Could not parse OpenAPI spec from ${source}: ${[...new Set(errors)].join("; ")}`);
527
+ }
528
+ function isDirectSpecUrl(url) {
529
+ return SPEC_FILE_EXTENSION.test(url.pathname);
530
+ }
531
+ async function fetchText(url, ctx) {
532
+ const response = await fetchWithLimits(url, {
533
+ timeoutMs: ctx.timeoutMs,
534
+ maxBytes: ctx.maxBytes,
535
+ maxRedirects: ctx.maxRedirects,
536
+ blockPrivateNetworks: ctx.blockPrivateNetworks,
537
+ headers: ctx.headers,
538
+ allowedHeaderOrigin: ctx.allowedHeaderOrigin,
539
+ onCrossOriginHeadersDropped: ctx.onCrossOriginHeadersDropped
540
+ });
541
+ return {
542
+ text: response.text,
543
+ contentType: response.contentType
544
+ };
545
+ }
546
+ function extractScripts(html) {
547
+ const $ = load(html);
548
+ const inline = [];
549
+ const external = [];
550
+ for (const element of $("script").toArray()) {
551
+ const body = $(element).html();
552
+ if (body?.trim()) inline.push(body);
553
+ const src = $(element).attr("src");
554
+ if (src?.trim()) external.push(src.trim());
555
+ }
556
+ return { inline, external };
557
+ }
558
+ function extractSpecUrlFromScripts(scripts, pageUrl) {
559
+ return extractSpecUrlsFromScripts(scripts, pageUrl)[0];
560
+ }
561
+ function extractSpecUrlsFromScripts(scripts, pageUrl) {
562
+ const found = [];
563
+ const pushCandidate = (candidate) => {
564
+ const trimmed = candidate.trim();
565
+ if (!trimmed || trimmed.startsWith("data:")) return;
566
+ try {
567
+ const resolved = new URL(trimmed, pageUrl).toString();
568
+ if (!found.includes(resolved)) found.push(resolved);
569
+ } catch {
570
+ }
571
+ };
572
+ for (const script of scripts) {
573
+ const urlsArray = findObjectLiteralArrayAfterKey(script, "urls");
574
+ if (urlsArray) {
575
+ const entryPattern = /\burl\s*:\s*(["'])(.*?)\1/gs;
576
+ for (const match of urlsArray.matchAll(entryPattern)) {
577
+ pushCandidate(match[2] ?? "");
578
+ }
579
+ }
580
+ if (!urlsArray) {
581
+ const urlPattern = /\burl\s*:\s*(["'])(.*?)\1/s;
582
+ const match = urlPattern.exec(script);
583
+ if (match?.[2]) pushCandidate(match[2]);
584
+ }
585
+ }
586
+ return found;
587
+ }
588
+ function findObjectLiteralArrayAfterKey(source, key) {
589
+ const keyPattern = new RegExp(`\\b${key}\\s*:`);
590
+ const match = keyPattern.exec(source);
591
+ if (!match) return void 0;
592
+ let index = match.index + match[0].length;
593
+ while (/\s/.test(source[index] ?? "")) index += 1;
594
+ if (source[index] !== "[") return void 0;
595
+ const start = index;
596
+ let depth = 0;
597
+ let quote;
598
+ let escaped = false;
599
+ let lineComment = false;
600
+ let blockComment = false;
601
+ for (; index < source.length; index += 1) {
602
+ const char = source[index] ?? "";
603
+ const next = source[index + 1] ?? "";
604
+ if (lineComment) {
605
+ if (char === "\n") lineComment = false;
606
+ continue;
607
+ }
608
+ if (blockComment) {
609
+ if (char === "*" && next === "/") {
610
+ blockComment = false;
611
+ index += 1;
612
+ }
613
+ continue;
614
+ }
615
+ if (quote) {
616
+ if (escaped) {
617
+ escaped = false;
618
+ } else if (char === "\\") {
619
+ escaped = true;
620
+ } else if (char === quote) {
621
+ quote = void 0;
622
+ }
623
+ continue;
624
+ }
625
+ if (char === "/" && next === "/") {
626
+ lineComment = true;
627
+ index += 1;
628
+ continue;
629
+ }
630
+ if (char === "/" && next === "*") {
631
+ blockComment = true;
632
+ index += 1;
633
+ continue;
634
+ }
635
+ if (char === '"' || char === "'" || char === "`") {
636
+ quote = char;
637
+ continue;
638
+ }
639
+ if (char === "[") depth += 1;
640
+ if (char === "]") {
641
+ depth -= 1;
642
+ if (depth === 0) return source.slice(start, index + 1);
643
+ }
644
+ }
645
+ return void 0;
646
+ }
647
+ function extractSpecUrls(scripts, pageUrl) {
648
+ const fromScripts = extractSpecUrlsFromScripts(scripts, pageUrl);
649
+ const queryUrl = pageUrl.searchParams.get("url");
650
+ if (queryUrl) {
651
+ try {
652
+ const resolved = new URL(queryUrl, pageUrl).toString();
653
+ if (!fromScripts.includes(resolved)) fromScripts.push(resolved);
654
+ } catch {
655
+ }
656
+ }
657
+ return fromScripts;
658
+ }
659
+ async function probeSpecUrls(candidates, ctx, recordFailure, onMultiple) {
660
+ if (candidates.length === 0) return void 0;
661
+ if (candidates.length > 1) {
662
+ onMultiple?.(candidates[0] ?? "", candidates);
663
+ }
664
+ const batchSize = 4;
665
+ for (let start = 0; start < candidates.length; start += batchSize) {
666
+ const batch = candidates.slice(start, start + batchSize);
667
+ const attempts = await Promise.all(
668
+ batch.map(async (candidate) => {
669
+ try {
670
+ return { candidate, spec: await tryFetchSpec(candidate, ctx) };
671
+ } catch (error) {
672
+ return { candidate, error };
673
+ }
674
+ })
675
+ );
676
+ const winner = attempts.find(
677
+ (attempt) => attempt.spec !== void 0
678
+ );
679
+ if (winner) {
680
+ return { spec: winner.spec, sourceUrl: winner.candidate, retrievalMethod: "html-config" };
681
+ }
682
+ for (const attempt of attempts) {
683
+ recordFailure("Configured spec", attempt.error, attempt.candidate);
684
+ }
685
+ }
686
+ return void 0;
687
+ }
688
+ async function fetchExternalConfigScripts(srcs, pageUrl, ctx, errors) {
689
+ const candidates = srcs.map((src) => {
690
+ try {
691
+ return new URL(src, pageUrl);
692
+ } catch {
693
+ return void 0;
694
+ }
695
+ }).filter((url) => Boolean(url && url.origin === pageUrl.origin && /\.m?js(?:$|[?#])/i.test(url.href)));
696
+ const priority = (url) => /swagger[-_]?initializer|swagger[-_]?config|swagger[-_]?ui[-_]?init|openapi[-_]?config|(?:^|[\/_-])config(?:[._/-]|$)/i.test(
697
+ url.pathname
698
+ ) ? 0 : 1;
699
+ candidates.sort((a, b) => priority(a) - priority(b));
700
+ const scripts = [];
701
+ for (const candidate of candidates.slice(0, 10)) {
702
+ try {
703
+ const response = await fetchText(candidate.toString(), ctx);
704
+ scripts.push(response.text);
705
+ if (extractSpecUrlFromScripts(scripts, pageUrl) || scripts.some((script) => findObjectLiteralAfterKey(script, "spec"))) {
706
+ break;
707
+ }
708
+ } catch (error) {
709
+ errors.push(`Swagger UI script (${candidate}): ${error instanceof Error ? error.message : String(error)}`);
710
+ }
711
+ }
712
+ return scripts;
713
+ }
714
+ function getFallbackUrls(pageUrl) {
715
+ const relativePaths = [
716
+ "./v3/api-docs",
717
+ "./swagger/v1/swagger.json",
718
+ "./swagger.json",
719
+ "./openapi.json",
720
+ "./api-docs",
721
+ "./doc.json",
722
+ "../v3/api-docs",
723
+ "../swagger/v1/swagger.json",
724
+ "../swagger.json",
725
+ "../openapi.json",
726
+ "../api-docs"
727
+ ];
728
+ const absolutePaths = [...relativePaths, ...FALLBACK_PATHS];
729
+ return [...new Set(absolutePaths.map((path2) => new URL(path2, pageUrl).toString()))];
730
+ }
731
+ function formatAttemptErrors(errors) {
732
+ if (errors.length <= 10) return errors.join("\n - ");
733
+ const visible = [...errors.slice(0, 6), `\u2026 ${errors.length - 9} additional attempts omitted \u2026`, ...errors.slice(-3)];
734
+ return visible.join("\n - ");
735
+ }
736
+ function findObjectLiteralAfterKey(source, key) {
737
+ const keyPattern = new RegExp(`\\b${key}\\s*:`);
738
+ const match = keyPattern.exec(source);
739
+ if (!match) return void 0;
740
+ let index = match.index + match[0].length;
741
+ while (/\s/.test(source[index] ?? "")) index += 1;
742
+ if (source[index] !== "{") return void 0;
743
+ const start = index;
744
+ let depth = 0;
745
+ let quote;
746
+ let escaped = false;
747
+ let lineComment = false;
748
+ let blockComment = false;
749
+ for (; index < source.length; index += 1) {
750
+ const char = source[index] ?? "";
751
+ const next = source[index + 1] ?? "";
752
+ if (lineComment) {
753
+ if (char === "\n") lineComment = false;
754
+ continue;
755
+ }
756
+ if (blockComment) {
757
+ if (char === "*" && next === "/") {
758
+ blockComment = false;
759
+ index += 1;
760
+ }
761
+ continue;
762
+ }
763
+ if (quote) {
764
+ if (escaped) {
765
+ escaped = false;
766
+ } else if (char === "\\") {
767
+ escaped = true;
768
+ } else if (char === quote) {
769
+ quote = void 0;
770
+ }
771
+ continue;
772
+ }
773
+ if (char === "/" && next === "/") {
774
+ lineComment = true;
775
+ index += 1;
776
+ continue;
777
+ }
778
+ if (char === "/" && next === "*") {
779
+ blockComment = true;
780
+ index += 1;
781
+ continue;
782
+ }
783
+ if (char === '"' || char === "'" || char === "`") {
784
+ quote = char;
785
+ continue;
786
+ }
787
+ if (char === "{") depth += 1;
788
+ if (char === "}") {
789
+ depth -= 1;
790
+ if (depth === 0) return source.slice(start, index + 1);
791
+ }
792
+ }
793
+ return void 0;
794
+ }
795
+ function parseInlineObjectLiteral(literal) {
796
+ const candidates = [
797
+ () => JSON.parse(literal),
798
+ () => parseYaml2(literal),
799
+ // Swagger config often uses JS object syntax with trailing commas.
800
+ () => JSON.parse(literal.replace(/,\s*([}\]])/g, "$1"))
801
+ ];
802
+ for (const parse of candidates) {
803
+ try {
804
+ const result = parse();
805
+ if (isOpenApiSpec(result)) return result;
806
+ } catch {
807
+ }
808
+ }
809
+ return void 0;
810
+ }
811
+ function extractInlineSpec(scripts) {
812
+ for (const script of scripts) {
813
+ const literal = findObjectLiteralAfterKey(script, "spec");
814
+ if (!literal) continue;
815
+ const spec = parseInlineObjectLiteral(literal);
816
+ if (spec) return spec;
817
+ }
818
+ return void 0;
819
+ }
820
+ async function tryFetchSpec(url, ctx) {
821
+ const response = await fetchText(url, ctx);
822
+ const parsed = parseSpecText(response.text, url);
823
+ try {
824
+ return await bundleExternalRefs(parsed, url, async (docUrl) => (await fetchText(docUrl, ctx)).text);
825
+ } catch {
826
+ return parsed;
827
+ }
828
+ }
829
+ function headersForBrowserRequest(requestUrl, headers, allowedOrigin) {
830
+ if (!headers || Object.keys(headers).length === 0) return void 0;
831
+ let requestOrigin;
832
+ try {
833
+ requestOrigin = new URL(requestUrl).origin;
834
+ } catch {
835
+ return void 0;
836
+ }
837
+ return requestOrigin === allowedOrigin ? headers : void 0;
838
+ }
839
+ async function tryBrowserFallback(pageUrl, headers) {
840
+ let playwright;
841
+ try {
842
+ const optionalModuleName = "playwright";
843
+ playwright = await import(optionalModuleName);
844
+ } catch {
845
+ throw new Error(
846
+ "Headless fallback needs optional Playwright. Install it with `npm install playwright` and `npx playwright install chromium`, then rerun."
847
+ );
848
+ }
849
+ const browser = await playwright.chromium.launch({ headless: true });
850
+ try {
851
+ const page = await browser.newPage();
852
+ const allowedOrigin = new URL(pageUrl).origin;
853
+ if (headers && Object.keys(headers).length > 0) {
854
+ const browserHeaders = Object.fromEntries(
855
+ Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value])
856
+ );
857
+ await page.route("**/*", async (route) => {
858
+ const extra = headersForBrowserRequest(route.request().url(), browserHeaders, allowedOrigin);
859
+ if (!extra) {
860
+ await route.continue();
861
+ return;
862
+ }
863
+ await route.continue({ headers: { ...route.request().headers(), ...extra } });
864
+ });
865
+ }
866
+ await page.goto(pageUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
867
+ await page.waitForFunction(
868
+ () => {
869
+ const w = window;
870
+ try {
871
+ const candidate = w.ui?.spec?.();
872
+ const document = candidate && typeof candidate.toJSON === "function" ? candidate.toJSON() : candidate;
873
+ const doc = document;
874
+ return Boolean(doc?.openapi || doc?.swagger || w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
875
+ } catch {
876
+ return Boolean(w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
877
+ }
878
+ },
879
+ void 0,
880
+ { timeout: 8e3 }
881
+ ).catch(() => void 0);
882
+ const extracted = await page.evaluate(() => {
883
+ const w = window;
884
+ try {
885
+ const spec = w.ui?.spec?.();
886
+ if (spec) {
887
+ const json = typeof spec.toJSON === "function" ? spec.toJSON() : spec;
888
+ if (json && typeof json === "object") return json;
889
+ }
890
+ } catch {
891
+ }
892
+ if (w.swaggerDoc && typeof w.swaggerDoc === "object") return w.swaggerDoc;
893
+ try {
894
+ const state = w.ui?.getSystem?.().specSelectors?.specJson?.();
895
+ if (state) return typeof state.toJS === "function" ? state.toJS() : state;
896
+ } catch {
897
+ }
898
+ return null;
899
+ });
900
+ if (!isOpenApiSpec(extracted)) {
901
+ throw new Error("The page loaded in a browser, but no parsed OpenAPI spec was exposed by Swagger UI.");
902
+ }
903
+ return extracted;
904
+ } finally {
905
+ await browser.close();
906
+ }
907
+ }
908
+ async function fetchOpenApiSpec(input, options = {}) {
909
+ let pageUrl;
910
+ try {
911
+ pageUrl = new URL(input);
912
+ } catch {
913
+ throw new Error(`Invalid URL: ${input}`);
914
+ }
915
+ if (!["http:", "https:"].includes(pageUrl.protocol)) {
916
+ throw new Error("Only HTTP and HTTPS URLs are supported.");
917
+ }
918
+ const timeoutMs = options.timeoutMs ?? 15e3;
919
+ const ctx = {
920
+ timeoutMs,
921
+ maxBytes: options.maxBytes,
922
+ maxRedirects: options.maxRedirects,
923
+ blockPrivateNetworks: options.blockPrivateNetworks,
924
+ allowedHeaderOrigin: pageUrl.origin,
925
+ headers: options.headers,
926
+ onCrossOriginHeadersDropped: (origin) => options.warn?.(
927
+ `Custom headers were withheld: ${origin} is a different origin from the URL you supplied. Only tool defaults were sent.`
928
+ )
929
+ };
930
+ const errors = [];
931
+ const recordFailure = (label, error, subject) => {
932
+ if (isFailFastFetchError(error)) throw error;
933
+ const prefix = subject ? `${label} (${subject}):` : `${label}:`;
934
+ errors.push(`${prefix} ${error instanceof Error ? error.message : String(error)}`);
935
+ };
936
+ if (isDirectSpecUrl(pageUrl)) {
937
+ try {
938
+ const spec = await tryFetchSpec(pageUrl.toString(), ctx);
939
+ return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "direct-spec" };
940
+ } catch (error) {
941
+ recordFailure("Direct spec", error);
942
+ }
943
+ }
944
+ if (options.specUrl) {
945
+ try {
946
+ const spec = await tryFetchSpec(options.specUrl, ctx);
947
+ return { spec, sourceUrl: options.specUrl, retrievalMethod: "direct-spec" };
948
+ } catch (error) {
949
+ recordFailure("Specified spec (--spec)", error, options.specUrl);
950
+ }
951
+ }
952
+ let html;
953
+ try {
954
+ const response = await fetchText(pageUrl.toString(), ctx);
955
+ try {
956
+ const spec = parseSpecText(response.text, pageUrl.toString());
957
+ return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "direct-spec" };
958
+ } catch {
959
+ html = response.text;
960
+ }
961
+ } catch (error) {
962
+ recordFailure("Swagger UI page", error);
963
+ }
964
+ if (html !== void 0) {
965
+ const pageScripts = extractScripts(html);
966
+ const scripts = pageScripts.inline;
967
+ const inlineSpec = extractInlineSpec(scripts);
968
+ if (inlineSpec) {
969
+ return { spec: inlineSpec, sourceUrl: pageUrl.toString(), retrievalMethod: "inline-spec" };
970
+ }
971
+ const noteMultiple = (picked, all) => {
972
+ if (all.length > 1) {
973
+ options.warn?.(
974
+ `Multiple spec definitions found (${String(all.length)}); using ${picked}. Others: ${all.filter((url) => url !== picked).join(", ")}.`
975
+ );
976
+ }
977
+ };
978
+ const fromInline = await probeSpecUrls(extractSpecUrls(scripts, pageUrl), ctx, recordFailure, noteMultiple);
979
+ if (fromInline) return fromInline;
980
+ const externalScripts = await fetchExternalConfigScripts(pageScripts.external, pageUrl, ctx, errors);
981
+ const inlineUrls = extractSpecUrls(scripts, pageUrl);
982
+ const externalUrls = extractSpecUrls(externalScripts, pageUrl).filter((url) => !inlineUrls.includes(url));
983
+ const fromExternal = await probeSpecUrls([...inlineUrls, ...externalUrls], ctx, recordFailure, noteMultiple);
984
+ if (fromExternal) return fromExternal;
985
+ for (const script of scripts) {
986
+ const literal = findObjectLiteralAfterKey(script, "spec");
987
+ if (!literal) continue;
988
+ const spec = parseInlineObjectLiteral(literal);
989
+ if (spec) return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "inline-spec" };
990
+ }
991
+ }
992
+ const probeCtx = { ...ctx, timeoutMs: options.probeTimeoutMs ?? 5e3 };
993
+ const candidates = getFallbackUrls(pageUrl);
994
+ const probeBatchSize = 4;
995
+ for (let start = 0; start < candidates.length; start += probeBatchSize) {
996
+ const batch = candidates.slice(start, start + probeBatchSize);
997
+ const attempts = await Promise.all(
998
+ batch.map(async (candidate) => {
999
+ try {
1000
+ return { candidate, spec: await tryFetchSpec(candidate, probeCtx) };
1001
+ } catch (error) {
1002
+ return { candidate, error };
1003
+ }
1004
+ })
1005
+ );
1006
+ const winner = attempts.find(
1007
+ (attempt) => attempt.spec !== void 0
1008
+ );
1009
+ if (winner) {
1010
+ return { spec: winner.spec, sourceUrl: winner.candidate, retrievalMethod: "fallback-path" };
1011
+ }
1012
+ for (const attempt of attempts) {
1013
+ recordFailure(attempt.candidate, attempt.error);
1014
+ }
1015
+ }
1016
+ if (options.useBrowserFallback !== false) {
1017
+ try {
1018
+ const spec = await tryBrowserFallback(pageUrl.toString(), ctx.headers);
1019
+ return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "headless-browser" };
1020
+ } catch (error) {
1021
+ errors.push(`Headless browser: ${error instanceof Error ? error.message : String(error)}`);
1022
+ }
1023
+ }
1024
+ const details = formatAttemptErrors(errors);
1025
+ throw new Error(
1026
+ `Unable to extract an OpenAPI definition from ${input}.
1027
+ - ${details}
1028
+
1029
+ Check that the URL is reachable from this terminal and points to a Swagger UI page or OpenAPI spec. If it opens only in a browser, verify that your VPN applies to terminal applications (browser VPN extensions do not automatically route Node.js requests). For JavaScript-only Swagger UI deployments, install Playwright and its Chromium browser; Playwright will also need the same network/VPN access.`
1030
+ );
1031
+ }
1032
+
1033
+ // src/tokens.ts
1034
+ var MODEL_CONFIGS = {
1035
+ claude: { name: "Claude (Sonnet / Opus)", contextWindow: 2e5, ratio: 1.08 },
1036
+ openai: { name: "OpenAI (GPT-4o / o-series)", contextWindow: 128e3, ratio: 0.95 },
1037
+ gemini: { name: "Google Gemini (Pro / Flash)", contextWindow: 1e6, ratio: 0.9 },
1038
+ deepseek: { name: "DeepSeek (V3 / R1)", contextWindow: 128e3, ratio: 0.97 },
1039
+ llama: { name: "Meta Llama (3.3 / 4)", contextWindow: 128e3, ratio: 0.98 }
1040
+ };
1041
+ function estimateTokens(text) {
1042
+ const characters = text.length;
1043
+ if (characters === 0) {
1044
+ const zeroModel = ({ name, contextWindow }) => ({
1045
+ name,
1046
+ tokens: 0,
1047
+ contextWindow,
1048
+ contextPercent: 0
1049
+ });
1050
+ return {
1051
+ characters: 0,
1052
+ models: {
1053
+ claude: zeroModel(MODEL_CONFIGS.claude),
1054
+ openai: zeroModel(MODEL_CONFIGS.openai),
1055
+ gemini: zeroModel(MODEL_CONFIGS.gemini),
1056
+ deepseek: zeroModel(MODEL_CONFIGS.deepseek),
1057
+ llama: zeroModel(MODEL_CONFIGS.llama)
1058
+ }
1059
+ };
1060
+ }
1061
+ const wordCount = (text.match(/\b[\w$]+\b/gu) || []).length;
1062
+ const syntaxSymbols = (text.match(/[{}[\]()<>=:;,."`|/-]/g) || []).length;
1063
+ const whitespaces = (text.match(/\s+/g) || []).length;
1064
+ const baseTokens = wordCount * 1.18 + syntaxSymbols * 0.42 + whitespaces * 0.05;
1065
+ const calculate = (name, ratio, contextWindow) => {
1066
+ const tokens = Math.max(1, Math.round(baseTokens * ratio));
1067
+ const contextPercent = Number((tokens / contextWindow * 100).toFixed(1));
1068
+ return { name, tokens, contextWindow, contextPercent };
1069
+ };
1070
+ return {
1071
+ characters,
1072
+ models: {
1073
+ claude: calculate(MODEL_CONFIGS.claude.name, MODEL_CONFIGS.claude.ratio, MODEL_CONFIGS.claude.contextWindow),
1074
+ openai: calculate(MODEL_CONFIGS.openai.name, MODEL_CONFIGS.openai.ratio, MODEL_CONFIGS.openai.contextWindow),
1075
+ gemini: calculate(MODEL_CONFIGS.gemini.name, MODEL_CONFIGS.gemini.ratio, MODEL_CONFIGS.gemini.contextWindow),
1076
+ deepseek: calculate(
1077
+ MODEL_CONFIGS.deepseek.name,
1078
+ MODEL_CONFIGS.deepseek.ratio,
1079
+ MODEL_CONFIGS.deepseek.contextWindow
1080
+ ),
1081
+ llama: calculate(MODEL_CONFIGS.llama.name, MODEL_CONFIGS.llama.ratio, MODEL_CONFIGS.llama.contextWindow)
1082
+ }
1083
+ };
1084
+ }
1085
+
1086
+ // src/transformer/security.ts
1087
+ function describeSecurityScheme(name, scheme) {
1088
+ const type = cleanText(scheme.type).toLowerCase();
1089
+ const label = cleanText(scheme.description);
1090
+ let details = name;
1091
+ if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "bearer")
1092
+ details += " (Bearer token via Authorization header)";
1093
+ else if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "basic")
1094
+ details += " (HTTP Basic via Authorization header)";
1095
+ else if (type === "apikey" || type === "apiKey")
1096
+ details += ` (API key in ${cleanText(scheme.in) || "header"}: ${cleanText(scheme.name) || "key"})`;
1097
+ else if (type === "oauth2") {
1098
+ const flows = entries(scheme.flows).map(([flowName]) => flowName).join(", ") || cleanText(scheme.flow);
1099
+ details += ` (OAuth2${flows ? `; ${flows}` : ""})`;
1100
+ } else if (type === "openidconnect" || type === "openIdConnect") details += " (OpenID Connect)";
1101
+ else if (type === "basic") details += " (HTTP Basic)";
1102
+ else if (type) details += ` (${type})`;
1103
+ if (label) details += ` \u2014 ${label}`;
1104
+ return details;
1105
+ }
1106
+ function getSecuritySchemes(spec) {
1107
+ const dictionary = spec.components?.securitySchemes ?? spec.securityDefinitions ?? {};
1108
+ return entries(dictionary).filter((entry) => isObject(entry[1]));
1109
+ }
1110
+ function securityRequirement(spec, operation) {
1111
+ const security = operation.security !== void 0 ? operation.security : spec.security;
1112
+ if (!Array.isArray(security) || security.length === 0) return "None explicitly required";
1113
+ const allSchemes = new Map(getSecuritySchemes(spec));
1114
+ return security.map((requirement) => {
1115
+ if (!isObject(requirement)) return "";
1116
+ const names = Object.entries(requirement).map(([name, scopes]) => {
1117
+ const label = allSchemes.has(name) ? name : `${name} (scheme not defined)`;
1118
+ return Array.isArray(scopes) && scopes.length ? `${label} [scopes: ${scopes.join(", ")}]` : label;
1119
+ });
1120
+ return names.length ? names.join(" + ") : "No auth";
1121
+ }).filter(Boolean).join(" OR ");
1122
+ }
1123
+ function authOverview(spec) {
1124
+ const schemes = getSecuritySchemes(spec);
1125
+ if (!schemes.length) return ["- **Authentication:** No security schemes declared."];
1126
+ const globalSecurity = securityRequirement(spec, {});
1127
+ return [
1128
+ `- **Authentication required globally:** ${globalSecurity}`,
1129
+ "- **Available schemes:**",
1130
+ ...schemes.map(([name, scheme]) => ` - ${describeSecurityScheme(name, scheme)}`)
1131
+ ];
1132
+ }
1133
+
1134
+ // src/transformer/render-style.ts
1135
+ var FULL_STYLE = { includeExamples: true, collapsed: false };
1136
+ function shortenDescription(text, limit) {
1137
+ if (limit === void 0 || text.length <= limit) return text;
1138
+ return `${text.slice(0, limit).trimEnd()}\u2026`;
1139
+ }
1140
+
1141
+ // src/transformer/schema-type.ts
1142
+ function schemaMetadata(schemaValue) {
1143
+ if (!isObject(schemaValue)) return [];
1144
+ const schema = schemaValue;
1145
+ const metadata = [];
1146
+ if (cleanText(schema.description)) metadata.push(cleanText(schema.description));
1147
+ if (cleanText(schema.format)) metadata.push(`format: ${cleanText(schema.format)}`);
1148
+ const constraints = [
1149
+ ["default", "default"],
1150
+ ["pattern", "pattern"],
1151
+ ["minLength", "minLength"],
1152
+ ["maxLength", "maxLength"],
1153
+ ["minimum", "minimum"],
1154
+ ["maximum", "maximum"],
1155
+ ["exclusiveMinimum", "exclusiveMinimum"],
1156
+ ["exclusiveMaximum", "exclusiveMaximum"],
1157
+ ["minItems", "minItems"],
1158
+ ["maxItems", "maxItems"],
1159
+ ["uniqueItems", "uniqueItems"],
1160
+ ["multipleOf", "multipleOf"]
1161
+ ];
1162
+ for (const [property, label] of constraints) {
1163
+ const value = schema[property];
1164
+ if (value === void 0) continue;
1165
+ if (property === "default" && (value === null || value === "")) continue;
1166
+ metadata.push(
1167
+ `${label}: ${property === "default" ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value)}`
1168
+ );
1169
+ }
1170
+ if (schema.readOnly === true) metadata.push("read-only");
1171
+ if (schema.writeOnly === true) metadata.push("write-only");
1172
+ if (schema.deprecated === true) metadata.push("deprecated");
1173
+ return [...new Set(metadata)];
1174
+ }
1175
+ function refName(ref) {
1176
+ const parts = ref.split("/");
1177
+ const last = parts[parts.length - 1];
1178
+ if (!last) return void 0;
1179
+ const decoded = last.replace(/~1/g, "/").replace(/~0/g, "~");
1180
+ return toTypeScriptIdentifier(decoded);
1181
+ }
1182
+ function isSchemaRef(ref) {
1183
+ return ref.includes("/components/schemas/") || ref.includes("/definitions/");
1184
+ }
1185
+ function nullableType(type, schema) {
1186
+ const typeList = Array.isArray(schema.type) ? schema.type : [];
1187
+ if (schema.nullable === true || typeList.includes("null")) {
1188
+ if (!type.split(/\s*\|\s*/).includes("null")) return `${parenthesizeUnion(type)} | null`;
1189
+ }
1190
+ return type;
1191
+ }
1192
+ function parenthesizeUnion(type) {
1193
+ return type.includes(" | ") ? `(${type})` : type;
1194
+ }
1195
+ function literalType(value) {
1196
+ if (typeof value === "string") return JSON.stringify(value);
1197
+ if (value === null) return "null";
1198
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1199
+ return "unknown";
1200
+ }
1201
+ function typeExpression(schemaValue, depth = 0) {
1202
+ if (depth > 12 || !isObject(schemaValue)) return "unknown";
1203
+ const schema = schemaValue;
1204
+ if (typeof schema.$ref === "string") {
1205
+ return refName(schema.$ref) ?? "unknown";
1206
+ }
1207
+ if (schema.const !== void 0) return nullableType(literalType(schema.const), schema);
1208
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
1209
+ return nullableType([...new Set(schema.enum.map(literalType))].join(" | "), schema);
1210
+ }
1211
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
1212
+ return nullableType(schema.oneOf.map((item) => typeExpression(item, depth + 1)).join(" | "), schema);
1213
+ }
1214
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
1215
+ return nullableType(schema.anyOf.map((item) => typeExpression(item, depth + 1)).join(" | "), schema);
1216
+ }
1217
+ if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
1218
+ const components = schema.allOf.map((item) => typeExpression(item, depth + 1));
1219
+ if (schema.properties || schema.additionalProperties !== void 0) {
1220
+ components.push(
1221
+ typeExpression(
1222
+ {
1223
+ type: "object",
1224
+ properties: schema.properties,
1225
+ required: schema.required,
1226
+ additionalProperties: schema.additionalProperties
1227
+ },
1228
+ depth + 1
1229
+ )
1230
+ );
1231
+ }
1232
+ return nullableType(components.join(" & "), schema);
1233
+ }
1234
+ const rawType = Array.isArray(schema.type) ? schema.type.filter((item) => item !== "null") : schema.type;
1235
+ const type = Array.isArray(rawType) ? rawType[0] : rawType;
1236
+ if (type === "array" || schema.items) {
1237
+ const itemType = typeExpression(schema.items, depth + 1);
1238
+ return nullableType(`${itemType.includes(" | ") ? `(${itemType})` : itemType}[]`, schema);
1239
+ }
1240
+ if (type === "object" || schema.properties || schema.additionalProperties !== void 0) {
1241
+ const properties = entries(schema.properties);
1242
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
1243
+ const lines = properties.map(([name, propertySchema]) => {
1244
+ const metadata = schemaMetadata(propertySchema).map(safeComment).filter(Boolean).join("; ");
1245
+ const comment = metadata ? `/** ${metadata} */ ` : "";
1246
+ return `${comment}${propertyKey(name)}${required.has(name) ? "" : "?"}: ${typeExpression(propertySchema, depth + 1)};`;
1247
+ });
1248
+ if (schema.additionalProperties === true) lines.push("[key: string]: unknown;");
1249
+ else if (isObject(schema.additionalProperties))
1250
+ lines.push(`[key: string]: ${typeExpression(schema.additionalProperties, depth + 1)};`);
1251
+ return nullableType(lines.length ? `{ ${lines.join(" ")} }` : "Record<string, unknown>", schema);
1252
+ }
1253
+ let base;
1254
+ switch (type) {
1255
+ case "string":
1256
+ case "file":
1257
+ base = cleanText(schema.format) ? `string /* ${safeComment(schema.format)} */` : "string";
1258
+ break;
1259
+ case "integer":
1260
+ case "number":
1261
+ base = "number";
1262
+ break;
1263
+ case "boolean":
1264
+ base = "boolean";
1265
+ break;
1266
+ case "null":
1267
+ base = "null";
1268
+ break;
1269
+ default:
1270
+ base = "unknown";
1271
+ }
1272
+ return nullableType(base, schema);
1273
+ }
1274
+ function getSchemaDictionary(spec) {
1275
+ const schemas = spec.components?.schemas ?? spec.definitions ?? {};
1276
+ return isObject(schemas) ? schemas : {};
1277
+ }
1278
+ function resolvePointer2(root, ref) {
1279
+ if (!ref.startsWith("#/")) return void 0;
1280
+ let current = root;
1281
+ for (const rawPart of ref.slice(2).split("/")) {
1282
+ const part = rawPart.replace(/~1/g, "/").replace(/~0/g, "~");
1283
+ if (!isObject(current)) return void 0;
1284
+ current = current[part];
1285
+ }
1286
+ return current;
1287
+ }
1288
+ function resolveReferenceObject(spec, value, seen = /* @__PURE__ */ new Set()) {
1289
+ if (!isObject(value)) return {};
1290
+ if (typeof value.$ref !== "string") return value;
1291
+ const ref = value.$ref;
1292
+ if (seen.has(ref)) return {};
1293
+ const target = resolvePointer2(spec, ref);
1294
+ if (!isObject(target)) return value;
1295
+ const nextSeen = new Set(seen).add(ref);
1296
+ return {
1297
+ ...resolveReferenceObject(spec, target, nextSeen),
1298
+ ...Object.fromEntries(Object.entries(value).filter(([key]) => key !== "$ref"))
1299
+ };
1300
+ }
1301
+
1302
+ // src/transformer/examples.ts
1303
+ function chooseMediaType(content) {
1304
+ const mediaTypes = Object.keys(content);
1305
+ return mediaTypes.find((type) => type.toLowerCase() === "application/json") ?? mediaTypes.find((type) => type.toLowerCase().includes("+json")) ?? mediaTypes[0];
1306
+ }
1307
+ function hasOwn(value, key) {
1308
+ return Object.prototype.hasOwnProperty.call(value, key);
1309
+ }
1310
+ function firstDeclaredExample(spec, schemaValue, seen = /* @__PURE__ */ new Set()) {
1311
+ if (!isObject(schemaValue)) return void 0;
1312
+ let schema = schemaValue;
1313
+ if (typeof schema.$ref === "string") {
1314
+ if (seen.has(schema.$ref)) return void 0;
1315
+ const target = resolvePointer2(spec, schema.$ref);
1316
+ if (!isObject(target)) return void 0;
1317
+ const nextSeen = new Set(seen).add(schema.$ref);
1318
+ schema = { ...target, ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$ref")) };
1319
+ const nested = firstDeclaredExample(spec, schema, nextSeen);
1320
+ if (nested !== void 0) return nested;
1321
+ }
1322
+ if (hasOwn(schema, "example")) return schema.example;
1323
+ if (Array.isArray(schema.examples) && schema.examples.length) return schema.examples[0];
1324
+ return void 0;
1325
+ }
1326
+ function generateSchemaExample(spec, schemaValue, depth = 0, seenRefs = /* @__PURE__ */ new Set()) {
1327
+ if (depth > 12 || !isObject(schemaValue)) return void 0;
1328
+ let schema = schemaValue;
1329
+ let nextSeen = seenRefs;
1330
+ if (typeof schema.$ref === "string") {
1331
+ if (seenRefs.has(schema.$ref)) return null;
1332
+ const target = resolvePointer2(spec, schema.$ref);
1333
+ if (!isObject(target)) return void 0;
1334
+ nextSeen = new Set(seenRefs).add(schema.$ref);
1335
+ schema = { ...target, ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$ref")) };
1336
+ }
1337
+ if (hasOwn(schema, "example")) return schema.example;
1338
+ if (Array.isArray(schema.examples) && schema.examples.length) return schema.examples[0];
1339
+ if (schema.default !== void 0) return schema.default;
1340
+ if (schema.const !== void 0) return schema.const;
1341
+ if (Array.isArray(schema.enum) && schema.enum.length) return schema.enum[0];
1342
+ for (const composition of ["oneOf", "anyOf"]) {
1343
+ if (Array.isArray(schema[composition]) && schema[composition].length) {
1344
+ return generateSchemaExample(spec, schema[composition][0], depth + 1, nextSeen);
1345
+ }
1346
+ }
1347
+ if (Array.isArray(schema.allOf) && schema.allOf.length) {
1348
+ const values = schema.allOf.map((part) => generateSchemaExample(spec, part, depth + 1, nextSeen)).filter((part) => part !== void 0);
1349
+ if (values.every((part) => isObject(part))) return Object.assign({}, ...values);
1350
+ return values[0];
1351
+ }
1352
+ const rawType = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
1353
+ const type = rawType ?? (schema.properties || schema.additionalProperties !== void 0 ? "object" : void 0);
1354
+ if (type === "array" || schema.items || schema.prefixItems) {
1355
+ const tupleSchemas = Array.isArray(schema.prefixItems) ? schema.prefixItems : void 0;
1356
+ if (tupleSchemas?.length) {
1357
+ return tupleSchemas.slice(0, 3).map((item2) => generateSchemaExample(spec, item2, depth + 1, nextSeen));
1358
+ }
1359
+ const item = generateSchemaExample(spec, schema.items, depth + 1, nextSeen);
1360
+ return item === void 0 ? [] : [item];
1361
+ }
1362
+ if (type === "object") {
1363
+ const result = {};
1364
+ for (const [name, propertySchema] of entries(schema.properties)) {
1365
+ const value = generateSchemaExample(spec, propertySchema, depth + 1, nextSeen);
1366
+ if (value !== void 0) result[name] = value;
1367
+ }
1368
+ if (schema.additionalProperties === true) result.additionalProp1 = "string";
1369
+ else if (isObject(schema.additionalProperties)) {
1370
+ const value = generateSchemaExample(spec, schema.additionalProperties, depth + 1, nextSeen);
1371
+ if (value !== void 0) result.additionalProp1 = value;
1372
+ }
1373
+ return result;
1374
+ }
1375
+ if (type === "string") {
1376
+ switch (cleanText(schema.format).toLowerCase()) {
1377
+ case "uuid":
1378
+ return "00000000-0000-4000-8000-000000000001";
1379
+ case "date":
1380
+ return "2026-01-01";
1381
+ case "date-time":
1382
+ return "2026-01-01T00:00:00Z";
1383
+ case "time":
1384
+ return "12:00:00Z";
1385
+ case "email":
1386
+ return "user@example.com";
1387
+ case "uri":
1388
+ case "url":
1389
+ return "https://example.com/resource";
1390
+ case "hostname":
1391
+ return "example.com";
1392
+ case "ipv4":
1393
+ return "192.0.2.1";
1394
+ case "ipv6":
1395
+ return "2001:db8::1";
1396
+ case "byte":
1397
+ return "c3RyaW5n";
1398
+ default:
1399
+ return "string";
1400
+ }
1401
+ }
1402
+ if (type === "integer" || type === "number") {
1403
+ if (typeof schema.minimum === "number") return schema.minimum;
1404
+ if (typeof schema.exclusiveMinimum === "number") return schema.exclusiveMinimum + (type === "integer" ? 1 : 0.1);
1405
+ return type === "integer" ? 0 : 0;
1406
+ }
1407
+ if (type === "boolean") return true;
1408
+ if (type === "null") return null;
1409
+ if (schema.properties || schema.additionalProperties !== void 0) return {};
1410
+ return void 0;
1411
+ }
1412
+ function payloadExample(spec, mediaValue, schemaValue, legacyExamples) {
1413
+ const media = isObject(mediaValue) ? mediaValue : {};
1414
+ if (hasOwn(media, "example")) return { value: media.example, origin: "declared" };
1415
+ for (const [name, exampleValue] of entries(media.examples)) {
1416
+ const example = resolveReferenceObject(spec, exampleValue);
1417
+ if (hasOwn(example, "value")) return { value: example.value, origin: "declared", name };
1418
+ }
1419
+ if (isObject(legacyExamples)) {
1420
+ const mediaType = Object.keys(legacyExamples).find((key) => key.toLowerCase() === "application/json") ?? Object.keys(legacyExamples)[0];
1421
+ if (mediaType && hasOwn(legacyExamples, mediaType)) {
1422
+ return { value: legacyExamples[mediaType], origin: "declared" };
1423
+ }
1424
+ }
1425
+ const declaredSchemaExample = firstDeclaredExample(spec, schemaValue);
1426
+ if (declaredSchemaExample !== void 0) return { value: declaredSchemaExample, origin: "declared" };
1427
+ const generated = generateSchemaExample(spec, schemaValue);
1428
+ return generated === void 0 ? void 0 : { value: generated, origin: "schema-derived" };
1429
+ }
1430
+ function formattedExample(value) {
1431
+ try {
1432
+ return JSON.stringify(value, null, 2) ?? "null";
1433
+ } catch {
1434
+ return "null";
1435
+ }
1436
+ }
1437
+
1438
+ // src/transformer/parameters.ts
1439
+ function mergedParameters(spec, endpoint) {
1440
+ const operationParameters = Array.isArray(endpoint.operation.parameters) ? endpoint.operation.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
1441
+ const byIdentity = /* @__PURE__ */ new Map();
1442
+ for (const parameter of [...endpoint.pathParameters, ...operationParameters]) {
1443
+ if (parameter.in === "body") continue;
1444
+ const key = `${cleanText(parameter.in)}:${cleanText(parameter.name)}`;
1445
+ if (key !== ":") byIdentity.set(key, parameter);
1446
+ }
1447
+ return [...byIdentity.values()];
1448
+ }
1449
+ function parameterType(parameter) {
1450
+ if (isObject(parameter.schema)) return typeExpression(parameter.schema);
1451
+ if (parameter.type === "array") return `${typeExpression(parameter.items)}[]`;
1452
+ if (parameter.type) return typeExpression({ type: parameter.type, format: parameter.format, enum: parameter.enum });
1453
+ return "unknown";
1454
+ }
1455
+ function parameterDisplayType(parameter) {
1456
+ return parameterType(parameter).replace(/\/\*\s*([^*]+?)\s*\*\//g, "($1)");
1457
+ }
1458
+ function parameterDefault(parameter) {
1459
+ if (parameter.default !== void 0) return parameter.default;
1460
+ if (isObject(parameter.schema)) return parameter.schema.default;
1461
+ return void 0;
1462
+ }
1463
+ function isMeaningfulDefault(value) {
1464
+ return value !== void 0 && value !== null && value !== "";
1465
+ }
1466
+ function parameterList(parameters, style = FULL_STYLE) {
1467
+ if (!parameters.length) return "_None._";
1468
+ if (style.collapsed) {
1469
+ return `- _${parameters.length} parameter${parameters.length === 1 ? "" : "s"} (detail collapsed for token budget)._`;
1470
+ }
1471
+ return parameters.map((parameter) => {
1472
+ const required = parameter.required === true || parameter.in === "path";
1473
+ const type = parameterDisplayType(parameter);
1474
+ const defaultValue = parameterDefault(parameter);
1475
+ const defaultSuffix = isMeaningfulDefault(defaultValue) ? ` (default: ${typeof defaultValue === "string" ? JSON.stringify(defaultValue) : String(defaultValue)})` : "";
1476
+ const description = shortenDescription(cleanText(parameter.description), style.descriptionLimit);
1477
+ const details = description ? ` \u2014 ${description}` : "";
1478
+ return `- \`${cleanText(parameter.name)}\` (\`${cleanText(parameter.in)}\`${required ? ", required" : ""}) \xB7 \`${type}\`${defaultSuffix}${details}`;
1479
+ }).join("\n");
1480
+ }
1481
+ function requestPayloadDescription(spec, operation, style = FULL_STYLE) {
1482
+ if (isObject(operation.requestBody) || operation.requestBody?.$ref) {
1483
+ const body = resolveReferenceObject(spec, operation.requestBody);
1484
+ const content = isObject(body.content) ? body.content : {};
1485
+ const mediaType = chooseMediaType(content);
1486
+ const media = mediaType ? content[mediaType] : void 0;
1487
+ const schema = isObject(media?.schema) ? media.schema : void 0;
1488
+ return {
1489
+ type: schema ? typeExpression(schema) : "unknown",
1490
+ mediaType: mediaType || "unknown media type",
1491
+ example: style.includeExamples && (media || schema) ? payloadExample(spec, media, schema) : void 0,
1492
+ required: body.required === true
1493
+ };
1494
+ }
1495
+ const parameters = Array.isArray(operation.parameters) ? operation.parameters : [];
1496
+ const bodyParameter = parameters.find((parameter) => isObject(parameter) && parameter.in === "body");
1497
+ if (bodyParameter) {
1498
+ const consumes = Array.isArray(operation.consumes) ? operation.consumes : Array.isArray(spec.consumes) ? spec.consumes : ["application/json"];
1499
+ return {
1500
+ type: typeExpression(bodyParameter.schema),
1501
+ mediaType: consumes[0] ?? "application/json",
1502
+ example: style.includeExamples ? payloadExample(spec, void 0, bodyParameter.schema) : void 0,
1503
+ required: bodyParameter.required === true
1504
+ };
1505
+ }
1506
+ return void 0;
1507
+ }
1508
+
1509
+ // src/transformer/responses.ts
1510
+ var COMMON_ERROR_CODES = /* @__PURE__ */ new Set([
1511
+ "400",
1512
+ "401",
1513
+ "403",
1514
+ "404",
1515
+ "405",
1516
+ "409",
1517
+ "415",
1518
+ "422",
1519
+ "429",
1520
+ "500",
1521
+ "501",
1522
+ "502",
1523
+ "503",
1524
+ "default"
1525
+ ]);
1526
+ function responseSchema(spec, responseValue, style = FULL_STYLE) {
1527
+ const response = resolveReferenceObject(spec, responseValue);
1528
+ if (isObject(response.content)) {
1529
+ const mediaType = chooseMediaType(response.content);
1530
+ const media = mediaType ? response.content[mediaType] : void 0;
1531
+ const schema = isObject(media?.schema) ? media.schema : void 0;
1532
+ return {
1533
+ type: schema ? typeExpression(schema) : void 0,
1534
+ mediaType,
1535
+ example: style.includeExamples && (schema || media) ? payloadExample(spec, media, schema, response.examples) : void 0
1536
+ };
1537
+ }
1538
+ if (isObject(response.schema)) {
1539
+ return {
1540
+ type: typeExpression(response.schema),
1541
+ mediaType: "application/json",
1542
+ example: style.includeExamples ? payloadExample(spec, void 0, response.schema, response.examples) : void 0
1543
+ };
1544
+ }
1545
+ return {};
1546
+ }
1547
+ function responseBlock(spec, code, responseValue, style = FULL_STYLE) {
1548
+ const response = resolveReferenceObject(spec, responseValue);
1549
+ const description = style.collapsed ? "" : shortenDescription(cleanText(response.description), style.descriptionLimit);
1550
+ const payload = responseSchema(spec, responseValue, style);
1551
+ const suffix = payload.type ? ` \u2014 \`${payload.type}\`${payload.mediaType ? ` (${payload.mediaType})` : ""}` : " \u2014 no body schema declared";
1552
+ if (style.collapsed) {
1553
+ return [`- **${markdownCell(code)}**${suffix}`].join("\n");
1554
+ }
1555
+ const lines = [`- **${markdownCell(code)}${description ? ` ${description}` : ""}**${suffix}`];
1556
+ if (payload.example?.origin === "declared") {
1557
+ const label = payload.example.name ? ` \xB7 ${payload.example.name}` : "";
1558
+ lines.push(
1559
+ "",
1560
+ ` **Example Value** *(declared in the spec${label})*`,
1561
+ "",
1562
+ " ```json",
1563
+ ...formattedExample(payload.example.value).split("\n").map((line) => ` ${line}`),
1564
+ " ```"
1565
+ );
1566
+ }
1567
+ return lines.join("\n");
1568
+ }
1569
+ var COLLAPSIBLE_ERROR_CODES = /* @__PURE__ */ new Set(["400", "401", "403", "404", "500", "502", "503"]);
1570
+ var GENERIC_ERROR_DESCRIPTIONS = /* @__PURE__ */ new Set([
1571
+ "",
1572
+ "an error occurred",
1573
+ "bad gateway",
1574
+ "bad request",
1575
+ "client error",
1576
+ "error",
1577
+ "failed",
1578
+ "forbidden",
1579
+ "internal error",
1580
+ "internal server error",
1581
+ "invalid request",
1582
+ "not found",
1583
+ "payment required",
1584
+ "request error",
1585
+ "request failed",
1586
+ "server error",
1587
+ "service unavailable",
1588
+ "too many requests",
1589
+ "unauthorized",
1590
+ "unexpected error",
1591
+ "unknown error"
1592
+ ]);
1593
+ function isGenericErrorDescription(description) {
1594
+ if (!description) return true;
1595
+ return GENERIC_ERROR_DESCRIPTIONS.has(
1596
+ description.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()
1597
+ );
1598
+ }
1599
+ function responseSchemaValue(spec, responseValue) {
1600
+ const response = resolveReferenceObject(spec, responseValue);
1601
+ if (isObject(response.content)) {
1602
+ const mediaType = chooseMediaType(response.content);
1603
+ const media = mediaType ? response.content[mediaType] : void 0;
1604
+ return isObject(media?.schema) ? media.schema : void 0;
1605
+ }
1606
+ if (isObject(response.schema)) return response.schema;
1607
+ return void 0;
1608
+ }
1609
+ function collapsibleErrorEntry(spec, code, responseValue) {
1610
+ if (!COLLAPSIBLE_ERROR_CODES.has(code)) return void 0;
1611
+ const response = resolveReferenceObject(spec, responseValue);
1612
+ if (!isGenericErrorDescription(cleanText(response.description))) return void 0;
1613
+ if (isObject(response.headers) && Object.keys(response.headers).length) return void 0;
1614
+ if (responseSchema(spec, responseValue).example?.origin === "declared") return void 0;
1615
+ const schemaValue = responseSchemaValue(spec, responseValue);
1616
+ if (schemaValue === void 0) return `\`${code}\``;
1617
+ return `\`${code}\` (\`${typeExpression(schemaValue)}\`)`;
1618
+ }
1619
+ function responseSection(spec, operation, style = FULL_STYLE) {
1620
+ const responses = entries(operation.responses).filter(([, value]) => isObject(value));
1621
+ if (!responses.length) return "_No responses declared._";
1622
+ const success = responses.filter(([code]) => /^2\d\d$/.test(code));
1623
+ success.sort(([a], [b]) => a === "200" ? -1 : b === "200" ? 1 : Number(a) - Number(b));
1624
+ const errors = responses.filter(
1625
+ ([code]) => (COMMON_ERROR_CODES.has(code) || /^([45])\d\d$/.test(code)) && !success.some(([successCode]) => successCode === code)
1626
+ );
1627
+ if (style.collapsed) {
1628
+ const collapsedLines = success.map(([code, response]) => responseBlock(spec, code, response, style));
1629
+ collapsedLines.push(...errors.map(([code, responseValue]) => responseBlock(spec, code, responseValue, style)));
1630
+ return collapsedLines.join("\n");
1631
+ }
1632
+ const detailedErrors = [];
1633
+ const collapsedErrors = [];
1634
+ for (const [code, responseValue] of errors) {
1635
+ const entry = collapsibleErrorEntry(spec, code, responseValue);
1636
+ if (entry === void 0) detailedErrors.push(responseBlock(spec, code, responseValue, style));
1637
+ else collapsedErrors.push(entry);
1638
+ }
1639
+ const primarySuccess = success[0];
1640
+ const lines = primarySuccess ? [responseBlock(spec, primarySuccess[0], primarySuccess[1], style)] : [];
1641
+ lines.push(...detailedErrors);
1642
+ if (collapsedErrors.length) lines.push(`- **Errors:** ${collapsedErrors.join(", ")}`);
1643
+ if (success.length > 1) {
1644
+ const others = success.slice(1).map(([code, response]) => responseBlock(spec, code, response, style));
1645
+ lines.push("", "<details><summary>Other successful responses</summary>", "", ...others, "", "</details>");
1646
+ }
1647
+ if (!lines.length) {
1648
+ return responses.map(([code, response]) => responseBlock(spec, code, response, style)).join("\n\n");
1649
+ }
1650
+ return lines.join("\n");
1651
+ }
1652
+
1653
+ // src/transformer/endpoints.ts
1654
+ var HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
1655
+ function getEndpoints(spec) {
1656
+ const result = [];
1657
+ const paths = isObject(spec.paths) ? spec.paths : {};
1658
+ for (const [path2, pathItemValue] of Object.entries(paths)) {
1659
+ if (!isObject(pathItemValue)) continue;
1660
+ const pathParameters = Array.isArray(pathItemValue.parameters) ? pathItemValue.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
1661
+ for (const method of HTTP_METHODS) {
1662
+ const operationValue = pathItemValue[method];
1663
+ if (!isObject(operationValue)) continue;
1664
+ const firstTag = Array.isArray(operationValue.tags) && operationValue.tags.length ? String(operationValue.tags[0]) : "General";
1665
+ result.push({ method, path: path2, operation: operationValue, pathParameters, tag: firstTag });
1666
+ }
1667
+ }
1668
+ return result;
1669
+ }
1670
+ function getServers(spec) {
1671
+ if (Array.isArray(spec.servers) && spec.servers.length) {
1672
+ return spec.servers.map((server) => cleanText(isObject(server) ? server.url : void 0)).filter(Boolean);
1673
+ }
1674
+ if (isObject(spec)) {
1675
+ const host = cleanText(spec.host);
1676
+ if (host) {
1677
+ const schemes = Array.isArray(spec.schemes) && spec.schemes.length ? spec.schemes : ["https"];
1678
+ const basePath = cleanText(spec.basePath) || "/";
1679
+ return schemes.map(
1680
+ (scheme) => `${scheme}://${host}${basePath.startsWith("/") ? basePath : `/${basePath}`}`
1681
+ );
1682
+ }
1683
+ }
1684
+ return [];
1685
+ }
1686
+ function collectTags(spec, endpoints) {
1687
+ const declared = Array.isArray(spec.tags) ? spec.tags.map((tag) => cleanText(isObject(tag) ? tag.name : void 0)).filter(Boolean) : [];
1688
+ const encountered = [...new Set(endpoints.map((endpoint) => endpoint.tag))];
1689
+ return [
1690
+ ...declared.filter((tag) => encountered.includes(tag)),
1691
+ ...encountered.filter((tag) => !declared.includes(tag))
1692
+ ];
1693
+ }
1694
+ function endpointTypeBase(method, path2, operation) {
1695
+ if (cleanText(operation.operationId)) return toTypeScriptIdentifier(cleanText(operation.operationId));
1696
+ const pathName = path2.replace(/[{}]/g, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1697
+ return toTypeScriptIdentifier(`${method.charAt(0).toUpperCase()}${method.slice(1)}${pathName}`);
1698
+ }
1699
+ function shortOperationDescription(operation) {
1700
+ return markdownCell(operation.summary || operation.operationId || operation.description || "\u2014");
1701
+ }
1702
+ function endpointMarkdown(spec, endpoint, style = FULL_STYLE) {
1703
+ const { operation, method, path: path2 } = endpoint;
1704
+ const heading = `#### \`${method.toUpperCase()} ${path2}\``;
1705
+ const summary = cleanText(operation.summary);
1706
+ const description = cleanText(operation.description);
1707
+ const lines = [heading];
1708
+ if (summary) lines.push("", shortenDescription(summary, style.descriptionLimit));
1709
+ if (description && description !== summary) {
1710
+ lines.push("", shortenDescription(description, style.descriptionLimit));
1711
+ }
1712
+ lines.push("", `**Authentication:** ${securityRequirement(spec, operation)}`);
1713
+ const parameters = mergedParameters(spec, endpoint);
1714
+ const request = requestPayloadDescription(spec, operation, style);
1715
+ lines.push("", "**Parameters**");
1716
+ lines.push(parameterList(parameters, style));
1717
+ if (request) {
1718
+ lines.push(
1719
+ "",
1720
+ `**Request body:** \`${request.mediaType}\` \u2192 \`${request.type}\`${request.required ? " (required)" : " (optional)"}`
1721
+ );
1722
+ if (request.example?.origin === "declared") {
1723
+ const label = request.example.name ? ` \xB7 ${request.example.name}` : "";
1724
+ lines.push(
1725
+ "",
1726
+ `**Request Example Value** *(declared in the spec${label})*`,
1727
+ "",
1728
+ "```json",
1729
+ formattedExample(request.example.value),
1730
+ "```"
1731
+ );
1732
+ }
1733
+ }
1734
+ lines.push("", "**Responses**", responseSection(spec, operation, style));
1735
+ return lines.join("\n");
1736
+ }
1737
+
1738
+ // src/transformer/models.ts
1739
+ function collectSchemaRefs(value, output, root, seen = /* @__PURE__ */ new Set(), followedRefs = /* @__PURE__ */ new Set()) {
1740
+ if (!value || typeof value !== "object" || seen.has(value)) return;
1741
+ seen.add(value);
1742
+ if (Array.isArray(value)) {
1743
+ for (const item of value) collectSchemaRefs(item, output, root, seen, followedRefs);
1744
+ return;
1745
+ }
1746
+ const object = value;
1747
+ if (typeof object.$ref === "string") {
1748
+ if (isSchemaRef(object.$ref)) {
1749
+ const name = refName(object.$ref);
1750
+ if (name) output.add(name);
1751
+ } else if (root && object.$ref.startsWith("#/") && !followedRefs.has(object.$ref)) {
1752
+ followedRefs.add(object.$ref);
1753
+ const target = resolvePointer2(root, object.$ref);
1754
+ if (target !== void 0) collectSchemaRefs(target, output, root, seen, followedRefs);
1755
+ }
1756
+ }
1757
+ for (const [key, child] of Object.entries(object)) {
1758
+ if (key !== "example" && key !== "examples" && key !== "externalDocs")
1759
+ collectSchemaRefs(child, output, root, seen, followedRefs);
1760
+ }
1761
+ }
1762
+ function referencedSchemas(spec) {
1763
+ const dictionary = getSchemaDictionary(spec);
1764
+ const knownByName = /* @__PURE__ */ new Map();
1765
+ for (const [name, schema] of Object.entries(dictionary)) {
1766
+ knownByName.set(toTypeScriptIdentifier(name), [name, schema]);
1767
+ }
1768
+ const refs = /* @__PURE__ */ new Set();
1769
+ collectSchemaRefs(spec.paths ?? {}, refs, spec);
1770
+ const selected = /* @__PURE__ */ new Set();
1771
+ const queue = [...refs];
1772
+ while (queue.length) {
1773
+ const name = queue.shift();
1774
+ if (!name || selected.has(name)) continue;
1775
+ selected.add(name);
1776
+ const model = knownByName.get(name);
1777
+ if (!model) continue;
1778
+ const childRefs = /* @__PURE__ */ new Set();
1779
+ collectSchemaRefs(model[1], childRefs, spec);
1780
+ for (const child of childRefs) if (!selected.has(child)) queue.push(child);
1781
+ }
1782
+ return [...selected].map((name) => knownByName.get(name)).filter((entry) => Boolean(entry));
1783
+ }
1784
+ function modelDeclaration(name, schema) {
1785
+ const identifier = toTypeScriptIdentifier(name);
1786
+ const description = safeComment(schema.description);
1787
+ const comment = description ? `/** ${description} */
1788
+ ` : "";
1789
+ if (schema.$ref) return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
1790
+ if (Array.isArray(schema.enum) || schema.const !== void 0 || schema.oneOf || schema.anyOf || schema.allOf) {
1791
+ return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
1792
+ }
1793
+ const kind = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
1794
+ if (kind === "object" || schema.properties || schema.additionalProperties !== void 0) {
1795
+ const properties = entries(schema.properties);
1796
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
1797
+ const fields = properties.map(([field, value]) => {
1798
+ const metadata = schemaMetadata(value).map(safeComment).filter(Boolean).join("; ");
1799
+ const doc = metadata ? ` /** ${metadata} */
1800
+ ` : "";
1801
+ return `${doc} ${propertyKey(field)}${required.has(field) ? "" : "?"}: ${typeExpression(value)};`;
1802
+ });
1803
+ if (schema.additionalProperties === true) fields.push(" [key: string]: unknown;");
1804
+ else if (isObject(schema.additionalProperties))
1805
+ fields.push(` [key: string]: ${typeExpression(schema.additionalProperties)};`);
1806
+ return `${comment}export interface ${identifier} {
1807
+ ${fields.join("\n")}
1808
+ }`;
1809
+ }
1810
+ return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
1811
+ }
1812
+ function normalizeInlinePayloadSchemas(spec) {
1813
+ const document = structuredClone(spec);
1814
+ const isOpenApi3 = typeof document.openapi === "string";
1815
+ const schemaPointer = isOpenApi3 ? "#/components/schemas/" : "#/definitions/";
1816
+ let schemaBucket;
1817
+ if (isOpenApi3) {
1818
+ if (!isObject(document.components)) document.components = {};
1819
+ if (!isObject(document.components.schemas)) document.components.schemas = {};
1820
+ schemaBucket = document.components.schemas;
1821
+ } else {
1822
+ if (!isObject(document.definitions)) document.definitions = {};
1823
+ schemaBucket = document.definitions;
1824
+ }
1825
+ const uniqueName = (suggestion) => {
1826
+ const base = toTypeScriptIdentifier(suggestion);
1827
+ let candidate = base;
1828
+ let suffix = 2;
1829
+ while (Object.prototype.hasOwnProperty.call(schemaBucket, candidate)) {
1830
+ candidate = `${base}${suffix}`;
1831
+ suffix += 1;
1832
+ }
1833
+ return candidate;
1834
+ };
1835
+ const replaceInline = (schema, name) => {
1836
+ if (!isObject(schema) || typeof schema.$ref === "string" || Object.keys(schema).length === 0) return schema;
1837
+ const unique = uniqueName(name);
1838
+ schemaBucket[unique] = schema;
1839
+ return { $ref: `${schemaPointer}${unique.replace(/~/g, "~0").replace(/\//g, "~1")}` };
1840
+ };
1841
+ const reusableBodies = isOpenApi3 ? document.components?.requestBodies : document.parameters;
1842
+ for (const [bodyName, bodyValue] of entries(reusableBodies)) {
1843
+ if (!isObject(bodyValue)) continue;
1844
+ if (isOpenApi3 && isObject(bodyValue.content)) {
1845
+ const mediaType = chooseMediaType(bodyValue.content);
1846
+ const media = mediaType ? bodyValue.content[mediaType] : void 0;
1847
+ if (isObject(media) && isObject(media.schema)) media.schema = replaceInline(media.schema, `${bodyName}Request`);
1848
+ } else if (!isOpenApi3 && bodyValue.in === "body" && isObject(bodyValue.schema)) {
1849
+ bodyValue.schema = replaceInline(bodyValue.schema, `${bodyName}Request`);
1850
+ }
1851
+ }
1852
+ const reusableResponses = isOpenApi3 ? document.components?.responses : document.responses;
1853
+ for (const [responseName, responseValue] of entries(reusableResponses)) {
1854
+ if (!isObject(responseValue)) continue;
1855
+ if (isOpenApi3 && isObject(responseValue.content)) {
1856
+ const mediaType = chooseMediaType(responseValue.content);
1857
+ const media = mediaType ? responseValue.content[mediaType] : void 0;
1858
+ if (isObject(media) && isObject(media.schema))
1859
+ media.schema = replaceInline(media.schema, `${responseName}Response`);
1860
+ } else if (!isOpenApi3 && isObject(responseValue.schema)) {
1861
+ responseValue.schema = replaceInline(responseValue.schema, `${responseName}Response`);
1862
+ }
1863
+ }
1864
+ const paths = isObject(document.paths) ? document.paths : {};
1865
+ for (const [path2, pathItemValue] of Object.entries(paths)) {
1866
+ if (!isObject(pathItemValue)) continue;
1867
+ for (const method of HTTP_METHODS) {
1868
+ const operation = pathItemValue[method];
1869
+ if (!isObject(operation)) continue;
1870
+ const base = endpointTypeBase(method, path2, operation);
1871
+ if (isOpenApi3 && isObject(operation.requestBody) && isObject(operation.requestBody.content)) {
1872
+ const content = operation.requestBody.content;
1873
+ const mediaType = chooseMediaType(content);
1874
+ const media = mediaType ? content[mediaType] : void 0;
1875
+ if (isObject(media) && isObject(media.schema)) {
1876
+ media.schema = replaceInline(media.schema, `${base}Request`);
1877
+ }
1878
+ } else if (!isOpenApi3 && Array.isArray(operation.parameters)) {
1879
+ for (const parameter of operation.parameters) {
1880
+ if (isObject(parameter) && parameter.in === "body" && isObject(parameter.schema)) {
1881
+ parameter.schema = replaceInline(parameter.schema, `${base}Request`);
1882
+ }
1883
+ }
1884
+ }
1885
+ if (isObject(operation.responses)) {
1886
+ for (const [code, responseValue] of Object.entries(operation.responses)) {
1887
+ if (!isObject(responseValue) || typeof responseValue.$ref === "string") continue;
1888
+ const suffix = code === "default" ? "Default" : code.replace(/[^a-zA-Z0-9]/g, "");
1889
+ if (isObject(responseValue.content)) {
1890
+ const mediaType = chooseMediaType(responseValue.content);
1891
+ const media = mediaType ? responseValue.content[mediaType] : void 0;
1892
+ if (isObject(media) && isObject(media.schema)) {
1893
+ media.schema = replaceInline(media.schema, `${base}${suffix}Response`);
1894
+ }
1895
+ } else if (!isOpenApi3 && isObject(responseValue.schema)) {
1896
+ responseValue.schema = replaceInline(responseValue.schema, `${base}${suffix}Response`);
1897
+ }
1898
+ }
1899
+ }
1900
+ }
1901
+ }
1902
+ return document;
1903
+ }
1904
+
1905
+ // src/transformer/index.ts
1906
+ var HTTP_METHODS2 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
1907
+ function tagsOfEndpoint(endpoint) {
1908
+ const tags = endpoint.operation.tags;
1909
+ return Array.isArray(tags) && tags.length ? tags.map(String) : ["General"];
1910
+ }
1911
+ function filterSpec(spec, endpoints, options, warnings) {
1912
+ const includeTags = options.includeTags ?? [];
1913
+ const excludeTags = options.excludeTags ?? [];
1914
+ const hasFilter = includeTags.length > 0 || excludeTags.length > 0 || options.pathPrefix !== void 0;
1915
+ if (!hasFilter) return spec;
1916
+ const declared = Array.isArray(spec.tags) ? spec.tags.map((tag) => isObject(tag) && typeof tag.name === "string" ? tag.name : "").filter(Boolean) : [];
1917
+ const available = [.../* @__PURE__ */ new Set([...declared, ...endpoints.map((endpoint) => endpoint.tag)])];
1918
+ const unknownTags = [
1919
+ ...includeTags.filter((tag) => !available.includes(tag)),
1920
+ ...excludeTags.filter((tag) => !available.includes(tag))
1921
+ ];
1922
+ if (unknownTags.length > 0) {
1923
+ warnings.push(
1924
+ `Unknown tag(s): ${[...new Set(unknownTags)].join(", ")}. Available tags: ${available.join(", ") || "none"}.`
1925
+ );
1926
+ }
1927
+ const keep = (endpoint) => {
1928
+ if (options.pathPrefix && !endpoint.path.startsWith(options.pathPrefix)) return false;
1929
+ const tags = tagsOfEndpoint(endpoint);
1930
+ if (includeTags.length > 0 && !tags.some((tag) => includeTags.includes(tag))) return false;
1931
+ if (excludeTags.length > 0 && tags.some((tag) => excludeTags.includes(tag))) return false;
1932
+ return true;
1933
+ };
1934
+ const paths = {};
1935
+ for (const endpoint of endpoints.filter(keep)) {
1936
+ const source = isObject(spec.paths?.[endpoint.path]) ? spec.paths[endpoint.path] : {};
1937
+ const item = paths[endpoint.path] ?? { ...source };
1938
+ for (const method of HTTP_METHODS2) delete item[method];
1939
+ item[endpoint.method] = source[endpoint.method];
1940
+ paths[endpoint.path] = item;
1941
+ }
1942
+ return { ...spec, paths };
1943
+ }
1944
+ function buildTrimStages(_endpoints, tags) {
1945
+ const stages = [];
1946
+ const emptyTags = /* @__PURE__ */ new Set();
1947
+ stages.push({
1948
+ includeExamples: true,
1949
+ collapsedTags: emptyTags,
1950
+ indexOnly: false
1951
+ });
1952
+ stages.push({
1953
+ includeExamples: false,
1954
+ collapsedTags: emptyTags,
1955
+ indexOnly: false,
1956
+ action: "removed declared example payloads"
1957
+ });
1958
+ stages.push({
1959
+ includeExamples: false,
1960
+ descriptionLimit: 200,
1961
+ collapsedTags: emptyTags,
1962
+ indexOnly: false,
1963
+ action: "shortened long descriptions"
1964
+ });
1965
+ const collapsedSoFar = /* @__PURE__ */ new Set();
1966
+ for (const tag of [...tags].reverse()) {
1967
+ collapsedSoFar.add(tag);
1968
+ stages.push({
1969
+ includeExamples: false,
1970
+ descriptionLimit: 200,
1971
+ collapsedTags: new Set(collapsedSoFar),
1972
+ indexOnly: false,
1973
+ action: `collapsed parameter and response detail for tag "${tag}"`
1974
+ });
1975
+ }
1976
+ stages.push({
1977
+ includeExamples: false,
1978
+ descriptionLimit: 200,
1979
+ collapsedTags: new Set(tags),
1980
+ indexOnly: true,
1981
+ action: "kept only the Quick index entries for the remaining tags"
1982
+ });
1983
+ return stages;
1984
+ }
1985
+ function renderDigest(spec, sourceUrl, endpoints, tags, stage) {
1986
+ const info = isObject(spec.info) ? spec.info : {};
1987
+ const title = cleanText(info.title) || "API";
1988
+ const version = cleanText(info.version) || "unspecified";
1989
+ const openApiVersion = cleanText(spec.openapi) || (spec.swagger ? `Swagger ${spec.swagger}` : "OpenAPI");
1990
+ const servers = getServers(spec);
1991
+ const indexRows = endpoints.map(
1992
+ (endpoint) => `| **${endpoint.method.toUpperCase()}** | \`${markdownCell(endpoint.path)}\` | ${shortOperationDescription(endpoint.operation)} | ${markdownCell(Array.isArray(endpoint.operation.tags) ? endpoint.operation.tags.join(", ") : endpoint.tag)} |`
1993
+ );
1994
+ const detailEndpoints = stage.indexOnly ? [] : endpoints.filter((endpoint) => !stage.collapsedTags.has(endpoint.tag));
1995
+ const groupedEndpoints = tags.map((tag) => {
1996
+ const members = endpoints.filter((endpoint) => endpoint.tag === tag);
1997
+ if (!members.length) return "";
1998
+ const rendered = members.map(
1999
+ (endpoint) => endpointMarkdown(spec, endpoint, {
2000
+ includeExamples: stage.includeExamples,
2001
+ descriptionLimit: stage.descriptionLimit,
2002
+ collapsed: stage.collapsedTags.has(endpoint.tag)
2003
+ })
2004
+ );
2005
+ return [`### ${tag}`, ...rendered].join("\n\n");
2006
+ }).filter(Boolean);
2007
+ let modelSpec = spec;
2008
+ if (detailEndpoints.length !== endpoints.length) {
2009
+ const scopedPaths = {};
2010
+ for (const endpoint of detailEndpoints) {
2011
+ const source = isObject(spec.paths?.[endpoint.path]) ? spec.paths[endpoint.path] : {};
2012
+ const item = scopedPaths[endpoint.path] ?? { ...source };
2013
+ for (const method of HTTP_METHODS2) delete item[method];
2014
+ item[endpoint.method] = source[endpoint.method];
2015
+ scopedPaths[endpoint.path] = item;
2016
+ }
2017
+ modelSpec = { ...spec, paths: scopedPaths };
2018
+ }
2019
+ const models = referencedSchemas(modelSpec);
2020
+ const modelSection = !stage.indexOnly && models.length ? [
2021
+ "## Data types",
2022
+ "",
2023
+ "```typescript",
2024
+ models.map(([name, schema]) => modelDeclaration(name, schema)).join("\n\n"),
2025
+ "```"
2026
+ ].join("\n") : "";
2027
+ const description = cleanText(info.description);
2028
+ const sections = [
2029
+ `# API Digest: ${title}`,
2030
+ "",
2031
+ `> Spec: ${openApiVersion} \xB7 API version: ${version}${sourceUrl ? ` \xB7 Source: \`${markdownCell(safeSourceUrl(sourceUrl))}\`` : ""}`,
2032
+ "",
2033
+ "## Overview",
2034
+ ...description ? ["", description] : [],
2035
+ "",
2036
+ `- **Base URL(s):** ${servers.length ? servers.map((server) => `\`${markdownCell(server)}\``).join(", ") : "Not declared"}`,
2037
+ ...authOverview(spec),
2038
+ "",
2039
+ "## Quick index",
2040
+ "",
2041
+ "| Method | Path | Summary | Tags |",
2042
+ "|---|---|---|---|",
2043
+ ...indexRows.length ? indexRows : ["| \u2014 | \u2014 | No operations declared | \u2014 |"]
2044
+ ];
2045
+ if (!stage.indexOnly) {
2046
+ sections.push(
2047
+ "",
2048
+ "## Endpoints",
2049
+ "",
2050
+ ...groupedEndpoints.length ? groupedEndpoints : ["_No endpoints declared._"]
2051
+ );
2052
+ }
2053
+ if (modelSection) {
2054
+ sections.push("", modelSection);
2055
+ }
2056
+ return `${sections.join("\n").replace(/\n{3,}/g, "\n\n").trim()}
2057
+ `;
2058
+ }
2059
+ function appendTrimNote(markdown, actions, budget) {
2060
+ return `${markdown}
2061
+ ## Trimmed
2062
+
2063
+ > _Trimmed to fit the ~${budget.toLocaleString()}-token budget: ${actions.join("; ")}._
2064
+ `;
2065
+ }
2066
+ function transformOpenApiToMarkdown(spec, sourceUrl, options = {}) {
2067
+ const warnings = [];
2068
+ const filteredSpec = filterSpec(spec, getEndpoints(spec), options, warnings);
2069
+ for (const message of warnings) options.onWarning?.(message);
2070
+ const normalizedSpec = normalizeInlinePayloadSchemas(filteredSpec);
2071
+ const endpoints = getEndpoints(normalizedSpec);
2072
+ const tags = collectTags(normalizedSpec, endpoints);
2073
+ const fullStage = {
2074
+ includeExamples: true,
2075
+ collapsedTags: /* @__PURE__ */ new Set(),
2076
+ indexOnly: false
2077
+ };
2078
+ if (options.maxTokens === void 0) {
2079
+ return renderDigest(normalizedSpec, sourceUrl, endpoints, tags, fullStage);
2080
+ }
2081
+ const worst = (markdown2) => {
2082
+ const report = estimateTokens(markdown2);
2083
+ return Math.max(...Object.values(report.models).map((model) => model.tokens));
2084
+ };
2085
+ const fits = (markdown2) => worst(markdown2) <= options.maxTokens;
2086
+ const full = renderDigest(normalizedSpec, sourceUrl, endpoints, tags, fullStage);
2087
+ if (fits(full)) return full;
2088
+ const stages = buildTrimStages(endpoints, tags);
2089
+ for (let i = 1; i < stages.length; i += 1) {
2090
+ const stage = stages[i];
2091
+ const markdown2 = renderDigest(normalizedSpec, sourceUrl, endpoints, tags, stage);
2092
+ if (fits(markdown2)) {
2093
+ return appendTrimNote(
2094
+ markdown2,
2095
+ stages.slice(1, i + 1).map((entry) => entry.action).filter((action) => Boolean(action)),
2096
+ options.maxTokens
2097
+ );
2098
+ }
2099
+ }
2100
+ const last = stages[stages.length - 1];
2101
+ const markdown = renderDigest(normalizedSpec, sourceUrl, endpoints, tags, last);
2102
+ const allActions = stages.slice(1).map((stage) => stage.action).filter((action) => Boolean(action));
2103
+ return appendTrimNote(markdown, allActions, options.maxTokens);
2104
+ }
2105
+
2106
+ // src/writer.ts
2107
+ import { mkdir, writeFile } from "fs/promises";
2108
+ import os from "os";
2109
+ import path from "path";
2110
+ function defaultOutputDirectory() {
2111
+ return path.join(os.homedir(), "Downloads");
2112
+ }
2113
+ function defaultFilename(apiTitle) {
2114
+ const slug = (apiTitle ?? "").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2115
+ return `${slug || "api"}-digest.md`;
2116
+ }
2117
+ function resolveOutputDirectory(directory, cwd = process.cwd()) {
2118
+ const expanded = directory === "~" ? os.homedir() : directory.startsWith(`~${path.sep}`) ? path.join(os.homedir(), directory.slice(2)) : directory;
2119
+ return path.resolve(cwd, expanded);
2120
+ }
2121
+ function normalizeFilename(filename, fallback = "api-digest.md") {
2122
+ const normalized = filename.trim().replace(/\\/g, "/");
2123
+ const basename = path.posix.basename(normalized);
2124
+ if (!basename || basename === "." || basename === "..") return fallback;
2125
+ return basename.toLowerCase().endsWith(".md") ? basename : `${basename}.md`;
2126
+ }
2127
+ async function writeDigest(options) {
2128
+ const directory = resolveOutputDirectory(options.directory);
2129
+ const filename = normalizeFilename(options.filename);
2130
+ const outputPath = path.join(directory, filename);
2131
+ await mkdir(directory, { recursive: true });
2132
+ await writeFile(outputPath, options.markdown, { encoding: "utf8" });
2133
+ return outputPath;
2134
+ }
2135
+
2136
+ export {
2137
+ VERSION,
2138
+ toTypeScriptIdentifier,
2139
+ isOpenApiSpec,
2140
+ parseSpecText,
2141
+ fetchOpenApiSpec,
2142
+ estimateTokens,
2143
+ transformOpenApiToMarkdown,
2144
+ defaultOutputDirectory,
2145
+ defaultFilename,
2146
+ resolveOutputDirectory,
2147
+ normalizeFilename,
2148
+ writeDigest
2149
+ };