eas-cli
Version:
EAS command line tool
215 lines (214 loc) • 8.33 kB
JavaScript
;
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;