eas-cli
Version:
EAS command line tool
157 lines (156 loc) • 7.25 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractAppFromLocalArchiveAsync = exports.downloadAndMaybeExtractAppAsync = void 0;
const tslib_1 = require("tslib");
const spawn_async_1 = tslib_1.__importDefault(require("@expo/spawn-async"));
const fast_glob_1 = tslib_1.__importDefault(require("fast-glob"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const stream_1 = require("stream");
const tar_1 = require("tar");
const util_1 = require("util");
const uuid_1 = require("uuid");
const files_1 = require("./files");
const paths_1 = require("./paths");
const progress_1 = require("./progress");
const fetch_1 = tslib_1.__importDefault(require("../fetch"));
const generated_1 = require("../graphql/generated");
const log_1 = tslib_1.__importDefault(require("../log"));
const prompts_1 = require("../prompts");
const pipeline = (0, util_1.promisify)(stream_1.Stream.pipeline);
function wrapFetchWithProgress() {
let didProgressBarFinish = false;
return async (url, init, progressHandler) => {
const response = await (0, fetch_1.default)(url, init);
if (response.ok) {
const totalDownloadSize = response.headers.get('Content-Length');
const total = Number(totalDownloadSize);
if (!totalDownloadSize || isNaN(total) || total < 0) {
log_1.default.warn('Progress callback not supported for network request because "Content-Length" header missing or invalid in response from URL:', url.toString());
return response;
}
let length = 0;
const onProgress = (chunkLength) => {
if (chunkLength) {
length += chunkLength;
}
const progress = length / total;
if (!didProgressBarFinish) {
progressHandler({
progress: { total, percent: progress, transferred: length },
isComplete: total === length,
});
if (total === length) {
didProgressBarFinish = true;
}
}
};
response.body.on('data', chunk => {
onProgress(chunk.length);
});
response.body.on('end', () => {
onProgress();
});
}
return response;
};
}
async function downloadFileWithProgressTrackerAsync(url, outputPath, progressTrackerMessage, progressTrackerCompletedMessage) {
log_1.default.newLine();
try {
const response = await wrapFetchWithProgress()(url, {
timeout: 1000 * 60 * 5, // 5 minutes
}, (0, progress_1.createProgressTracker)({
message: progressTrackerMessage,
completedMessage: progressTrackerCompletedMessage,
}));
if (!response.ok) {
throw new Error(`Failed to download file from ${url}`);
}
await pipeline(response.body, fs_extra_1.default.createWriteStream(outputPath));
}
catch (error) {
if (await fs_extra_1.default.pathExists(outputPath)) {
await fs_extra_1.default.remove(outputPath);
}
throw error;
}
}
async function maybeCacheAppAsync(appPath, cachedAppPath) {
if (cachedAppPath) {
await fs_extra_1.default.ensureDir(path_1.default.dirname(cachedAppPath));
await fs_extra_1.default.move(appPath, cachedAppPath);
return cachedAppPath;
}
return appPath;
}
async function downloadAndMaybeExtractAppAsync(url, platform, cachedAppPath) {
const outputDir = path_1.default.join((0, paths_1.getTmpDirectory)(), (0, uuid_1.v4)());
await fs_extra_1.default.promises.mkdir(outputDir, { recursive: true });
if (url.endsWith('apk')) {
const apkFilePath = path_1.default.join(outputDir, `${(0, uuid_1.v4)()}.apk`);
await downloadFileWithProgressTrackerAsync(url, apkFilePath, (ratio, total) => `Downloading app (${(0, files_1.formatBytes)(total * ratio)} / ${(0, files_1.formatBytes)(total)})`, 'Successfully downloaded app');
return await maybeCacheAppAsync(apkFilePath, cachedAppPath);
}
else {
const tmpArchivePathDir = path_1.default.join((0, paths_1.getTmpDirectory)(), (0, uuid_1.v4)());
await fs_extra_1.default.mkdir(tmpArchivePathDir, { recursive: true });
const tmpArchivePath = path_1.default.join(tmpArchivePathDir, `${(0, uuid_1.v4)()}.tar.gz`);
await downloadFileWithProgressTrackerAsync(url, tmpArchivePath, (ratio, total) => `Downloading app archive (${(0, files_1.formatBytes)(total * ratio)} / ${(0, files_1.formatBytes)(total)})`, 'Successfully downloaded app archive');
await tarExtractAsync(tmpArchivePath, outputDir);
const appPath = await getAppPathAsync(outputDir, platform === generated_1.AppPlatform.Ios ? 'app' : 'apk');
return await maybeCacheAppAsync(appPath, cachedAppPath);
}
}
exports.downloadAndMaybeExtractAppAsync = downloadAndMaybeExtractAppAsync;
async function extractAppFromLocalArchiveAsync(appArchivePath, platform) {
const outputDir = path_1.default.join((0, paths_1.getTmpDirectory)(), (0, uuid_1.v4)());
await fs_extra_1.default.promises.mkdir(outputDir, { recursive: true });
await tarExtractAsync(appArchivePath, outputDir);
return await getAppPathAsync(outputDir, platform === generated_1.AppPlatform.Android ? 'apk' : 'app');
}
exports.extractAppFromLocalArchiveAsync = extractAppFromLocalArchiveAsync;
async function getAppPathAsync(outputDir, applicationExtension) {
const appFilePaths = await (0, fast_glob_1.default)(`./**/*.${applicationExtension}`, {
cwd: outputDir,
onlyFiles: false,
});
if (appFilePaths.length === 0) {
throw Error('Did not find any installable apps inside tarball.');
}
if (appFilePaths.length === 1) {
return path_1.default.join(outputDir, appFilePaths[0]);
}
log_1.default.newLine();
log_1.default.log('Detected multiple apps in the tarball:');
log_1.default.newLine();
const { selectedFile } = await (0, prompts_1.promptAsync)({
type: 'select',
message: 'Select the app to run:',
name: 'selectedFile',
choices: [
...appFilePaths.map(filePath => ({
title: filePath,
value: filePath,
})),
],
});
return path_1.default.join(outputDir, selectedFile);
}
async function tarExtractAsync(input, output) {
try {
if (process.platform !== 'win32') {
await (0, spawn_async_1.default)('tar', ['-xf', input, '-C', output], {
stdio: 'inherit',
});
return;
}
}
catch (error) {
log_1.default.warn(`Failed to extract tar using native tools, falling back on JS tar module. ${error.message}`);
}
log_1.default.debug(`Extracting ${input} to ${output} using JS tar module`);
// tar node module has previously had problems with big files, and seems to
// be slower, so only use it as a backup.
await (0, tar_1.extract)({ file: input, cwd: output });
}
;