fetch-to-curl-ts
Version:
Convert fetch HTTP request to curl
132 lines (131 loc) • 5.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchToCurl = void 0;
const headers_1 = require("./headers");
const web_1 = require("node:stream/web");
const method_1 = require("./method");
const node_stream_1 = require("node:stream");
const node_assert_1 = __importDefault(require("node:assert"));
const getHeaderString = (name, val) => `-H "${name}: ${`${val}`.replace(/(\\|")/g, "\\$1")}"`;
const normalizeHeaders = (headers) => (0, headers_1.toTupleArray)(headers).reduce((acc, [key, value]) => {
if (key.toLocaleLowerCase() === "accept-encoding") {
acc.isEncoding = true;
}
acc.headers.push([key, value]);
return acc;
}, {
headers: [],
isEncoding: false,
});
const escapeBody = (body) => typeof body === "string" ? body.replace(/'/g, `'\\''`) : body;
const generateBody = (body) => typeof body === "object"
? escapeBody(JSON.stringify(body))
: typeof body === "string"
? escapeBody(body)
: (() => {
throw new Error(`Invalid body type: ${typeof body}`);
})();
const buildCurl = (requestInfo, requestInit, serialized) => {
var _a;
const [url, options] = typeof requestInfo === "string" || requestInfo instanceof URL
? [requestInfo.toString(), requestInit !== null && requestInit !== void 0 ? requestInit : {}]
: [
(requestInfo || {}).url,
requestInfo,
];
const { body } = options;
const serializedBody = serialized === null || serialized === void 0 ? void 0 : serialized.body;
const formattedBody = serializedBody
? serializedBody
: body
? generateBody(body)
: undefined;
const { headers, isEncoding } = normalizeHeaders(options.headers || {});
return [
"curl",
// URL
`'${url}'`,
// Method
"-X",
(0, method_1.getMethod)((_a = options.method) !== null && _a !== void 0 ? _a : method_1.HTTP_METHODS.GET),
// Headers
...headers.map(([k, v]) => getHeaderString(k, v)),
// Body
...(formattedBody ? ["--data-binary", `'${formattedBody}'`] : []),
// Flags
isEncoding ? " --compressed" : undefined,
]
.filter(Boolean)
.join(" ");
};
const webStreamToString = (stream) => __awaiter(void 0, void 0, void 0, function* () {
const reader = stream.getReader();
let result = yield reader.read();
let body = "";
while (!result.done) {
body += new TextDecoder().decode(result.value, { stream: true });
result = yield reader.read();
}
return body;
});
const nodeStreamToString = (stream) => __awaiter(void 0, void 0, void 0, function* () {
let body = "";
stream.on("data", (chunk) => {
body += chunk;
});
(0, node_assert_1.default)(!stream.readableEnded, "Stream has already ended");
return new Promise((res, reject) => {
stream.on("error", reject);
stream.on("end", () => {
return res(body);
});
});
});
const fromRequest = (request) => __awaiter(void 0, void 0, void 0, function* () {
const serializedBody = yield maybeSerializeBody(request.body);
return buildCurl(request, undefined, { body: serializedBody });
});
const fetchToCurl = (requestInfo, requestInit) => __awaiter(void 0, void 0, void 0, function* () {
if (isRequestLike(requestInfo)) {
return fromRequest(requestInfo);
}
if (!requestInit) {
return buildCurl(requestInfo, requestInit);
}
const serializedBody = yield maybeSerializeBody(requestInit.body, requestInit);
return buildCurl(requestInfo, requestInit, { body: serializedBody });
});
exports.fetchToCurl = fetchToCurl;
/**
* @warn If there is a backing requestInit, it must be passed, otherwise,
* the underlying stream will be consumed and break the actual requestInit
* requesting serialiation.
*/
const maybeSerializeBody = (initStream, requestInit) => __awaiter(void 0, void 0, void 0, function* () {
if (initStream instanceof node_stream_1.Readable) {
return nodeStreamToString(initStream);
}
if (initStream instanceof web_1.ReadableStream) {
const [s1, s2] = initStream.tee();
if (requestInit) {
requestInit.body = s2;
}
return webStreamToString(s1);
}
});
const isRequestLike = (requestInfo) => !!requestInfo &&
"url" &&
typeof requestInfo === "object" &&
"url" in requestInfo;