@aws-amplify/cli
Version:
169 lines (149 loc) • 5.17 kB
text/typescript
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;
}
}