eas-cli
Version:
EAS command line tool
166 lines (165 loc) • 6.13 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.batchUploadAsync = exports.callUploadApiAsync = exports.uploadAsync = void 0;
const tslib_1 = require("tslib");
const https = tslib_1.__importStar(require("https"));
const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent"));
const mime_1 = tslib_1.__importDefault(require("mime"));
const minizlib_1 = require("minizlib");
const node_fetch_1 = tslib_1.__importStar(require("node-fetch"));
const node_fs_1 = tslib_1.__importStar(require("node:fs"));
const node_path_1 = tslib_1.__importDefault(require("node:path"));
const promise_retry_1 = tslib_1.__importDefault(require("promise-retry"));
const MAX_RETRIES = 4;
const MAX_CONCURRENCY = 10;
const MIN_RETRY_TIMEOUT = 100;
const MAX_UPLOAD_SIZE = 5e8; // 5MB
const MIN_COMPRESSION_SIZE = 5e4; // 50kB
const isCompressible = (contentType, size) => {
if (size < MIN_COMPRESSION_SIZE) {
// Don't compress small files
return false;
}
else if (contentType && /^(?:audio|video|image)\//i.test(contentType)) {
// Never compress images, audio, or videos as they're presumably precompressed
return false;
}
else if (contentType && /^application\//i.test(contentType)) {
// Only compress `application/` files if they're marked as XML/JSON/JS
return /(?:xml|json5?|javascript)$/i.test(contentType);
}
else {
return true;
}
};
let sharedAgent;
const getAgent = () => {
if (sharedAgent) {
return sharedAgent;
}
else if (process.env.https_proxy) {
return (sharedAgent = (0, https_proxy_agent_1.default)(process.env.https_proxy));
}
else {
return (sharedAgent = new https.Agent({
keepAlive: true,
maxSockets: MAX_CONCURRENCY,
maxTotalSockets: MAX_CONCURRENCY,
scheduling: 'lifo',
timeout: 4000,
}));
}
};
async function uploadAsync(params) {
const { filePath, signal, compress, method = 'POST', url, headers: headersInit, ...requestInit } = params;
const stat = await node_fs_1.default.promises.stat(params.filePath);
if (stat.size > MAX_UPLOAD_SIZE) {
throw new Error(`Upload of "${params.filePath}" aborted: File size is greater than the upload limit (>500MB)`);
}
const contentType = mime_1.default.getType(node_path_1.default.basename(params.filePath));
return await (0, promise_retry_1.default)(async (retry) => {
const headers = new node_fetch_1.Headers(headersInit);
if (contentType) {
headers.set('content-type', contentType);
}
let bodyStream = (0, node_fs_1.createReadStream)(filePath);
if (compress && isCompressible(contentType, stat.size)) {
const gzip = new minizlib_1.Gzip({ portable: true });
bodyStream.on('error', error => gzip.emit('error', error));
// @ts-expect-error: Gzip implements a Readable-like interface
bodyStream = bodyStream.pipe(gzip);
headers.set('content-encoding', 'gzip');
}
let response;
try {
response = await (0, node_fetch_1.default)(params.url, {
...requestInit,
method,
body: bodyStream,
headers,
agent: getAgent(),
// @ts-expect-error: Internal types don't match
signal,
});
}
catch (error) {
return retry(error);
}
const getErrorMessageAsync = async () => {
const body = await response.json().catch(() => null);
return body?.error ?? `Upload of "${filePath}" failed: ${response.statusText}`;
};
if (response.status === 408 ||
response.status === 409 ||
response.status === 429 ||
(response.status >= 500 && response.status <= 599)) {
return retry(new Error(await getErrorMessageAsync()));
}
else if (response.status === 413) {
const message = `Upload of "${filePath}" failed: File size exceeded the upload limit`;
throw new Error(message);
}
else if (!response.ok) {
throw new Error(await getErrorMessageAsync());
}
return {
params,
response,
};
}, {
retries: MAX_RETRIES,
minTimeout: MIN_RETRY_TIMEOUT,
randomize: true,
factor: 2,
});
}
exports.uploadAsync = uploadAsync;
async function callUploadApiAsync(url, init) {
return await (0, promise_retry_1.default)(async (retry) => {
let response;
try {
response = await (0, node_fetch_1.default)(url, {
...init,
agent: getAgent(),
});
}
catch (error) {
return retry(error);
}
if (response.status >= 500 && response.status <= 599) {
retry(new Error(`Deployment failed: ${response.statusText}`));
}
try {
return await response.json();
}
catch (error) {
retry(error);
}
});
}
exports.callUploadApiAsync = callUploadApiAsync;
async function* batchUploadAsync(uploads) {
const controller = new AbortController();
const queue = new Set();
try {
let index = 0;
while (index < uploads.length || queue.size > 0) {
while (queue.size < MAX_CONCURRENCY && index < uploads.length) {
const uploadParams = uploads[index++];
let uploadPromise;
queue.add((uploadPromise = uploadAsync({ ...uploadParams, signal: controller.signal }).finally(() => queue.delete(uploadPromise))));
yield { params: uploadParams };
}
yield await Promise.race(queue);
}
if (queue.size > 0) {
controller.abort();
}
}
catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
}
}
exports.batchUploadAsync = batchUploadAsync;
;