@redocly/cli 2.41.2 → 2.43.0
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/README.md +15 -0
- package/THIRD_PARTY_NOTICES +3 -2
- package/lib/chunks/32XCOMOR.js +7 -0
- package/lib/chunks/3BCHPW6T.js +22 -0
- package/lib/chunks/3VZLNQK3.js +47 -0
- package/lib/chunks/4VF6MPJJ.js +11 -0
- package/lib/chunks/55GMSQDD.js +7 -0
- package/lib/chunks/5E3VKU5T.js +7 -0
- package/lib/chunks/AE6JGTY4.js +7 -0
- package/lib/chunks/C35TOMVY.js +8 -0
- package/lib/chunks/D5T6ETPZ.js +8 -0
- package/lib/chunks/DNOXLF53.js +8 -0
- package/lib/chunks/E36IFTLX.js +59 -0
- package/lib/chunks/ECZUPR2N.js +7 -0
- package/lib/chunks/EU6GURJZ.js +7 -0
- package/lib/chunks/HXYO6QS2.js +3132 -0
- package/lib/chunks/IW4HG6VD.js +19 -0
- package/lib/chunks/KIVPQZDC.js +31 -0
- package/lib/chunks/KLY5RV3W.js +7 -0
- package/lib/chunks/KM7RL6C4.js +2317 -0
- package/lib/chunks/L3YH7RCT.js +8 -0
- package/lib/chunks/NEWT5A6S.js +159 -0
- package/lib/chunks/NGPG6D6W.js +7 -0
- package/lib/chunks/O6MCQRCP.js +59 -0
- package/lib/chunks/O6PRCLSV.js +13 -0
- package/lib/chunks/OFAMJNQ6.js +7 -0
- package/lib/chunks/P26PQ2RU.js +7 -0
- package/lib/chunks/PFD7KGH7.js +93 -0
- package/lib/chunks/QFG7TTKG.js +22 -0
- package/lib/chunks/RFWIIJFG.js +7 -0
- package/lib/chunks/RS5PUOTT.js +18 -0
- package/lib/chunks/TMQJQANP.js +7 -0
- package/lib/chunks/UPPWQBGX.js +7 -0
- package/lib/chunks/UXH3ZSSV.js +9 -0
- package/lib/chunks/UYZXI3NO.js +7 -0
- package/lib/chunks/WNTLDT6L.js +184 -0
- package/lib/chunks/WZXA5XPB.js +7 -0
- package/lib/chunks/X6IKW6K4.js +7 -0
- package/lib/index.js +226 -13885
- package/package.json +1 -1
- package/lib/chunks/3CX2Z7JI.js +0 -496
- package/lib/chunks/3UUTQVCJ.js +0 -3614
- package/lib/chunks/4FB5ZIPP.js +0 -1133
- package/lib/chunks/5ILQMFXK.js +0 -59
- package/lib/chunks/7F5O43EU.js +0 -1756
- package/lib/chunks/7IX44JLO.js +0 -16
- package/lib/chunks/7MOL2QVN.js +0 -92635
- package/lib/chunks/A3VFVVHD.js +0 -25
- package/lib/chunks/A6JSOHCP.js +0 -41
- package/lib/chunks/AIT3U3JT.js +0 -22
- package/lib/chunks/ASYQPOQY.js +0 -681
- package/lib/chunks/BVP23YP7.js +0 -13374
- package/lib/chunks/FO6BDVGE.js +0 -25
- package/lib/chunks/GFU5KGSW.js +0 -92
- package/lib/chunks/ICPYP2LP.js +0 -401
- package/lib/chunks/KPMZLKQG.js +0 -79
- package/lib/chunks/KRMBH6JF.js +0 -17902
- package/lib/chunks/NLIKC7UP.js +0 -528
- package/lib/chunks/NW3XJZXQ.js +0 -175
- package/lib/chunks/P6UGG7F6.js +0 -414
- package/lib/chunks/RRC5IDC3.js +0 -2416
- package/lib/chunks/T6UZU3XM.js +0 -17
- package/lib/chunks/TS33ZDKX.js +0 -35
- package/lib/chunks/UUU33DK3.js +0 -125
- package/lib/chunks/VUDTNFRL.js +0 -42
- package/lib/chunks/VXIQEZPR.js +0 -2564
- package/lib/chunks/W7G5WVPB.js +0 -44
- package/lib/chunks/XB6C62FW.js +0 -18497
- package/lib/chunks/XHPY6UJF.js +0 -1638
- package/lib/chunks/Y6DTFCLS.js +0 -15
- package/lib/chunks/Z2664VV5.js +0 -39331
- package/lib/chunks/Z2I5YXYN.js +0 -20272
- package/lib/chunks/ZK3QRKL5.js +0 -129
package/lib/chunks/NLIKC7UP.js
DELETED
|
@@ -1,528 +0,0 @@
|
|
|
1
|
-
import { createRequire as __createRequire } from 'node:module';
|
|
2
|
-
const require = __createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
ValidationSession,
|
|
5
|
-
loadOpenApiIndex,
|
|
6
|
-
parseCsv,
|
|
7
|
-
renderReport
|
|
8
|
-
} from "./RRC5IDC3.js";
|
|
9
|
-
import {
|
|
10
|
-
createNormalizedExchange,
|
|
11
|
-
isJsonMime,
|
|
12
|
-
normalizeFsPath
|
|
13
|
-
} from "./3CX2Z7JI.js";
|
|
14
|
-
import {
|
|
15
|
-
AbortFlowError,
|
|
16
|
-
exitWithError
|
|
17
|
-
} from "./T6UZU3XM.js";
|
|
18
|
-
import {
|
|
19
|
-
require_undici
|
|
20
|
-
} from "./XB6C62FW.js";
|
|
21
|
-
import {
|
|
22
|
-
isPlainObject,
|
|
23
|
-
logger
|
|
24
|
-
} from "./Z2664VV5.js";
|
|
25
|
-
import "./Z2I5YXYN.js";
|
|
26
|
-
import {
|
|
27
|
-
__toESM
|
|
28
|
-
} from "./5ILQMFXK.js";
|
|
29
|
-
|
|
30
|
-
// src/commands/proxy/har-writer.ts
|
|
31
|
-
import { createReadStream, createWriteStream, existsSync } from "node:fs";
|
|
32
|
-
import { appendFile, mkdir, rm, writeFile } from "node:fs/promises";
|
|
33
|
-
import path from "node:path";
|
|
34
|
-
import { createInterface } from "node:readline";
|
|
35
|
-
var HarWriter = class {
|
|
36
|
-
outputPath;
|
|
37
|
-
entriesPath;
|
|
38
|
-
creatorVersion;
|
|
39
|
-
writeChain = Promise.resolve();
|
|
40
|
-
directoryEnsured = false;
|
|
41
|
-
started = false;
|
|
42
|
-
count = 0;
|
|
43
|
-
constructor(outputPath, creatorVersion) {
|
|
44
|
-
this.outputPath = path.resolve(process.cwd(), outputPath);
|
|
45
|
-
this.entriesPath = `${this.outputPath}.entries.tmp`;
|
|
46
|
-
this.creatorVersion = creatorVersion;
|
|
47
|
-
}
|
|
48
|
-
get entryCount() {
|
|
49
|
-
return this.count;
|
|
50
|
-
}
|
|
51
|
-
add(entry) {
|
|
52
|
-
const line = `${JSON.stringify(entry)}
|
|
53
|
-
`;
|
|
54
|
-
const append = this.writeChain.catch(() => void 0).then(() => this.append(line)).then(() => {
|
|
55
|
-
this.count += 1;
|
|
56
|
-
});
|
|
57
|
-
this.writeChain = append.catch(() => void 0);
|
|
58
|
-
return append;
|
|
59
|
-
}
|
|
60
|
-
async finalize() {
|
|
61
|
-
await this.writeChain.catch(() => void 0);
|
|
62
|
-
await this.ensureDirectory();
|
|
63
|
-
await this.writeDocument();
|
|
64
|
-
await rm(this.entriesPath, { force: true });
|
|
65
|
-
}
|
|
66
|
-
async append(line) {
|
|
67
|
-
await this.ensureDirectory();
|
|
68
|
-
if (!this.started) {
|
|
69
|
-
await writeFile(this.entriesPath, line, "utf8");
|
|
70
|
-
this.started = true;
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
await appendFile(this.entriesPath, line, "utf8");
|
|
74
|
-
}
|
|
75
|
-
async ensureDirectory() {
|
|
76
|
-
if (this.directoryEnsured) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
await mkdir(path.dirname(this.outputPath), { recursive: true });
|
|
80
|
-
this.directoryEnsured = true;
|
|
81
|
-
}
|
|
82
|
-
async writeDocument() {
|
|
83
|
-
const stream = createWriteStream(this.outputPath, { encoding: "utf8" });
|
|
84
|
-
const write = (chunk) => new Promise((resolve, reject) => {
|
|
85
|
-
stream.write(chunk, (error) => error ? reject(error) : resolve());
|
|
86
|
-
});
|
|
87
|
-
try {
|
|
88
|
-
const creator = JSON.stringify({ name: "redocly-cli proxy", version: this.creatorVersion });
|
|
89
|
-
await write(
|
|
90
|
-
`{
|
|
91
|
-
"log": {
|
|
92
|
-
"version": "1.2",
|
|
93
|
-
"creator": ${creator},
|
|
94
|
-
"entries": [`
|
|
95
|
-
);
|
|
96
|
-
let written = 0;
|
|
97
|
-
if (existsSync(this.entriesPath)) {
|
|
98
|
-
const lines = createInterface({
|
|
99
|
-
input: createReadStream(this.entriesPath, { encoding: "utf8" }),
|
|
100
|
-
crlfDelay: Infinity
|
|
101
|
-
});
|
|
102
|
-
for await (const line of lines) {
|
|
103
|
-
if (!line.trim()) {
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
await write(`${written === 0 ? "\n" : ",\n"} ${line}`);
|
|
107
|
-
written += 1;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
await write(written === 0 ? "]\n }\n}\n" : "\n ]\n }\n}\n");
|
|
111
|
-
} finally {
|
|
112
|
-
await new Promise((resolve, reject) => {
|
|
113
|
-
stream.on("error", reject);
|
|
114
|
-
stream.end(() => resolve());
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// src/commands/proxy/server.ts
|
|
121
|
-
var import_undici = __toESM(require_undici(), 1);
|
|
122
|
-
import {
|
|
123
|
-
createServer,
|
|
124
|
-
STATUS_CODES
|
|
125
|
-
} from "node:http";
|
|
126
|
-
var HTTP_METHODS = ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "PATCH"];
|
|
127
|
-
function toHttpMethod(value) {
|
|
128
|
-
const upper = (value ?? "GET").toUpperCase();
|
|
129
|
-
return HTTP_METHODS.find((method) => method === upper);
|
|
130
|
-
}
|
|
131
|
-
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
132
|
-
"connection",
|
|
133
|
-
"keep-alive",
|
|
134
|
-
"proxy-authenticate",
|
|
135
|
-
"proxy-authorization",
|
|
136
|
-
"te",
|
|
137
|
-
"trailer",
|
|
138
|
-
"transfer-encoding",
|
|
139
|
-
"upgrade"
|
|
140
|
-
]);
|
|
141
|
-
function readRequestBody(req) {
|
|
142
|
-
return new Promise((resolve, reject) => {
|
|
143
|
-
const chunks = [];
|
|
144
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
145
|
-
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
146
|
-
req.on("error", reject);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
function toForwardRequestHeaders(req) {
|
|
150
|
-
const headers = {};
|
|
151
|
-
for (const [name, value] of Object.entries(req.headers)) {
|
|
152
|
-
if (value === void 0) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const lower = name.toLowerCase();
|
|
156
|
-
if (HOP_BY_HOP_HEADERS.has(lower) || lower === "host" || lower === "content-length") {
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (lower === "accept-encoding") {
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
headers[name] = Array.isArray(value) ? value.join(", ") : value;
|
|
163
|
-
}
|
|
164
|
-
return headers;
|
|
165
|
-
}
|
|
166
|
-
function toClientResponseHeaders(headers) {
|
|
167
|
-
const result = {};
|
|
168
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
169
|
-
if (value === void 0 || HOP_BY_HOP_HEADERS.has(name.toLowerCase())) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
result[name] = value;
|
|
173
|
-
}
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
function rawHeadersToHarHeaders(rawHeaders) {
|
|
177
|
-
const headers = [];
|
|
178
|
-
for (let index = 0; index < rawHeaders.length - 1; index += 2) {
|
|
179
|
-
headers.push({ name: rawHeaders[index], value: rawHeaders[index + 1] });
|
|
180
|
-
}
|
|
181
|
-
return headers;
|
|
182
|
-
}
|
|
183
|
-
function responseHeadersToHarHeaders(headers) {
|
|
184
|
-
const result = [];
|
|
185
|
-
for (const [name, value] of Object.entries(headers)) {
|
|
186
|
-
if (value === void 0) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (Array.isArray(value)) {
|
|
190
|
-
for (const item of value) {
|
|
191
|
-
result.push({ name, value: item });
|
|
192
|
-
}
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
result.push({ name, value });
|
|
196
|
-
}
|
|
197
|
-
return result;
|
|
198
|
-
}
|
|
199
|
-
function toQueryString(url) {
|
|
200
|
-
const query = [];
|
|
201
|
-
for (const [name, value] of url.searchParams.entries()) {
|
|
202
|
-
query.push({ name, value });
|
|
203
|
-
}
|
|
204
|
-
return query;
|
|
205
|
-
}
|
|
206
|
-
function parseCookieHeader(cookieHeader) {
|
|
207
|
-
if (!cookieHeader) {
|
|
208
|
-
return [];
|
|
209
|
-
}
|
|
210
|
-
const cookies = [];
|
|
211
|
-
for (const pair of cookieHeader.split(";")) {
|
|
212
|
-
const separatorIndex = pair.indexOf("=");
|
|
213
|
-
if (separatorIndex === -1) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
const name = pair.slice(0, separatorIndex).trim();
|
|
217
|
-
const value = pair.slice(separatorIndex + 1).trim();
|
|
218
|
-
if (name) {
|
|
219
|
-
cookies.push({ name, value });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return cookies;
|
|
223
|
-
}
|
|
224
|
-
function isTextualContentType(contentType) {
|
|
225
|
-
if (!contentType) {
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
if (isJsonMime(contentType)) {
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
const mime = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
232
|
-
return mime.startsWith("text/") || mime === "application/xml" || mime === "application/x-www-form-urlencoded" || mime.endsWith("+xml");
|
|
233
|
-
}
|
|
234
|
-
function buildBodyPayload(body, contentType) {
|
|
235
|
-
if (body.length === 0) {
|
|
236
|
-
return { size: 0 };
|
|
237
|
-
}
|
|
238
|
-
if (isTextualContentType(contentType)) {
|
|
239
|
-
return { text: body.toString("utf8"), size: body.length };
|
|
240
|
-
}
|
|
241
|
-
return { text: body.toString("base64"), encoding: "base64", size: body.length };
|
|
242
|
-
}
|
|
243
|
-
function buildPostData(body, contentType) {
|
|
244
|
-
if (body.length === 0) {
|
|
245
|
-
return void 0;
|
|
246
|
-
}
|
|
247
|
-
const payload = buildBodyPayload(body, contentType);
|
|
248
|
-
return {
|
|
249
|
-
mimeType: contentType ?? "application/octet-stream",
|
|
250
|
-
text: payload.text ?? "",
|
|
251
|
-
...payload.encoding ? { encoding: payload.encoding } : {}
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
function startProxyServer(options) {
|
|
255
|
-
const targetUrl = new URL(options.target);
|
|
256
|
-
let exchangeIndex = 0;
|
|
257
|
-
const nextExchangeIndex = () => exchangeIndex++;
|
|
258
|
-
const server = createServer((req, res) => {
|
|
259
|
-
void handleRequest(req, res, targetUrl, options, nextExchangeIndex);
|
|
260
|
-
});
|
|
261
|
-
return new Promise((resolve, reject) => {
|
|
262
|
-
server.once("error", reject);
|
|
263
|
-
server.listen(options.port, options.host, () => {
|
|
264
|
-
server.removeListener("error", reject);
|
|
265
|
-
const address = server.address();
|
|
266
|
-
const boundPort = isPlainObject(address) ? address.port : options.port;
|
|
267
|
-
resolve({
|
|
268
|
-
url: `http://${options.host}:${boundPort}`,
|
|
269
|
-
close: () => new Promise((resolveClose, rejectClose) => {
|
|
270
|
-
server.close((error) => error ? rejectClose(error) : resolveClose());
|
|
271
|
-
})
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
function buildForwardUrl(requestUrl, targetUrl) {
|
|
277
|
-
const requested = new URL(requestUrl, targetUrl);
|
|
278
|
-
const basePath = targetUrl.pathname.endsWith("/") ? targetUrl.pathname.slice(0, -1) : targetUrl.pathname;
|
|
279
|
-
const requestPath = requested.pathname;
|
|
280
|
-
const alreadyPrefixed = requestPath === basePath || requestPath.startsWith(`${basePath}/`);
|
|
281
|
-
const forwardUrl = new URL(targetUrl.toString());
|
|
282
|
-
forwardUrl.pathname = basePath && !alreadyPrefixed ? `${basePath}${requestPath}` : requestPath;
|
|
283
|
-
forwardUrl.search = requested.search;
|
|
284
|
-
return forwardUrl;
|
|
285
|
-
}
|
|
286
|
-
async function handleRequest(req, res, targetUrl, options, nextExchangeIndex) {
|
|
287
|
-
const startedAt = /* @__PURE__ */ new Date();
|
|
288
|
-
const method = toHttpMethod(req.method);
|
|
289
|
-
if (!method) {
|
|
290
|
-
res.writeHead(501, { "content-type": "text/plain" });
|
|
291
|
-
res.end(`Unsupported HTTP method: ${req.method}`);
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
const forwardUrl = buildForwardUrl(req.url ?? "/", targetUrl);
|
|
295
|
-
let captured = null;
|
|
296
|
-
try {
|
|
297
|
-
const requestBody = await readRequestBody(req);
|
|
298
|
-
const hasBody = method !== "GET" && method !== "HEAD" && requestBody.length > 0;
|
|
299
|
-
const upstream = await (0, import_undici.request)(forwardUrl, {
|
|
300
|
-
method,
|
|
301
|
-
headers: toForwardRequestHeaders(req),
|
|
302
|
-
body: hasBody ? requestBody : void 0
|
|
303
|
-
});
|
|
304
|
-
const responseBody = Buffer.from(await upstream.body.arrayBuffer());
|
|
305
|
-
res.writeHead(upstream.statusCode, toClientResponseHeaders(upstream.headers));
|
|
306
|
-
res.end(responseBody);
|
|
307
|
-
const elapsedMs = Date.now() - startedAt.getTime();
|
|
308
|
-
captured = buildCapturedExchange({
|
|
309
|
-
index: nextExchangeIndex(),
|
|
310
|
-
method,
|
|
311
|
-
forwardUrl,
|
|
312
|
-
req,
|
|
313
|
-
requestBody,
|
|
314
|
-
statusCode: upstream.statusCode,
|
|
315
|
-
responseHeaders: upstream.headers,
|
|
316
|
-
responseBody,
|
|
317
|
-
startedAt,
|
|
318
|
-
elapsedMs
|
|
319
|
-
});
|
|
320
|
-
} catch (error) {
|
|
321
|
-
options.onError(error);
|
|
322
|
-
if (!res.writableEnded) {
|
|
323
|
-
if (!res.headersSent) {
|
|
324
|
-
res.writeHead(502, { "content-type": "text/plain" });
|
|
325
|
-
}
|
|
326
|
-
res.end(`Proxy error: ${error.message}`);
|
|
327
|
-
}
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
if (captured) {
|
|
331
|
-
try {
|
|
332
|
-
await options.onExchange(captured);
|
|
333
|
-
} catch (error) {
|
|
334
|
-
options.onError(error);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
function buildCapturedExchange(params) {
|
|
339
|
-
const requestContentType = singleHeader(params.req.headers["content-type"]);
|
|
340
|
-
const responseContentType = singleHeader(params.responseHeaders["content-type"]);
|
|
341
|
-
const exchange = createNormalizedExchange(
|
|
342
|
-
{
|
|
343
|
-
method: params.method,
|
|
344
|
-
url: params.forwardUrl.toString(),
|
|
345
|
-
requestHeaders: params.req.headers,
|
|
346
|
-
requestBody: params.requestBody,
|
|
347
|
-
requestContentType,
|
|
348
|
-
responseStatus: params.statusCode,
|
|
349
|
-
responseHeaders: params.responseHeaders,
|
|
350
|
-
responseBody: params.responseBody,
|
|
351
|
-
responseContentType,
|
|
352
|
-
startedAt: params.startedAt.toISOString()
|
|
353
|
-
},
|
|
354
|
-
params.index,
|
|
355
|
-
"(proxy)"
|
|
356
|
-
);
|
|
357
|
-
if (!exchange) {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
const responsePayload = buildBodyPayload(params.responseBody, responseContentType);
|
|
361
|
-
const harEntry = {
|
|
362
|
-
startedDateTime: params.startedAt.toISOString(),
|
|
363
|
-
time: params.elapsedMs,
|
|
364
|
-
request: {
|
|
365
|
-
method: params.method,
|
|
366
|
-
url: params.forwardUrl.toString(),
|
|
367
|
-
httpVersion: `HTTP/${params.req.httpVersion}`,
|
|
368
|
-
cookies: parseCookieHeader(singleHeader(params.req.headers.cookie)),
|
|
369
|
-
headers: rawHeadersToHarHeaders(params.req.rawHeaders),
|
|
370
|
-
queryString: toQueryString(params.forwardUrl),
|
|
371
|
-
postData: buildPostData(params.requestBody, requestContentType),
|
|
372
|
-
headersSize: -1,
|
|
373
|
-
bodySize: params.requestBody.length
|
|
374
|
-
},
|
|
375
|
-
response: {
|
|
376
|
-
status: params.statusCode,
|
|
377
|
-
statusText: STATUS_CODES[params.statusCode] ?? "",
|
|
378
|
-
httpVersion: "HTTP/1.1",
|
|
379
|
-
cookies: [],
|
|
380
|
-
headers: responseHeadersToHarHeaders(params.responseHeaders),
|
|
381
|
-
content: {
|
|
382
|
-
size: responsePayload.size,
|
|
383
|
-
mimeType: responseContentType ?? "application/octet-stream",
|
|
384
|
-
...responsePayload.text !== void 0 ? { text: responsePayload.text } : {},
|
|
385
|
-
...responsePayload.encoding ? { encoding: responsePayload.encoding } : {}
|
|
386
|
-
},
|
|
387
|
-
redirectURL: singleHeader(params.responseHeaders.location) ?? "",
|
|
388
|
-
headersSize: -1,
|
|
389
|
-
bodySize: params.responseBody.length
|
|
390
|
-
},
|
|
391
|
-
cache: {},
|
|
392
|
-
timings: { send: 0, wait: params.elapsedMs, receive: 0 }
|
|
393
|
-
};
|
|
394
|
-
return { exchange, harEntry };
|
|
395
|
-
}
|
|
396
|
-
function singleHeader(value) {
|
|
397
|
-
if (Array.isArray(value)) {
|
|
398
|
-
return value[0];
|
|
399
|
-
}
|
|
400
|
-
return value;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// src/commands/proxy/index.ts
|
|
404
|
-
var USE_COLOR = Boolean(process.stdout.isTTY) && process.env.NO_COLOR === void 0;
|
|
405
|
-
function severityIcon(severity) {
|
|
406
|
-
if (severity === "error") return "\u2716";
|
|
407
|
-
if (severity === "warning") return "\u25B2";
|
|
408
|
-
return "\u25CF";
|
|
409
|
-
}
|
|
410
|
-
function formatLiveFinding(finding) {
|
|
411
|
-
const status = finding.status !== void 0 ? ` (${finding.status})` : "";
|
|
412
|
-
const operation = finding.operationId ? ` ${finding.operationId}` : "";
|
|
413
|
-
return `${severityIcon(finding.severity)} ${finding.severity.toUpperCase()} ${finding.method} ${finding.path}${status}${operation} \u2192 [${finding.ruleId}] ${finding.message}`;
|
|
414
|
-
}
|
|
415
|
-
function waitForShutdownSignal() {
|
|
416
|
-
return new Promise((resolve) => {
|
|
417
|
-
process.once("SIGINT", () => resolve());
|
|
418
|
-
process.once("SIGTERM", () => resolve());
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
async function handleProxy({ argv, config, version }) {
|
|
422
|
-
const targetInput = /^[a-z][a-z0-9+.-]*:\/\//i.test(argv.target) ? argv.target : `http://${argv.target}`;
|
|
423
|
-
let target;
|
|
424
|
-
try {
|
|
425
|
-
target = new URL(targetInput);
|
|
426
|
-
} catch {
|
|
427
|
-
return exitWithError(`Invalid --target URL: ${argv.target}`);
|
|
428
|
-
}
|
|
429
|
-
const harPath = normalizeFsPath(argv.har);
|
|
430
|
-
const harWriter = new HarWriter(harPath, version);
|
|
431
|
-
let session = null;
|
|
432
|
-
if (argv.api) {
|
|
433
|
-
const specPath = normalizeFsPath(argv.api);
|
|
434
|
-
const openApiIndex = await loadOpenApiIndex(specPath, config);
|
|
435
|
-
if (openApiIndex.loadedOperations === 0) {
|
|
436
|
-
return exitWithError(`No OpenAPI operations were loaded from: ${specPath}`);
|
|
437
|
-
}
|
|
438
|
-
session = ValidationSession.create({
|
|
439
|
-
openApiIndex,
|
|
440
|
-
matchMode: argv["match-mode"],
|
|
441
|
-
ignoreCookies: argv["ignore-cookies"],
|
|
442
|
-
ignoreHeaders: argv["ignore-headers"] ? parseCsv(argv["ignore-headers"]) : void 0,
|
|
443
|
-
previewFindingsLimit: argv["max-findings"],
|
|
444
|
-
activeRules: argv.rules ? parseCsv(argv.rules) : void 0
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
let exchangeQueue = Promise.resolve();
|
|
448
|
-
let server;
|
|
449
|
-
try {
|
|
450
|
-
server = await startProxyServer({
|
|
451
|
-
target: target.toString(),
|
|
452
|
-
port: argv.port,
|
|
453
|
-
host: argv.host,
|
|
454
|
-
onExchange: ({ exchange, harEntry }) => {
|
|
455
|
-
const task = exchangeQueue.then(async () => {
|
|
456
|
-
try {
|
|
457
|
-
await harWriter.add(harEntry);
|
|
458
|
-
} catch (error) {
|
|
459
|
-
logger.error(
|
|
460
|
-
`Failed to write HAR entry, skipping validation for this exchange: ${error.message}
|
|
461
|
-
`
|
|
462
|
-
);
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
if (!session) {
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
const findings2 = await session.process(exchange);
|
|
469
|
-
for (const finding of findings2) {
|
|
470
|
-
logger.info(`${formatLiveFinding(finding)}
|
|
471
|
-
`);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
exchangeQueue = task.catch(() => void 0);
|
|
475
|
-
return task;
|
|
476
|
-
},
|
|
477
|
-
onError: (error) => {
|
|
478
|
-
logger.error(`Proxy request failed: ${error.message}
|
|
479
|
-
`);
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
} catch (error) {
|
|
483
|
-
return exitWithError(`Failed to start proxy server: ${error.message}`);
|
|
484
|
-
}
|
|
485
|
-
logger.info(`Proxy listening on ${server.url} \u2192 forwarding to ${target.toString()}
|
|
486
|
-
`);
|
|
487
|
-
logger.info(`Recording traffic to ${harPath}
|
|
488
|
-
`);
|
|
489
|
-
if (session) {
|
|
490
|
-
logger.info(`Validating live traffic against ${normalizeFsPath(argv.api)}
|
|
491
|
-
`);
|
|
492
|
-
}
|
|
493
|
-
logger.info("Press Ctrl+C to stop.\n");
|
|
494
|
-
await waitForShutdownSignal();
|
|
495
|
-
logger.info("\nShutting down proxy\u2026\n");
|
|
496
|
-
await server.close();
|
|
497
|
-
await exchangeQueue;
|
|
498
|
-
await harWriter.finalize();
|
|
499
|
-
logger.info(`Captured ${harWriter.entryCount} exchange(s) to ${harPath}
|
|
500
|
-
`);
|
|
501
|
-
if (!session) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
const { runId, summary, findings } = session.finalize();
|
|
505
|
-
const result = {
|
|
506
|
-
runId,
|
|
507
|
-
summary,
|
|
508
|
-
findings,
|
|
509
|
-
meta: {
|
|
510
|
-
specSource: normalizeFsPath(argv.api),
|
|
511
|
-
trafficPath: harPath,
|
|
512
|
-
format: "har",
|
|
513
|
-
matchMode: argv["match-mode"]
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
|
-
const report = renderReport(result, {
|
|
517
|
-
format: argv["report-format"],
|
|
518
|
-
color: USE_COLOR && argv["report-format"] === "pretty",
|
|
519
|
-
maxFindings: argv["max-findings"]
|
|
520
|
-
});
|
|
521
|
-
logger.output(report);
|
|
522
|
-
if (summary.findingsBySeverity.error > 0) {
|
|
523
|
-
throw new AbortFlowError("Drift detected.");
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
export {
|
|
527
|
-
handleProxy
|
|
528
|
-
};
|
package/lib/chunks/NW3XJZXQ.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { createRequire as __createRequire } from 'node:module';
|
|
2
|
-
const require = __createRequire(import.meta.url);
|
|
3
|
-
import {
|
|
4
|
-
selectTrafficParser
|
|
5
|
-
} from "./P6UGG7F6.js";
|
|
6
|
-
import {
|
|
7
|
-
ValidationSession,
|
|
8
|
-
loadOpenApiIndex,
|
|
9
|
-
parseCsv,
|
|
10
|
-
renderReport
|
|
11
|
-
} from "./RRC5IDC3.js";
|
|
12
|
-
import {
|
|
13
|
-
listFilesRecursively,
|
|
14
|
-
normalizeFsPath
|
|
15
|
-
} from "./3CX2Z7JI.js";
|
|
16
|
-
import {
|
|
17
|
-
AbortFlowError,
|
|
18
|
-
exitWithError
|
|
19
|
-
} from "./T6UZU3XM.js";
|
|
20
|
-
import {
|
|
21
|
-
logger
|
|
22
|
-
} from "./Z2664VV5.js";
|
|
23
|
-
import "./Z2I5YXYN.js";
|
|
24
|
-
import "./5ILQMFXK.js";
|
|
25
|
-
|
|
26
|
-
// src/commands/drift/index.ts
|
|
27
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
28
|
-
import path from "node:path";
|
|
29
|
-
|
|
30
|
-
// src/commands/drift/engine/runner.ts
|
|
31
|
-
async function runTrafficValidation(options) {
|
|
32
|
-
const trafficFiles = await listFilesRecursively(options.trafficPath);
|
|
33
|
-
if (trafficFiles.length === 0) {
|
|
34
|
-
throw new Error("No traffic files found in the provided traffic path.");
|
|
35
|
-
}
|
|
36
|
-
const session = ValidationSession.create({
|
|
37
|
-
openApiIndex: options.openApiIndex,
|
|
38
|
-
matchMode: options.matchMode,
|
|
39
|
-
ignoreCookies: options.ignoreCookies,
|
|
40
|
-
ignoreHeaders: options.ignoreHeaders,
|
|
41
|
-
previewFindingsLimit: options.previewFindingsLimit,
|
|
42
|
-
activeRules: options.activeRules,
|
|
43
|
-
server: options.server,
|
|
44
|
-
minSeverity: options.minSeverity
|
|
45
|
-
});
|
|
46
|
-
let supportedTrafficFileCount = 0;
|
|
47
|
-
let exchangeIndex = 0;
|
|
48
|
-
for (const trafficFile of trafficFiles) {
|
|
49
|
-
const parser = await selectTrafficParser(trafficFile, options.format);
|
|
50
|
-
if (!parser) {
|
|
51
|
-
logger.warn(`Skipping traffic file with unrecognized format: ${trafficFile}
|
|
52
|
-
`);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
supportedTrafficFileCount += 1;
|
|
56
|
-
for await (const exchange of parser.parse(trafficFile)) {
|
|
57
|
-
await session.process({ ...exchange, index: exchangeIndex });
|
|
58
|
-
exchangeIndex += 1;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (supportedTrafficFileCount === 0) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
"No supported traffic files found. In auto mode, files must match built-in traffic parser signatures."
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
if (exchangeIndex === 0) {
|
|
67
|
-
throw new Error("No HTTP exchanges were parsed from the provided traffic files.");
|
|
68
|
-
}
|
|
69
|
-
return session.finalize();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// src/commands/drift/index.ts
|
|
73
|
-
var USE_COLOR = Boolean(process.stdout.isTTY) && process.env.NO_COLOR === void 0;
|
|
74
|
-
function collectSpecServerUrls(openApiIndex) {
|
|
75
|
-
const urls = /* @__PURE__ */ new Set();
|
|
76
|
-
for (const operations of openApiIndex.operationsByMethod.values()) {
|
|
77
|
-
for (const operation of operations) {
|
|
78
|
-
for (const server of operation.servers) {
|
|
79
|
-
urls.add(server.rawUrl);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return Array.from(urls).sort();
|
|
84
|
-
}
|
|
85
|
-
function warnWhenNothingMatched(summary, openApiIndex, server) {
|
|
86
|
-
const validatedExchanges = summary.totalExchanges - summary.skippedExchanges;
|
|
87
|
-
if (validatedExchanges === 0 && summary.skippedExchanges > 0) {
|
|
88
|
-
logger.warn(
|
|
89
|
-
`All ${summary.skippedExchanges} exchange(s) were outside the --server "${server}" and were skipped. Check that the server matches the traffic URLs.
|
|
90
|
-
`
|
|
91
|
-
);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (summary.documentedExchanges > 0 || validatedExchanges === 0) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const serverUrls = collectSpecServerUrls(openApiIndex);
|
|
98
|
-
const hint = server ? `Check that the --server "${server}" matches the traffic URLs and that the description paths align with the remainder.` : summary.hostCompatibleExchanges === validatedExchanges ? `The traffic hosts are compatible with the description servers (${serverUrls.join(
|
|
99
|
-
", "
|
|
100
|
-
)}), so the endpoints are likely undocumented; if they should be documented, check that the description base paths and paths align with the traffic URLs, or use --server to declare the server the traffic was captured against.` : `Check that the traffic host and base path match the description servers (${serverUrls.join(
|
|
101
|
-
", "
|
|
102
|
-
)}), or use --server to declare the server the traffic was captured against.`;
|
|
103
|
-
logger.warn(
|
|
104
|
-
`None of the ${validatedExchanges} validated exchange(s) matched a documented operation. ${hint}
|
|
105
|
-
`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
async function writeOutput(outputPath, content) {
|
|
109
|
-
const resolved = normalizeFsPath(outputPath);
|
|
110
|
-
await mkdir(path.dirname(resolved), { recursive: true });
|
|
111
|
-
await writeFile(resolved, content, "utf8");
|
|
112
|
-
}
|
|
113
|
-
async function handleDrift({ argv, config }) {
|
|
114
|
-
const trafficPath = normalizeFsPath(argv.traffic);
|
|
115
|
-
const trafficFormat = argv["traffic-format"];
|
|
116
|
-
const activeRules = argv.rules ? parseCsv(argv.rules) : void 0;
|
|
117
|
-
const ignoreHeaders = argv["ignore-headers"] ? parseCsv(argv["ignore-headers"]) : void 0;
|
|
118
|
-
const server = argv.server;
|
|
119
|
-
if (server && argv["match-mode"]) {
|
|
120
|
-
return exitWithError(
|
|
121
|
-
"The --server and --match-mode options are mutually exclusive: --match-mode controls how requests are located via the description servers, while --server replaces the description servers with the one the traffic was captured against."
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
const matchMode = argv["match-mode"] ?? "strict-host";
|
|
125
|
-
const specPath = normalizeFsPath(argv.api);
|
|
126
|
-
const openApiIndex = await loadOpenApiIndex(specPath, config);
|
|
127
|
-
if (openApiIndex.loadedOperations === 0) {
|
|
128
|
-
return exitWithError(`No OpenAPI operations were loaded from: ${specPath}`);
|
|
129
|
-
}
|
|
130
|
-
const { runId, summary, findings } = await runTrafficValidation({
|
|
131
|
-
trafficPath,
|
|
132
|
-
format: trafficFormat,
|
|
133
|
-
matchMode,
|
|
134
|
-
ignoreCookies: argv["ignore-cookies"],
|
|
135
|
-
ignoreHeaders,
|
|
136
|
-
previewFindingsLimit: argv["max-findings"],
|
|
137
|
-
activeRules,
|
|
138
|
-
openApiIndex,
|
|
139
|
-
server,
|
|
140
|
-
minSeverity: argv["min-severity"]
|
|
141
|
-
});
|
|
142
|
-
warnWhenNothingMatched(summary, openApiIndex, server);
|
|
143
|
-
const report = renderReport(
|
|
144
|
-
{
|
|
145
|
-
runId,
|
|
146
|
-
summary,
|
|
147
|
-
findings,
|
|
148
|
-
meta: {
|
|
149
|
-
specSource: specPath,
|
|
150
|
-
trafficPath,
|
|
151
|
-
format: trafficFormat,
|
|
152
|
-
matchMode,
|
|
153
|
-
server
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
format: argv["report-format"],
|
|
158
|
-
color: USE_COLOR && argv["report-format"] === "pretty" && !argv.output,
|
|
159
|
-
maxFindings: argv["max-findings"]
|
|
160
|
-
}
|
|
161
|
-
);
|
|
162
|
-
if (argv.output) {
|
|
163
|
-
await writeOutput(argv.output, report);
|
|
164
|
-
logger.info(`Drift report written to: ${normalizeFsPath(argv.output)}
|
|
165
|
-
`);
|
|
166
|
-
} else {
|
|
167
|
-
logger.output(report);
|
|
168
|
-
}
|
|
169
|
-
if (summary.findingsBySeverity.error > 0) {
|
|
170
|
-
throw new AbortFlowError("Drift detected.");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
export {
|
|
174
|
-
handleDrift
|
|
175
|
-
};
|