UNPKG

eas-cli

Version:
157 lines (156 loc) 7.25 kB
"use strict"; 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 }); }