UNPKG

eas-cli

Version:

EAS command line tool

215 lines (214 loc) 8.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createProgressBar = exports.batchUploadAsync = exports.callUploadApiAsync = exports.uploadAsync = void 0; const tslib_1 = require("tslib"); const cli_progress_1 = tslib_1.__importDefault(require("cli-progress")); const https = tslib_1.__importStar(require("https")); const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent")); const node_fetch_1 = tslib_1.__importStar(require("node-fetch")); const node_fs_1 = tslib_1.__importDefault(require("node:fs")); const node_os_1 = tslib_1.__importDefault(require("node:os")); const node_stream_1 = require("node:stream"); const promise_retry_1 = tslib_1.__importDefault(require("promise-retry")); const multipart_1 = require("./utils/multipart"); const MAX_CONCURRENCY = Math.min(10, Math.max(node_os_1.default.availableParallelism() * 2, 20)); const MAX_RETRIES = 4; 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(init, payload, onProgressUpdate) { return await (0, promise_retry_1.default)(async (retry) => { if (onProgressUpdate) { onProgressUpdate(0); } const headers = new node_fetch_1.Headers(init.headers); const url = new URL(`${init.baseURL}`); let errorPrefix; let body; let method = init.method || 'POST'; if ('asset' in payload) { const { asset } = payload; errorPrefix = `Upload of "${asset.normalizedPath}" failed`; if (asset.type) { headers.set('content-type', asset.type); } if (asset.size) { headers.set('content-length', `${asset.size}`); } method = 'POST'; url.pathname = `/asset/${asset.sha512}`; body = node_stream_1.Readable.from((0, multipart_1.createReadStreamAsync)(asset), { objectMode: false }); } else if ('filePath' in payload) { const { filePath } = payload; errorPrefix = 'Worker deployment failed'; body = node_fs_1.default.createReadStream(filePath); } else if ('multipart' in payload) { const { multipart } = payload; errorPrefix = `Upload of ${multipart.length} assets failed`; headers.set('content-type', multipart_1.multipartContentType); method = 'PATCH'; url.pathname = '/asset/batch'; body = node_stream_1.Readable.from((0, multipart_1.createMultipartBodyFromFilesAsync)(multipart, onProgressUpdate), { objectMode: false, }); } let response; try { response = await (0, node_fetch_1.default)(url, { method, body, headers, agent: getAgent(), signal: init.signal, }); } catch (error) { return retry(error); } const getErrorMessageAsync = async () => { const rayId = response.headers.get('cf-ray'); const contentType = response.headers.get('Content-Type'); if (contentType?.startsWith('text/html')) { // NOTE(@kitten): We've received a CDN error most likely. There's not much we can do // except for quoting the Request ID, so a user can send it to us. We can check // why a request was blocked by looking up a WAF event via the "Ray ID" here: // https://dash.cloudflare.com/e6f39f67f543faa6038768e8f37e4234/expo.app/security/events let message = `CDN firewall has aborted the upload with ${response.statusText}.`; if (rayId) { message += `\nReport this error quoting Request ID ${rayId}`; } return `${errorPrefix}: ${message}`; } else { const json = await response.json().catch(() => null); return json?.error ?? `${errorPrefix}: ${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 = `${errorPrefix}: File size exceeded the upload limit`; throw new Error(message); } else if (!response.ok) { throw new Error(await getErrorMessageAsync()); } else if (onProgressUpdate) { onProgressUpdate(1); } return { payload, response, }; }, { retries: MAX_RETRIES, minTimeout: 50, randomize: false, }); } 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(init, payloads, onProgressUpdate) { const progressTracker = new Array(payloads.length).fill(0); const controller = new AbortController(); const queue = new Set(); const initWithSignal = { ...init, signal: controller.signal }; const getProgressValue = () => { const progress = progressTracker.reduce((acc, value) => acc + value, 0); return progress / payloads.length; }; const sendProgressUpdate = onProgressUpdate && (() => { onProgressUpdate(getProgressValue()); }); try { let index = 0; while (index < payloads.length || queue.size > 0) { while (queue.size < MAX_CONCURRENCY && index < payloads.length) { const currentIndex = index++; const payload = payloads[currentIndex]; const onChildProgressUpdate = sendProgressUpdate && ((progress) => { progressTracker[currentIndex] = progress; sendProgressUpdate(); }); const uploadPromise = uploadAsync(initWithSignal, payload, onChildProgressUpdate).finally(() => { queue.delete(uploadPromise); progressTracker[currentIndex] = 1; }); queue.add(uploadPromise); yield { payload, progress: getProgressValue() }; } yield { ...(await Promise.race(queue)), progress: getProgressValue(), }; } if (queue.size > 0) { controller.abort(); } } catch (error) { if (error.name !== 'AbortError') { throw error; } } } exports.batchUploadAsync = batchUploadAsync; function createProgressBar(label = 'Uploading assets') { const queueProgressBar = new cli_progress_1.default.SingleBar({ format: `|{bar}| {percentage}% ${label}` }, cli_progress_1.default.Presets.rect); queueProgressBar.start(1, 0); return { update(progress) { queueProgressBar.update(progress); }, stop() { queueProgressBar.stop(); }, }; } exports.createProgressBar = createProgressBar;