UNPKG

@aws-amplify/cli

Version:
169 lines (149 loc) • 5.17 kB
import fs from 'fs'; import path from 'path'; import { spawnSync } from 'child_process'; import util from 'util'; import tar from 'tar-stream'; import { createGunzip } from 'zlib'; import stream from 'stream'; import os from 'os'; import axios from 'axios'; import rimraf from 'rimraf'; import { name, version } from './package.json'; const BINARY_LOCATION = 'https://package.cli.amplify.aws'; const pipeline = util.promisify(stream.pipeline); const error = (msg: string | Error): void => { console.error(msg); process.exit(1); }; const supportedPlatforms = [ { TYPE: 'Windows_NT', ARCHITECTURE: 'x64', COMPRESSED_BINARY_PATH: 'amplify-pkg-win-x64.tgz', }, { TYPE: 'Linux', ARCHITECTURE: 'x64', COMPRESSED_BINARY_PATH: 'amplify-pkg-linux-x64.tgz', }, { TYPE: 'Linux', ARCHITECTURE: 'arm64', COMPRESSED_BINARY_PATH: 'amplify-pkg-linux-arm64.tgz', }, { TYPE: 'Darwin', ARCHITECTURE: 'x64', COMPRESSED_BINARY_PATH: 'amplify-pkg-macos-x64.tgz', }, { TYPE: 'Darwin', ARCHITECTURE: 'arm64', COMPRESSED_BINARY_PATH: 'amplify-pkg-macos-x64.tgz', }, ]; /** * Gets an object with platform information * * @returns Object */ const getPlatformCompressedBinaryName = (): string => { const type = os.type(); const architecture = os.arch(); const platform = supportedPlatforms.find((platformInfo) => type === platformInfo.TYPE && architecture === platformInfo.ARCHITECTURE); if (!platform) { error(`Platform with type "${type}" and architecture "${architecture}" is not supported by ${name}.}`); } return platform!.COMPRESSED_BINARY_PATH; }; /** * Get url where desired binary can be downloaded * * @returns string */ const getCompressedBinaryUrl = (): string => { const compressedBinaryName = getPlatformCompressedBinaryName(); let url = `${BINARY_LOCATION}/${version}/${compressedBinaryName}`; if (process.env.IS_AMPLIFY_CI && process.env.CODEBUILD_SRC_DIR) { // use cloudfront distribution for e2e url = `https://${process.env.PKG_CLI_CLOUDFRONT_URL}/${version}/${compressedBinaryName}`; } return url; }; /** * Wraps logic to download and run binary */ export class Binary { public binaryPath: string; public installDirectory: string; constructor() { this.installDirectory = path.join(os.homedir(), '.amplify', 'bin'); if (!fs.existsSync(this.installDirectory)) { fs.mkdirSync(this.installDirectory, { recursive: true }); } const amplifyExecutableName = os.type() === 'Windows_NT' ? 'amplify.exe' : 'amplify'; this.binaryPath = path.join(this.installDirectory, amplifyExecutableName); } /** * Downloads the binary to the installDirectory */ async install(): Promise<void> { if (fs.existsSync(this.installDirectory)) { rimraf.sync(this.installDirectory); } fs.mkdirSync(this.installDirectory, { recursive: true }); console.log(`Downloading release from ${getCompressedBinaryUrl()}`); try { const res = await axios({ url: getCompressedBinaryUrl(), responseType: 'stream' }); // An array to collect a promises from nested pipeline that extracts tar content to a file. // The tar file to actual file on disk streaming is kicked off by asynchronous events // of extract step. So top level pipeline may complete before streaming is completed. // We capture a Promise from that process to await it before proceeding, // so that we don't call spawnSync prematurely before content streaming completes. const extractPromiseCollector: Array<Promise<void>> = []; await pipeline(res.data, createGunzip(), this.extract(extractPromiseCollector)); await Promise.all(extractPromiseCollector); console.log('amplify has been installed!'); spawnSync(this.binaryPath, ['version'], { cwd: process.cwd(), stdio: 'inherit' }); } catch (e) { error(`Error fetching release: ${e.message}`); } } /** * Passes all arguments into the downloaded binary */ async run(): Promise<void> { if (!fs.existsSync(this.binaryPath)) { await this.install(); } const [, , ...args] = process.argv; const result = spawnSync(this.binaryPath, args, { cwd: process.cwd(), stdio: 'inherit' }); if (args[0] === 'uninstall') { spawnSync('npm', ['uninstall', '-g', '@aws-amplify/cli'], { cwd: process.cwd(), stdio: 'inherit' }); } process.exit(result.status as number); } /** * Extracts a .tar file * * @returns tar.Extract */ private extract(extractPromiseCollector: Array<Promise<void>>): tar.Extract { const extract = tar.extract(); extract.on('entry', (header, extractStream, next) => { if (header.type === 'file') { const fileWriteStream = fs.createWriteStream(this.binaryPath, { mode: 0o755, }); // pipe tar entry to file stream // and collect a promise so that top level process can await it extractPromiseCollector.push(pipeline(extractStream, fileWriteStream)); } extractStream.on('end', () => { next(); }); extractStream.resume(); }); return extract; } }