swagger-digest 0.1.1 → 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.
- package/LICENSE +2 -2
- package/README.md +85 -1
- package/dist/{chunk-UZQM2SHL.js → chunk-CBSDTIKN.js} +1266 -529
- package/dist/cli.js +144 -8
- package/dist/index.d.ts +44 -4
- package/dist/index.js +1 -1
- package/package.json +9 -11
|
@@ -1,6 +1,504 @@
|
|
|
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
|
+
|
|
1
133
|
// src/fetcher.ts
|
|
2
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
|
|
3
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
|
|
4
502
|
var SPEC_FILE_EXTENSION = /\.(?:json|ya?ml)$/i;
|
|
5
503
|
var FALLBACK_PATHS = ["/v3/api-docs", "/swagger/v1/swagger.json", "/swagger.json", "/openapi.json", "/api-docs"];
|
|
6
504
|
function isRecord(value) {
|
|
@@ -14,7 +512,7 @@ function isOpenApiSpec(value) {
|
|
|
14
512
|
return hasVersion && hasInfo && hasPaths;
|
|
15
513
|
}
|
|
16
514
|
function parseSpecText(text, source = "response") {
|
|
17
|
-
const parsers = [() => JSON.parse(text), () =>
|
|
515
|
+
const parsers = [() => JSON.parse(text), () => parseYaml2(text)];
|
|
18
516
|
const errors = [];
|
|
19
517
|
for (const parser of parsers) {
|
|
20
518
|
try {
|
|
@@ -30,62 +528,164 @@ function parseSpecText(text, source = "response") {
|
|
|
30
528
|
function isDirectSpecUrl(url) {
|
|
31
529
|
return SPEC_FILE_EXTENSION.test(url.pathname);
|
|
32
530
|
}
|
|
33
|
-
async function fetchText(url,
|
|
34
|
-
const response = await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
41
540
|
});
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`HTTP ${response.status} ${response.statusText} for ${url}`);
|
|
44
|
-
}
|
|
45
541
|
return {
|
|
46
|
-
text:
|
|
47
|
-
contentType: response.
|
|
542
|
+
text: response.text,
|
|
543
|
+
contentType: response.contentType
|
|
48
544
|
};
|
|
49
545
|
}
|
|
50
546
|
function extractScripts(html) {
|
|
51
547
|
const $ = load(html);
|
|
52
548
|
const inline = [];
|
|
53
549
|
const external = [];
|
|
54
|
-
$("script").
|
|
550
|
+
for (const element of $("script").toArray()) {
|
|
55
551
|
const body = $(element).html();
|
|
56
552
|
if (body?.trim()) inline.push(body);
|
|
57
553
|
const src = $(element).attr("src");
|
|
58
554
|
if (src?.trim()) external.push(src.trim());
|
|
59
|
-
}
|
|
555
|
+
}
|
|
60
556
|
return { inline, external };
|
|
61
557
|
}
|
|
62
558
|
function extractSpecUrlFromScripts(scripts, pageUrl) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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;
|
|
69
566
|
try {
|
|
70
|
-
|
|
567
|
+
const resolved = new URL(trimmed, pageUrl).toString();
|
|
568
|
+
if (!found.includes(resolved)) found.push(resolved);
|
|
71
569
|
} catch {
|
|
72
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
|
+
}
|
|
73
644
|
}
|
|
74
645
|
return void 0;
|
|
75
646
|
}
|
|
76
|
-
function
|
|
77
|
-
const
|
|
78
|
-
if (fromScript) return fromScript;
|
|
647
|
+
function extractSpecUrls(scripts, pageUrl) {
|
|
648
|
+
const fromScripts = extractSpecUrlsFromScripts(scripts, pageUrl);
|
|
79
649
|
const queryUrl = pageUrl.searchParams.get("url");
|
|
80
650
|
if (queryUrl) {
|
|
81
651
|
try {
|
|
82
|
-
|
|
652
|
+
const resolved = new URL(queryUrl, pageUrl).toString();
|
|
653
|
+
if (!fromScripts.includes(resolved)) fromScripts.push(resolved);
|
|
83
654
|
} catch {
|
|
84
655
|
}
|
|
85
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
|
+
}
|
|
86
686
|
return void 0;
|
|
87
687
|
}
|
|
88
|
-
async function fetchExternalConfigScripts(srcs, pageUrl,
|
|
688
|
+
async function fetchExternalConfigScripts(srcs, pageUrl, ctx, errors) {
|
|
89
689
|
const candidates = srcs.map((src) => {
|
|
90
690
|
try {
|
|
91
691
|
return new URL(src, pageUrl);
|
|
@@ -100,7 +700,7 @@ async function fetchExternalConfigScripts(srcs, pageUrl, timeoutMs, errors) {
|
|
|
100
700
|
const scripts = [];
|
|
101
701
|
for (const candidate of candidates.slice(0, 10)) {
|
|
102
702
|
try {
|
|
103
|
-
const response = await fetchText(candidate.toString(),
|
|
703
|
+
const response = await fetchText(candidate.toString(), ctx);
|
|
104
704
|
scripts.push(response.text);
|
|
105
705
|
if (extractSpecUrlFromScripts(scripts, pageUrl) || scripts.some((script) => findObjectLiteralAfterKey(script, "spec"))) {
|
|
106
706
|
break;
|
|
@@ -195,7 +795,7 @@ function findObjectLiteralAfterKey(source, key) {
|
|
|
195
795
|
function parseInlineObjectLiteral(literal) {
|
|
196
796
|
const candidates = [
|
|
197
797
|
() => JSON.parse(literal),
|
|
198
|
-
() =>
|
|
798
|
+
() => parseYaml2(literal),
|
|
199
799
|
// Swagger config often uses JS object syntax with trailing commas.
|
|
200
800
|
() => JSON.parse(literal.replace(/,\s*([}\]])/g, "$1"))
|
|
201
801
|
];
|
|
@@ -217,11 +817,26 @@ function extractInlineSpec(scripts) {
|
|
|
217
817
|
}
|
|
218
818
|
return void 0;
|
|
219
819
|
}
|
|
220
|
-
async function tryFetchSpec(url,
|
|
221
|
-
const response = await fetchText(url,
|
|
222
|
-
|
|
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;
|
|
223
838
|
}
|
|
224
|
-
async function tryBrowserFallback(pageUrl) {
|
|
839
|
+
async function tryBrowserFallback(pageUrl, headers) {
|
|
225
840
|
let playwright;
|
|
226
841
|
try {
|
|
227
842
|
const optionalModuleName = "playwright";
|
|
@@ -234,6 +849,20 @@ async function tryBrowserFallback(pageUrl) {
|
|
|
234
849
|
const browser = await playwright.chromium.launch({ headless: true });
|
|
235
850
|
try {
|
|
236
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
|
+
}
|
|
237
866
|
await page.goto(pageUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
238
867
|
await page.waitForFunction(
|
|
239
868
|
() => {
|
|
@@ -241,7 +870,8 @@ async function tryBrowserFallback(pageUrl) {
|
|
|
241
870
|
try {
|
|
242
871
|
const candidate = w.ui?.spec?.();
|
|
243
872
|
const document = candidate && typeof candidate.toJSON === "function" ? candidate.toJSON() : candidate;
|
|
244
|
-
|
|
873
|
+
const doc = document;
|
|
874
|
+
return Boolean(doc?.openapi || doc?.swagger || w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
|
|
245
875
|
} catch {
|
|
246
876
|
return Boolean(w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
|
|
247
877
|
}
|
|
@@ -286,18 +916,42 @@ async function fetchOpenApiSpec(input, options = {}) {
|
|
|
286
916
|
throw new Error("Only HTTP and HTTPS URLs are supported.");
|
|
287
917
|
}
|
|
288
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
|
+
};
|
|
289
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
|
+
};
|
|
290
936
|
if (isDirectSpecUrl(pageUrl)) {
|
|
291
937
|
try {
|
|
292
|
-
const spec = await tryFetchSpec(pageUrl.toString(),
|
|
938
|
+
const spec = await tryFetchSpec(pageUrl.toString(), ctx);
|
|
293
939
|
return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "direct-spec" };
|
|
294
940
|
} catch (error) {
|
|
295
|
-
|
|
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);
|
|
296
950
|
}
|
|
297
951
|
}
|
|
298
952
|
let html;
|
|
299
953
|
try {
|
|
300
|
-
const response = await fetchText(pageUrl.toString(),
|
|
954
|
+
const response = await fetchText(pageUrl.toString(), ctx);
|
|
301
955
|
try {
|
|
302
956
|
const spec = parseSpecText(response.text, pageUrl.toString());
|
|
303
957
|
return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "direct-spec" };
|
|
@@ -305,35 +959,29 @@ async function fetchOpenApiSpec(input, options = {}) {
|
|
|
305
959
|
html = response.text;
|
|
306
960
|
}
|
|
307
961
|
} catch (error) {
|
|
308
|
-
|
|
962
|
+
recordFailure("Swagger UI page", error);
|
|
309
963
|
}
|
|
310
964
|
if (html !== void 0) {
|
|
311
965
|
const pageScripts = extractScripts(html);
|
|
312
|
-
|
|
313
|
-
let specUrl = extractSpecUrl(scripts, pageUrl);
|
|
966
|
+
const scripts = pageScripts.inline;
|
|
314
967
|
const inlineSpec = extractInlineSpec(scripts);
|
|
315
968
|
if (inlineSpec) {
|
|
316
969
|
return { spec: inlineSpec, sourceUrl: pageUrl.toString(), retrievalMethod: "inline-spec" };
|
|
317
970
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
errors.push(`Configured spec (${specUrl}): ${error instanceof Error ? error.message : String(error)}`);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
const externalScripts = await fetchExternalConfigScripts(pageScripts.external, pageUrl, timeoutMs, errors);
|
|
327
|
-
scripts = [...scripts, ...externalScripts];
|
|
328
|
-
specUrl = extractSpecUrl(scripts, pageUrl);
|
|
329
|
-
if (specUrl && specUrl !== pageUrl.searchParams.get("url")) {
|
|
330
|
-
try {
|
|
331
|
-
const spec = await tryFetchSpec(specUrl, timeoutMs);
|
|
332
|
-
return { spec, sourceUrl: specUrl, retrievalMethod: "html-config" };
|
|
333
|
-
} catch (error) {
|
|
334
|
-
errors.push(`Configured spec (${specUrl}): ${error instanceof Error ? error.message : String(error)}`);
|
|
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
|
+
);
|
|
335
976
|
}
|
|
336
|
-
}
|
|
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;
|
|
337
985
|
for (const script of scripts) {
|
|
338
986
|
const literal = findObjectLiteralAfterKey(script, "spec");
|
|
339
987
|
if (!literal) continue;
|
|
@@ -341,17 +989,33 @@ async function fetchOpenApiSpec(input, options = {}) {
|
|
|
341
989
|
if (spec) return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "inline-spec" };
|
|
342
990
|
}
|
|
343
991
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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);
|
|
350
1014
|
}
|
|
351
1015
|
}
|
|
352
1016
|
if (options.useBrowserFallback !== false) {
|
|
353
1017
|
try {
|
|
354
|
-
const spec = await tryBrowserFallback(pageUrl.toString());
|
|
1018
|
+
const spec = await tryBrowserFallback(pageUrl.toString(), ctx.headers);
|
|
355
1019
|
return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "headless-browser" };
|
|
356
1020
|
} catch (error) {
|
|
357
1021
|
errors.push(`Headless browser: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -366,171 +1030,147 @@ Check that the URL is reachable from this terminal and points to a Swagger UI pa
|
|
|
366
1030
|
);
|
|
367
1031
|
}
|
|
368
1032
|
|
|
369
|
-
// src/
|
|
370
|
-
var
|
|
371
|
-
|
|
372
|
-
"
|
|
373
|
-
"
|
|
374
|
-
"
|
|
375
|
-
"
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return typeof value === "string" ? value.trim().replace(/\s+/g, " ") : "";
|
|
395
|
-
}
|
|
396
|
-
function markdownCell(value) {
|
|
397
|
-
return cleanText(value).replace(/\|/g, "\\|").replace(/`/g, "\\`") || "\u2014";
|
|
398
|
-
}
|
|
399
|
-
function safeSourceUrl(value) {
|
|
400
|
-
try {
|
|
401
|
-
const url = new URL(value);
|
|
402
|
-
url.username = "";
|
|
403
|
-
url.password = "";
|
|
404
|
-
for (const key of [...url.searchParams.keys()]) {
|
|
405
|
-
if (/(token|secret|password|signature|credential|api[-_]?key|authorization|auth)/i.test(key)) {
|
|
406
|
-
url.searchParams.set(key, "[redacted]");
|
|
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)
|
|
407
1058
|
}
|
|
408
|
-
}
|
|
409
|
-
return url.toString();
|
|
410
|
-
} catch {
|
|
411
|
-
return value;
|
|
1059
|
+
};
|
|
412
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
|
+
};
|
|
413
1084
|
}
|
|
414
|
-
|
|
415
|
-
|
|
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;
|
|
416
1105
|
}
|
|
417
|
-
function
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const metadata = [];
|
|
421
|
-
if (cleanText(schema.description)) metadata.push(cleanText(schema.description));
|
|
422
|
-
if (cleanText(schema.format)) metadata.push(`format: ${cleanText(schema.format)}`);
|
|
423
|
-
const constraints = [
|
|
424
|
-
["default", "default"],
|
|
425
|
-
["pattern", "pattern"],
|
|
426
|
-
["minLength", "minLength"],
|
|
427
|
-
["maxLength", "maxLength"],
|
|
428
|
-
["minimum", "minimum"],
|
|
429
|
-
["maximum", "maximum"],
|
|
430
|
-
["exclusiveMinimum", "exclusiveMinimum"],
|
|
431
|
-
["exclusiveMaximum", "exclusiveMaximum"],
|
|
432
|
-
["minItems", "minItems"],
|
|
433
|
-
["maxItems", "maxItems"],
|
|
434
|
-
["uniqueItems", "uniqueItems"],
|
|
435
|
-
["multipleOf", "multipleOf"]
|
|
436
|
-
];
|
|
437
|
-
for (const [property, label] of constraints) {
|
|
438
|
-
const value = schema[property];
|
|
439
|
-
if (value === void 0) continue;
|
|
440
|
-
if (property === "default" && (value === null || value === "")) continue;
|
|
441
|
-
metadata.push(
|
|
442
|
-
`${label}: ${property === "default" ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value)}`
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
if (schema.readOnly === true) metadata.push("read-only");
|
|
446
|
-
if (schema.writeOnly === true) metadata.push("write-only");
|
|
447
|
-
if (schema.deprecated === true) metadata.push("deprecated");
|
|
448
|
-
return [...new Set(metadata)];
|
|
1106
|
+
function getSecuritySchemes(spec) {
|
|
1107
|
+
const dictionary = spec.components?.securitySchemes ?? spec.securityDefinitions ?? {};
|
|
1108
|
+
return entries(dictionary).filter((entry) => isObject(entry[1]));
|
|
449
1109
|
}
|
|
450
|
-
function
|
|
451
|
-
|
|
452
|
-
if (!
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
"
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
"
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
"
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
"do",
|
|
473
|
-
"else",
|
|
474
|
-
"enum",
|
|
475
|
-
"export",
|
|
476
|
-
"extends",
|
|
477
|
-
"false",
|
|
478
|
-
"finally",
|
|
479
|
-
"for",
|
|
480
|
-
"from",
|
|
481
|
-
"function",
|
|
482
|
-
"get",
|
|
483
|
-
"if",
|
|
484
|
-
"implements",
|
|
485
|
-
"import",
|
|
486
|
-
"in",
|
|
487
|
-
"infer",
|
|
488
|
-
"instanceof",
|
|
489
|
-
"interface",
|
|
490
|
-
"is",
|
|
491
|
-
"keyof",
|
|
492
|
-
"let",
|
|
493
|
-
"module",
|
|
494
|
-
"namespace",
|
|
495
|
-
"never",
|
|
496
|
-
"new",
|
|
497
|
-
"null",
|
|
498
|
-
"number",
|
|
499
|
-
"object",
|
|
500
|
-
"of",
|
|
501
|
-
"package",
|
|
502
|
-
"private",
|
|
503
|
-
"protected",
|
|
504
|
-
"public",
|
|
505
|
-
"readonly",
|
|
506
|
-
"require",
|
|
507
|
-
"return",
|
|
508
|
-
"set",
|
|
509
|
-
"static",
|
|
510
|
-
"string",
|
|
511
|
-
"super",
|
|
512
|
-
"switch",
|
|
513
|
-
"symbol",
|
|
514
|
-
"this",
|
|
515
|
-
"throw",
|
|
516
|
-
"true",
|
|
517
|
-
"try",
|
|
518
|
-
"type",
|
|
519
|
-
"typeof",
|
|
520
|
-
"undefined",
|
|
521
|
-
"unique",
|
|
522
|
-
"unknown",
|
|
523
|
-
"var",
|
|
524
|
-
"void",
|
|
525
|
-
"while",
|
|
526
|
-
"with",
|
|
527
|
-
"yield"
|
|
528
|
-
]);
|
|
529
|
-
if (reserved.has(identifier)) identifier = `${identifier}Model`;
|
|
530
|
-
return identifier;
|
|
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
|
+
];
|
|
531
1132
|
}
|
|
532
|
-
|
|
533
|
-
|
|
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)];
|
|
534
1174
|
}
|
|
535
1175
|
function refName(ref) {
|
|
536
1176
|
const parts = ref.split("/");
|
|
@@ -635,80 +1275,7 @@ function getSchemaDictionary(spec) {
|
|
|
635
1275
|
const schemas = spec.components?.schemas ?? spec.definitions ?? {};
|
|
636
1276
|
return isObject(schemas) ? schemas : {};
|
|
637
1277
|
}
|
|
638
|
-
function
|
|
639
|
-
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
640
|
-
seen.add(value);
|
|
641
|
-
if (Array.isArray(value)) {
|
|
642
|
-
for (const item of value) collectSchemaRefs(item, output, root, seen, followedRefs);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const object = value;
|
|
646
|
-
if (typeof object.$ref === "string") {
|
|
647
|
-
if (isSchemaRef(object.$ref)) {
|
|
648
|
-
const name = refName(object.$ref);
|
|
649
|
-
if (name) output.add(name);
|
|
650
|
-
} else if (root && object.$ref.startsWith("#/") && !followedRefs.has(object.$ref)) {
|
|
651
|
-
followedRefs.add(object.$ref);
|
|
652
|
-
const target = resolvePointer(root, object.$ref);
|
|
653
|
-
if (target !== void 0) collectSchemaRefs(target, output, root, seen, followedRefs);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
for (const [key, child] of Object.entries(object)) {
|
|
657
|
-
if (key !== "example" && key !== "examples" && key !== "externalDocs")
|
|
658
|
-
collectSchemaRefs(child, output, root, seen, followedRefs);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
function referencedSchemas(spec) {
|
|
662
|
-
const dictionary = getSchemaDictionary(spec);
|
|
663
|
-
const knownByName = /* @__PURE__ */ new Map();
|
|
664
|
-
for (const [name, schema] of Object.entries(dictionary)) {
|
|
665
|
-
knownByName.set(toTypeScriptIdentifier(name), [name, schema]);
|
|
666
|
-
}
|
|
667
|
-
const refs = /* @__PURE__ */ new Set();
|
|
668
|
-
collectSchemaRefs(spec.paths ?? {}, refs, spec);
|
|
669
|
-
const selected = /* @__PURE__ */ new Set();
|
|
670
|
-
const queue = [...refs];
|
|
671
|
-
while (queue.length) {
|
|
672
|
-
const name = queue.shift();
|
|
673
|
-
if (!name || selected.has(name)) continue;
|
|
674
|
-
selected.add(name);
|
|
675
|
-
const model = knownByName.get(name);
|
|
676
|
-
if (!model) continue;
|
|
677
|
-
const childRefs = /* @__PURE__ */ new Set();
|
|
678
|
-
collectSchemaRefs(model[1], childRefs, spec);
|
|
679
|
-
for (const child of childRefs) if (!selected.has(child)) queue.push(child);
|
|
680
|
-
}
|
|
681
|
-
return [...selected].map((name) => knownByName.get(name)).filter((entry) => Boolean(entry));
|
|
682
|
-
}
|
|
683
|
-
function modelDeclaration(name, schema) {
|
|
684
|
-
const identifier = toTypeScriptIdentifier(name);
|
|
685
|
-
const description = safeComment(schema.description);
|
|
686
|
-
const comment = description ? `/** ${description} */
|
|
687
|
-
` : "";
|
|
688
|
-
if (schema.$ref) return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
|
|
689
|
-
if (Array.isArray(schema.enum) || schema.const !== void 0 || schema.oneOf || schema.anyOf || schema.allOf) {
|
|
690
|
-
return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
|
|
691
|
-
}
|
|
692
|
-
const kind = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
|
|
693
|
-
if (kind === "object" || schema.properties || schema.additionalProperties !== void 0) {
|
|
694
|
-
const properties = entries(schema.properties);
|
|
695
|
-
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
696
|
-
const fields = properties.map(([field, value]) => {
|
|
697
|
-
const metadata = schemaMetadata(value).map(safeComment).filter(Boolean).join("; ");
|
|
698
|
-
const doc = metadata ? ` /** ${metadata} */
|
|
699
|
-
` : "";
|
|
700
|
-
return `${doc} ${propertyKey(field)}${required.has(field) ? "" : "?"}: ${typeExpression(value)};`;
|
|
701
|
-
});
|
|
702
|
-
if (schema.additionalProperties === true) fields.push(" [key: string]: unknown;");
|
|
703
|
-
else if (isObject(schema.additionalProperties))
|
|
704
|
-
fields.push(` [key: string]: ${typeExpression(schema.additionalProperties)};`);
|
|
705
|
-
return `${comment}export interface ${identifier} {
|
|
706
|
-
${fields.join("\n")}
|
|
707
|
-
}`;
|
|
708
|
-
}
|
|
709
|
-
return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
|
|
710
|
-
}
|
|
711
|
-
function resolvePointer(root, ref) {
|
|
1278
|
+
function resolvePointer2(root, ref) {
|
|
712
1279
|
if (!ref.startsWith("#/")) return void 0;
|
|
713
1280
|
let current = root;
|
|
714
1281
|
for (const rawPart of ref.slice(2).split("/")) {
|
|
@@ -723,7 +1290,7 @@ function resolveReferenceObject(spec, value, seen = /* @__PURE__ */ new Set()) {
|
|
|
723
1290
|
if (typeof value.$ref !== "string") return value;
|
|
724
1291
|
const ref = value.$ref;
|
|
725
1292
|
if (seen.has(ref)) return {};
|
|
726
|
-
const target =
|
|
1293
|
+
const target = resolvePointer2(spec, ref);
|
|
727
1294
|
if (!isObject(target)) return value;
|
|
728
1295
|
const nextSeen = new Set(seen).add(ref);
|
|
729
1296
|
return {
|
|
@@ -731,112 +1298,8 @@ function resolveReferenceObject(spec, value, seen = /* @__PURE__ */ new Set()) {
|
|
|
731
1298
|
...Object.fromEntries(Object.entries(value).filter(([key]) => key !== "$ref"))
|
|
732
1299
|
};
|
|
733
1300
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const paths = isObject(spec.paths) ? spec.paths : {};
|
|
737
|
-
for (const [path2, pathItemValue] of Object.entries(paths)) {
|
|
738
|
-
if (!isObject(pathItemValue)) continue;
|
|
739
|
-
const pathParameters = Array.isArray(pathItemValue.parameters) ? pathItemValue.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
|
|
740
|
-
for (const method of HTTP_METHODS) {
|
|
741
|
-
const operationValue = pathItemValue[method];
|
|
742
|
-
if (!isObject(operationValue)) continue;
|
|
743
|
-
const firstTag = Array.isArray(operationValue.tags) && operationValue.tags.length ? String(operationValue.tags[0]) : "General";
|
|
744
|
-
result.push({ method, path: path2, operation: operationValue, pathParameters, tag: firstTag });
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
return result;
|
|
748
|
-
}
|
|
749
|
-
function getServers(spec) {
|
|
750
|
-
if (Array.isArray(spec.servers) && spec.servers.length) {
|
|
751
|
-
return spec.servers.map((server) => cleanText(server?.url)).filter(Boolean);
|
|
752
|
-
}
|
|
753
|
-
if (isObject(spec)) {
|
|
754
|
-
const host = cleanText(spec.host);
|
|
755
|
-
if (host) {
|
|
756
|
-
const schemes = Array.isArray(spec.schemes) && spec.schemes.length ? spec.schemes : ["https"];
|
|
757
|
-
const basePath = cleanText(spec.basePath) || "/";
|
|
758
|
-
return schemes.map(
|
|
759
|
-
(scheme) => `${scheme}://${host}${basePath.startsWith("/") ? basePath : `/${basePath}`}`
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return [];
|
|
764
|
-
}
|
|
765
|
-
function describeSecurityScheme(name, scheme) {
|
|
766
|
-
const type = cleanText(scheme.type).toLowerCase();
|
|
767
|
-
const label = cleanText(scheme.description);
|
|
768
|
-
let details = name;
|
|
769
|
-
if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "bearer")
|
|
770
|
-
details += " (Bearer token via Authorization header)";
|
|
771
|
-
else if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "basic")
|
|
772
|
-
details += " (HTTP Basic via Authorization header)";
|
|
773
|
-
else if (type === "apikey" || type === "apiKey")
|
|
774
|
-
details += ` (API key in ${cleanText(scheme.in) || "header"}: ${cleanText(scheme.name) || "key"})`;
|
|
775
|
-
else if (type === "oauth2") {
|
|
776
|
-
const flows = entries(scheme.flows).map(([flowName]) => flowName).join(", ") || cleanText(scheme.flow);
|
|
777
|
-
details += ` (OAuth2${flows ? `; ${flows}` : ""})`;
|
|
778
|
-
} else if (type === "openidconnect" || type === "openIdConnect") details += " (OpenID Connect)";
|
|
779
|
-
else if (type === "basic") details += " (HTTP Basic)";
|
|
780
|
-
else if (type) details += ` (${type})`;
|
|
781
|
-
if (label) details += ` \u2014 ${label}`;
|
|
782
|
-
return details;
|
|
783
|
-
}
|
|
784
|
-
function getSecuritySchemes(spec) {
|
|
785
|
-
const dictionary = spec.components?.securitySchemes ?? spec.securityDefinitions ?? {};
|
|
786
|
-
return entries(dictionary).filter((entry) => isObject(entry[1]));
|
|
787
|
-
}
|
|
788
|
-
function securityRequirement(spec, operation) {
|
|
789
|
-
const security = operation.security !== void 0 ? operation.security : spec.security;
|
|
790
|
-
if (!Array.isArray(security) || security.length === 0) return "None explicitly required";
|
|
791
|
-
const allSchemes = new Map(getSecuritySchemes(spec));
|
|
792
|
-
return security.map((requirement) => {
|
|
793
|
-
if (!isObject(requirement)) return "";
|
|
794
|
-
const names = Object.entries(requirement).map(([name, scopes]) => {
|
|
795
|
-
const label = allSchemes.has(name) ? name : `${name} (scheme not defined)`;
|
|
796
|
-
return Array.isArray(scopes) && scopes.length ? `${label} [scopes: ${scopes.join(", ")}]` : label;
|
|
797
|
-
});
|
|
798
|
-
return names.length ? names.join(" + ") : "No auth";
|
|
799
|
-
}).filter(Boolean).join(" OR ");
|
|
800
|
-
}
|
|
801
|
-
function mergedParameters(spec, endpoint) {
|
|
802
|
-
const operationParameters = Array.isArray(endpoint.operation.parameters) ? endpoint.operation.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
|
|
803
|
-
const byIdentity = /* @__PURE__ */ new Map();
|
|
804
|
-
for (const parameter of [...endpoint.pathParameters, ...operationParameters]) {
|
|
805
|
-
if (parameter.in === "body") continue;
|
|
806
|
-
const key = `${cleanText(parameter.in)}:${cleanText(parameter.name)}`;
|
|
807
|
-
if (key !== ":") byIdentity.set(key, parameter);
|
|
808
|
-
}
|
|
809
|
-
return [...byIdentity.values()];
|
|
810
|
-
}
|
|
811
|
-
function parameterType(parameter) {
|
|
812
|
-
if (isObject(parameter.schema)) return typeExpression(parameter.schema);
|
|
813
|
-
if (parameter.type === "array") return `${typeExpression(parameter.items)}[]`;
|
|
814
|
-
if (parameter.type) return typeExpression({ type: parameter.type, format: parameter.format, enum: parameter.enum });
|
|
815
|
-
return "unknown";
|
|
816
|
-
}
|
|
817
|
-
function parameterDisplayType(parameter) {
|
|
818
|
-
return parameterType(parameter).replace(/\/\*\s*([^*]+?)\s*\*\//g, "($1)");
|
|
819
|
-
}
|
|
820
|
-
function parameterDefault(parameter) {
|
|
821
|
-
if (parameter.default !== void 0) return parameter.default;
|
|
822
|
-
if (isObject(parameter.schema)) return parameter.schema.default;
|
|
823
|
-
return void 0;
|
|
824
|
-
}
|
|
825
|
-
function isMeaningfulDefault(value) {
|
|
826
|
-
return value !== void 0 && value !== null && value !== "";
|
|
827
|
-
}
|
|
828
|
-
function parameterList(parameters) {
|
|
829
|
-
if (!parameters.length) return "_None._";
|
|
830
|
-
return parameters.map((parameter) => {
|
|
831
|
-
const required = parameter.required === true || parameter.in === "path";
|
|
832
|
-
const type = parameterDisplayType(parameter);
|
|
833
|
-
const defaultValue = parameterDefault(parameter);
|
|
834
|
-
const defaultSuffix = isMeaningfulDefault(defaultValue) ? ` (default: ${typeof defaultValue === "string" ? JSON.stringify(defaultValue) : String(defaultValue)})` : "";
|
|
835
|
-
const description = cleanText(parameter.description);
|
|
836
|
-
const details = description ? ` \u2014 ${description}` : "";
|
|
837
|
-
return `- \`${cleanText(parameter.name)}\` (\`${cleanText(parameter.in)}\`${required ? ", required" : ""}) \xB7 \`${type}\`${defaultSuffix}${details}`;
|
|
838
|
-
}).join("\n");
|
|
839
|
-
}
|
|
1301
|
+
|
|
1302
|
+
// src/transformer/examples.ts
|
|
840
1303
|
function chooseMediaType(content) {
|
|
841
1304
|
const mediaTypes = Object.keys(content);
|
|
842
1305
|
return mediaTypes.find((type) => type.toLowerCase() === "application/json") ?? mediaTypes.find((type) => type.toLowerCase().includes("+json")) ?? mediaTypes[0];
|
|
@@ -849,7 +1312,7 @@ function firstDeclaredExample(spec, schemaValue, seen = /* @__PURE__ */ new Set(
|
|
|
849
1312
|
let schema = schemaValue;
|
|
850
1313
|
if (typeof schema.$ref === "string") {
|
|
851
1314
|
if (seen.has(schema.$ref)) return void 0;
|
|
852
|
-
const target =
|
|
1315
|
+
const target = resolvePointer2(spec, schema.$ref);
|
|
853
1316
|
if (!isObject(target)) return void 0;
|
|
854
1317
|
const nextSeen = new Set(seen).add(schema.$ref);
|
|
855
1318
|
schema = { ...target, ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$ref")) };
|
|
@@ -866,7 +1329,7 @@ function generateSchemaExample(spec, schemaValue, depth = 0, seenRefs = /* @__PU
|
|
|
866
1329
|
let nextSeen = seenRefs;
|
|
867
1330
|
if (typeof schema.$ref === "string") {
|
|
868
1331
|
if (seenRefs.has(schema.$ref)) return null;
|
|
869
|
-
const target =
|
|
1332
|
+
const target = resolvePointer2(spec, schema.$ref);
|
|
870
1333
|
if (!isObject(target)) return void 0;
|
|
871
1334
|
nextSeen = new Set(seenRefs).add(schema.$ref);
|
|
872
1335
|
schema = { ...target, ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$ref")) };
|
|
@@ -946,25 +1409,76 @@ function generateSchemaExample(spec, schemaValue, depth = 0, seenRefs = /* @__PU
|
|
|
946
1409
|
if (schema.properties || schema.additionalProperties !== void 0) return {};
|
|
947
1410
|
return void 0;
|
|
948
1411
|
}
|
|
949
|
-
function payloadExample(spec, mediaValue, schemaValue, legacyExamples) {
|
|
950
|
-
const media = isObject(mediaValue) ? mediaValue : {};
|
|
951
|
-
if (hasOwn(media, "example")) return { value: media.example, origin: "declared" };
|
|
952
|
-
for (const [name, exampleValue] of entries(media.examples)) {
|
|
953
|
-
const example = resolveReferenceObject(spec, exampleValue);
|
|
954
|
-
if (hasOwn(example, "value")) return { value: example.value, origin: "declared", name };
|
|
955
|
-
}
|
|
956
|
-
if (isObject(legacyExamples)) {
|
|
957
|
-
const mediaType = Object.keys(legacyExamples).find((key) => key.toLowerCase() === "application/json") ?? Object.keys(legacyExamples)[0];
|
|
958
|
-
if (mediaType && hasOwn(legacyExamples, mediaType)) {
|
|
959
|
-
return { value: legacyExamples[mediaType], origin: "declared" };
|
|
960
|
-
}
|
|
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)._`;
|
|
961
1470
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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");
|
|
966
1480
|
}
|
|
967
|
-
function requestPayloadDescription(spec, operation) {
|
|
1481
|
+
function requestPayloadDescription(spec, operation, style = FULL_STYLE) {
|
|
968
1482
|
if (isObject(operation.requestBody) || operation.requestBody?.$ref) {
|
|
969
1483
|
const body = resolveReferenceObject(spec, operation.requestBody);
|
|
970
1484
|
const content = isObject(body.content) ? body.content : {};
|
|
@@ -974,7 +1488,7 @@ function requestPayloadDescription(spec, operation) {
|
|
|
974
1488
|
return {
|
|
975
1489
|
type: schema ? typeExpression(schema) : "unknown",
|
|
976
1490
|
mediaType: mediaType || "unknown media type",
|
|
977
|
-
example: media || schema ? payloadExample(spec, media, schema) : void 0,
|
|
1491
|
+
example: style.includeExamples && (media || schema) ? payloadExample(spec, media, schema) : void 0,
|
|
978
1492
|
required: body.required === true
|
|
979
1493
|
};
|
|
980
1494
|
}
|
|
@@ -985,13 +1499,31 @@ function requestPayloadDescription(spec, operation) {
|
|
|
985
1499
|
return {
|
|
986
1500
|
type: typeExpression(bodyParameter.schema),
|
|
987
1501
|
mediaType: consumes[0] ?? "application/json",
|
|
988
|
-
example: payloadExample(spec, void 0, bodyParameter.schema),
|
|
1502
|
+
example: style.includeExamples ? payloadExample(spec, void 0, bodyParameter.schema) : void 0,
|
|
989
1503
|
required: bodyParameter.required === true
|
|
990
1504
|
};
|
|
991
1505
|
}
|
|
992
1506
|
return void 0;
|
|
993
1507
|
}
|
|
994
|
-
|
|
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) {
|
|
995
1527
|
const response = resolveReferenceObject(spec, responseValue);
|
|
996
1528
|
if (isObject(response.content)) {
|
|
997
1529
|
const mediaType = chooseMediaType(response.content);
|
|
@@ -1000,30 +1532,26 @@ function responseSchema(spec, responseValue) {
|
|
|
1000
1532
|
return {
|
|
1001
1533
|
type: schema ? typeExpression(schema) : void 0,
|
|
1002
1534
|
mediaType,
|
|
1003
|
-
example: schema || media ? payloadExample(spec, media, schema, response.examples) : void 0
|
|
1535
|
+
example: style.includeExamples && (schema || media) ? payloadExample(spec, media, schema, response.examples) : void 0
|
|
1004
1536
|
};
|
|
1005
1537
|
}
|
|
1006
1538
|
if (isObject(response.schema)) {
|
|
1007
1539
|
return {
|
|
1008
1540
|
type: typeExpression(response.schema),
|
|
1009
1541
|
mediaType: "application/json",
|
|
1010
|
-
example: payloadExample(spec, void 0, response.schema, response.examples)
|
|
1542
|
+
example: style.includeExamples ? payloadExample(spec, void 0, response.schema, response.examples) : void 0
|
|
1011
1543
|
};
|
|
1012
1544
|
}
|
|
1013
1545
|
return {};
|
|
1014
1546
|
}
|
|
1015
|
-
function
|
|
1016
|
-
try {
|
|
1017
|
-
return JSON.stringify(value, null, 2) ?? "null";
|
|
1018
|
-
} catch {
|
|
1019
|
-
return "null";
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
function responseBlock(spec, code, responseValue) {
|
|
1547
|
+
function responseBlock(spec, code, responseValue, style = FULL_STYLE) {
|
|
1023
1548
|
const response = resolveReferenceObject(spec, responseValue);
|
|
1024
|
-
const description = cleanText(response.description);
|
|
1025
|
-
const payload = responseSchema(spec, responseValue);
|
|
1549
|
+
const description = style.collapsed ? "" : shortenDescription(cleanText(response.description), style.descriptionLimit);
|
|
1550
|
+
const payload = responseSchema(spec, responseValue, style);
|
|
1026
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
|
+
}
|
|
1027
1555
|
const lines = [`- **${markdownCell(code)}${description ? ` ${description}` : ""}**${suffix}`];
|
|
1028
1556
|
if (payload.example?.origin === "declared") {
|
|
1029
1557
|
const label = payload.example.name ? ` \xB7 ${payload.example.name}` : "";
|
|
@@ -1088,7 +1616,7 @@ function collapsibleErrorEntry(spec, code, responseValue) {
|
|
|
1088
1616
|
if (schemaValue === void 0) return `\`${code}\``;
|
|
1089
1617
|
return `\`${code}\` (\`${typeExpression(schemaValue)}\`)`;
|
|
1090
1618
|
}
|
|
1091
|
-
function responseSection(spec, operation) {
|
|
1619
|
+
function responseSection(spec, operation, style = FULL_STYLE) {
|
|
1092
1620
|
const responses = entries(operation.responses).filter(([, value]) => isObject(value));
|
|
1093
1621
|
if (!responses.length) return "_No responses declared._";
|
|
1094
1622
|
const success = responses.filter(([code]) => /^2\d\d$/.test(code));
|
|
@@ -1096,42 +1624,96 @@ function responseSection(spec, operation) {
|
|
|
1096
1624
|
const errors = responses.filter(
|
|
1097
1625
|
([code]) => (COMMON_ERROR_CODES.has(code) || /^([45])\d\d$/.test(code)) && !success.some(([successCode]) => successCode === code)
|
|
1098
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
|
+
}
|
|
1099
1632
|
const detailedErrors = [];
|
|
1100
1633
|
const collapsedErrors = [];
|
|
1101
1634
|
for (const [code, responseValue] of errors) {
|
|
1102
1635
|
const entry = collapsibleErrorEntry(spec, code, responseValue);
|
|
1103
|
-
if (entry === void 0) detailedErrors.push(responseBlock(spec, code, responseValue));
|
|
1636
|
+
if (entry === void 0) detailedErrors.push(responseBlock(spec, code, responseValue, style));
|
|
1104
1637
|
else collapsedErrors.push(entry);
|
|
1105
1638
|
}
|
|
1106
1639
|
const primarySuccess = success[0];
|
|
1107
|
-
const lines = primarySuccess ? [responseBlock(spec, primarySuccess[0], primarySuccess[1])] : [];
|
|
1640
|
+
const lines = primarySuccess ? [responseBlock(spec, primarySuccess[0], primarySuccess[1], style)] : [];
|
|
1108
1641
|
lines.push(...detailedErrors);
|
|
1109
1642
|
if (collapsedErrors.length) lines.push(`- **Errors:** ${collapsedErrors.join(", ")}`);
|
|
1110
1643
|
if (success.length > 1) {
|
|
1111
|
-
const others = success.slice(1).map(([code, response]) => responseBlock(spec, code, response));
|
|
1644
|
+
const others = success.slice(1).map(([code, response]) => responseBlock(spec, code, response, style));
|
|
1112
1645
|
lines.push("", "<details><summary>Other successful responses</summary>", "", ...others, "", "</details>");
|
|
1113
1646
|
}
|
|
1114
1647
|
if (!lines.length) {
|
|
1115
|
-
return responses.map(([code, response]) => responseBlock(spec, code, response)).join("\n\n");
|
|
1648
|
+
return responses.map(([code, response]) => responseBlock(spec, code, response, style)).join("\n\n");
|
|
1116
1649
|
}
|
|
1117
1650
|
return lines.join("\n");
|
|
1118
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
|
+
}
|
|
1119
1699
|
function shortOperationDescription(operation) {
|
|
1120
1700
|
return markdownCell(operation.summary || operation.operationId || operation.description || "\u2014");
|
|
1121
1701
|
}
|
|
1122
|
-
function endpointMarkdown(spec, endpoint) {
|
|
1702
|
+
function endpointMarkdown(spec, endpoint, style = FULL_STYLE) {
|
|
1123
1703
|
const { operation, method, path: path2 } = endpoint;
|
|
1124
1704
|
const heading = `#### \`${method.toUpperCase()} ${path2}\``;
|
|
1125
1705
|
const summary = cleanText(operation.summary);
|
|
1126
1706
|
const description = cleanText(operation.description);
|
|
1127
1707
|
const lines = [heading];
|
|
1128
|
-
if (summary) lines.push("", summary);
|
|
1129
|
-
if (description && description !== summary)
|
|
1708
|
+
if (summary) lines.push("", shortenDescription(summary, style.descriptionLimit));
|
|
1709
|
+
if (description && description !== summary) {
|
|
1710
|
+
lines.push("", shortenDescription(description, style.descriptionLimit));
|
|
1711
|
+
}
|
|
1130
1712
|
lines.push("", `**Authentication:** ${securityRequirement(spec, operation)}`);
|
|
1131
1713
|
const parameters = mergedParameters(spec, endpoint);
|
|
1132
|
-
const request = requestPayloadDescription(spec, operation);
|
|
1714
|
+
const request = requestPayloadDescription(spec, operation, style);
|
|
1133
1715
|
lines.push("", "**Parameters**");
|
|
1134
|
-
lines.push(parameterList(parameters));
|
|
1716
|
+
lines.push(parameterList(parameters, style));
|
|
1135
1717
|
if (request) {
|
|
1136
1718
|
lines.push(
|
|
1137
1719
|
"",
|
|
@@ -1149,31 +1731,83 @@ function endpointMarkdown(spec, endpoint) {
|
|
|
1149
1731
|
);
|
|
1150
1732
|
}
|
|
1151
1733
|
}
|
|
1152
|
-
lines.push("", "**Responses**", responseSection(spec, operation));
|
|
1734
|
+
lines.push("", "**Responses**", responseSection(spec, operation, style));
|
|
1153
1735
|
return lines.join("\n");
|
|
1154
1736
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
+
}
|
|
1164
1761
|
}
|
|
1165
|
-
function
|
|
1166
|
-
const
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
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));
|
|
1172
1783
|
}
|
|
1173
|
-
function
|
|
1174
|
-
|
|
1175
|
-
const
|
|
1176
|
-
|
|
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)};`;
|
|
1177
1811
|
}
|
|
1178
1812
|
function normalizeInlinePayloadSchemas(spec) {
|
|
1179
1813
|
const document = structuredClone(spec);
|
|
@@ -1267,26 +1901,123 @@ function normalizeInlinePayloadSchemas(spec) {
|
|
|
1267
1901
|
}
|
|
1268
1902
|
return document;
|
|
1269
1903
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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) {
|
|
1273
1986
|
const info = isObject(spec.info) ? spec.info : {};
|
|
1274
1987
|
const title = cleanText(info.title) || "API";
|
|
1275
1988
|
const version = cleanText(info.version) || "unspecified";
|
|
1276
1989
|
const openApiVersion = cleanText(spec.openapi) || (spec.swagger ? `Swagger ${spec.swagger}` : "OpenAPI");
|
|
1277
1990
|
const servers = getServers(spec);
|
|
1278
|
-
const endpoints = getEndpoints(spec);
|
|
1279
|
-
const tags = collectTags(spec, endpoints);
|
|
1280
1991
|
const indexRows = endpoints.map(
|
|
1281
1992
|
(endpoint) => `| **${endpoint.method.toUpperCase()}** | \`${markdownCell(endpoint.path)}\` | ${shortOperationDescription(endpoint.operation)} | ${markdownCell(Array.isArray(endpoint.operation.tags) ? endpoint.operation.tags.join(", ") : endpoint.tag)} |`
|
|
1282
1993
|
);
|
|
1994
|
+
const detailEndpoints = stage.indexOnly ? [] : endpoints.filter((endpoint) => !stage.collapsedTags.has(endpoint.tag));
|
|
1283
1995
|
const groupedEndpoints = tags.map((tag) => {
|
|
1284
1996
|
const members = endpoints.filter((endpoint) => endpoint.tag === tag);
|
|
1285
1997
|
if (!members.length) return "";
|
|
1286
|
-
|
|
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");
|
|
1287
2006
|
}).filter(Boolean);
|
|
1288
|
-
|
|
1289
|
-
|
|
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 ? [
|
|
1290
2021
|
"## Data types",
|
|
1291
2022
|
"",
|
|
1292
2023
|
"```typescript",
|
|
@@ -1309,62 +2040,67 @@ function transformOpenApiToMarkdown(spec, sourceUrl) {
|
|
|
1309
2040
|
"",
|
|
1310
2041
|
"| Method | Path | Summary | Tags |",
|
|
1311
2042
|
"|---|---|---|---|",
|
|
1312
|
-
...indexRows.length ? indexRows : ["| \u2014 | \u2014 | No operations declared | \u2014 |"]
|
|
1313
|
-
"",
|
|
1314
|
-
"## Endpoints",
|
|
1315
|
-
"",
|
|
1316
|
-
...groupedEndpoints.length ? groupedEndpoints : ["_No endpoints declared._"],
|
|
1317
|
-
...modelSection ? ["", modelSection] : []
|
|
2043
|
+
...indexRows.length ? indexRows : ["| \u2014 | \u2014 | No operations declared | \u2014 |"]
|
|
1318
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
|
+
}
|
|
1319
2056
|
return `${sections.join("\n").replace(/\n{3,}/g, "\n\n").trim()}
|
|
1320
2057
|
`;
|
|
1321
2058
|
}
|
|
2059
|
+
function appendTrimNote(markdown, actions, budget) {
|
|
2060
|
+
return `${markdown}
|
|
2061
|
+
## Trimmed
|
|
1322
2062
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
llama: zeroModel("Meta Llama (3.3 / 4)", 128e3)
|
|
1341
|
-
}
|
|
1342
|
-
};
|
|
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);
|
|
1343
2080
|
}
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
const baseTokens = wordCount * 1.18 + syntaxSymbols * 0.42 + whitespaces * 0.05;
|
|
1348
|
-
const calculate = (name, ratio, contextWindow) => {
|
|
1349
|
-
const tokens = Math.max(1, Math.round(baseTokens * ratio));
|
|
1350
|
-
const contextPercent = Number((tokens / contextWindow * 100).toFixed(1));
|
|
1351
|
-
return { name, tokens, contextWindow, contextPercent };
|
|
2081
|
+
const worst = (markdown2) => {
|
|
2082
|
+
const report = estimateTokens(markdown2);
|
|
2083
|
+
return Math.max(...Object.values(report.models).map((model) => model.tokens));
|
|
1352
2084
|
};
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
+
);
|
|
1366
2098
|
}
|
|
1367
|
-
}
|
|
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);
|
|
1368
2104
|
}
|
|
1369
2105
|
|
|
1370
2106
|
// src/writer.ts
|
|
@@ -1398,12 +2134,13 @@ async function writeDigest(options) {
|
|
|
1398
2134
|
}
|
|
1399
2135
|
|
|
1400
2136
|
export {
|
|
2137
|
+
VERSION,
|
|
2138
|
+
toTypeScriptIdentifier,
|
|
1401
2139
|
isOpenApiSpec,
|
|
1402
2140
|
parseSpecText,
|
|
1403
2141
|
fetchOpenApiSpec,
|
|
1404
|
-
toTypeScriptIdentifier,
|
|
1405
|
-
transformOpenApiToMarkdown,
|
|
1406
2142
|
estimateTokens,
|
|
2143
|
+
transformOpenApiToMarkdown,
|
|
1407
2144
|
defaultOutputDirectory,
|
|
1408
2145
|
defaultFilename,
|
|
1409
2146
|
resolveOutputDirectory,
|