UNPKG

eas-cli

Version:
166 lines (165 loc) 6.13 kB
"use strict"; 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;