UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

233 lines (232 loc) 13.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var DownloadBuildStatusCommand_1; Object.defineProperty(exports, "__esModule", { value: true }); const commandline_1 = require("../../util/commandline"); const interaction_1 = require("../../util/interaction"); const util_1 = require("util"); const _ = require("lodash"); const Process = require("process"); const appcenter_file_upload_client_node_1 = require("appcenter-file-upload-client-node"); const JsZip = require("jszip"); const JsZipHelper = require("../../util/misc/jszip-helper"); const Path = require("path"); const Pfs = require("../../util/misc/promisfied-fs"); const mkdirp = require("mkdirp"); const debug = require("debug")("appcenter-cli:commands:build:download"); let DownloadBuildStatusCommand = DownloadBuildStatusCommand_1 = class DownloadBuildStatusCommand extends commandline_1.AppCommand { run(client) { return __awaiter(this, void 0, void 0, function* () { this.type = this.getNormalizedTypeValue(this.type); const buildIdNumber = this.getNormalizedBuildId(this.buildId); // set directory to current if it is not specified if (_.isNil(this.directory)) { this.directory = Process.cwd(); } const app = this.app; debug(`Getting build status`); const buildInfo = yield this.getBuildStatus(client, app, buildIdNumber); debug(`Getting download URL for ${this.type}`); const uri = yield this.getDownloadUri(client, app, buildIdNumber); debug(`Downloading content from ${uri}`); const downloadedContent = yield this.downloadContent(uri); debug(`Creating (if necessary) destination folder ${this.directory}`); yield interaction_1.out.progress("Creating destination folder... ", mkdirp(this.directory)); let outputPath; if (this.type === DownloadBuildStatusCommand_1.buildType) { debug("Reading received ZIP archive"); const zip = yield interaction_1.out.progress("Reading downloaded ZIP...", new JsZip().loadAsync(downloadedContent)); const payloadZipEntry = this.getPayload(zip); const extension = Path.extname(payloadZipEntry.name).substring(1); if (payloadZipEntry.dir) { // xcarchive outputPath = yield interaction_1.out.progress("Unpacking .xcarchive folder...", this.unpackAndWriteDirectory(zip, extension, buildInfo.sourceBranch, payloadZipEntry.name)); } else { // IPA or AAB or APK const payload = yield interaction_1.out.progress("Extracting application package...", payloadZipEntry.async("nodebuffer")); outputPath = yield interaction_1.out.progress("Writing application package...", this.writeFile(payload, extension, buildInfo.sourceBranch)); } } else { outputPath = yield this.writeFile(downloadedContent, "zip", buildInfo.sourceBranch); } interaction_1.out.text((pathObject) => `Downloaded content was saved to ${pathObject.path}`, { path: Path.resolve(outputPath) }); return commandline_1.success(); }); } downloadFile(uri) { return __awaiter(this, void 0, void 0, function* () { const response = yield appcenter_file_upload_client_node_1.fetchWithOptions(uri, { compress: false, }); return response; }); } generateNameForOutputFile(branchName, extension) { return __awaiter(this, void 0, void 0, function* () { if (this.file) { return this.file.includes(extension) ? this.file : `${this.file}.${extension}`; } // file name should be unique for the directory const filesInDirectory = (yield Pfs.readdir(this.directory)).map((name) => name.toLowerCase()); let id = 1; let newFileName; do { const encodedBranchName = encodeURIComponent(branchName); newFileName = `${this.type}_${encodedBranchName}_${this.buildId}_${id++}.${extension}`; } while (_.includes(filesInDirectory, newFileName.toLowerCase())); return newFileName; }); } getNormalizedTypeValue(type) { const lowerCaseType = type.toLowerCase(); if (lowerCaseType !== DownloadBuildStatusCommand_1.buildType && lowerCaseType !== DownloadBuildStatusCommand_1.bundleType && lowerCaseType !== DownloadBuildStatusCommand_1.logsType && lowerCaseType !== DownloadBuildStatusCommand_1.symbolsType) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `download type should be '${DownloadBuildStatusCommand_1.bundleType}', ${DownloadBuildStatusCommand_1.buildType}', '${DownloadBuildStatusCommand_1.logsType}' or '${DownloadBuildStatusCommand_1.symbolsType}'`); } return lowerCaseType; } getNormalizedBuildId(buildId) { const buildIdNumber = Number(this.buildId); if (!Number.isSafeInteger(buildIdNumber) || buildIdNumber < 1) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, "build id should be positive integer"); } return buildIdNumber; } getBuildStatus(client, app, buildIdNumber) { return __awaiter(this, void 0, void 0, function* () { try { const buildInfo = yield interaction_1.out.progress(`Getting status of build ${this.buildId}...`, client.builds.get(buildIdNumber, app.ownerName, app.appName)); if (buildInfo.status !== DownloadBuildStatusCommand_1.completedStatus) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `cannot download ${this.type} for an uncompleted build`); } if (buildInfo.result === DownloadBuildStatusCommand_1.failedResult && this.type !== DownloadBuildStatusCommand_1.logsType) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `no ${this.type} to download - build failed`); } return buildInfo; } catch (error) { if (error.statusCode === 404) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `build ${buildIdNumber} was not found`); } else { debug(`Request failed - ${util_1.inspect(error)}`); throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to get status of build ${this.buildId}`); } } }); } getDownloadUri(client, app, buildIdNumber) { return __awaiter(this, void 0, void 0, function* () { try { const downloadDataResponse = yield interaction_1.out.progress(`Getting ${this.type} download URL for build ${this.buildId}...`, client.builds.getDownloadUri(buildIdNumber, this.type, app.ownerName, app.appName)); return downloadDataResponse.uri; } catch (error) { debug(`Request failed - ${util_1.inspect(error)}`); throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to get ${this.type} downloading URL for build ${this.buildId}`); } }); } downloadContent(uri) { return __awaiter(this, void 0, void 0, function* () { let downloadFileRequestResponse; try { downloadFileRequestResponse = yield interaction_1.out.progress(`Loading ${this.type} for build ${this.buildId}...`, this.downloadFile(uri)); } catch (error) { debug(`File download failed - ${util_1.inspect(error)}`); throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to load file with ${this.type} for build ${this.buildId}`); } const statusCode = downloadFileRequestResponse.status; if (statusCode >= 400) { switch (statusCode) { case 404: throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `unable to find ${this.type} for build ${this.buildId}`); default: throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to load file with ${this.type} for build ${this.buildId} - HTTP ${statusCode} ${downloadFileRequestResponse.statusText}`); } } return yield downloadFileRequestResponse.buffer(); }); } getPayload(zip) { // looking for aab, apk, ipa or xcarchive return _.find(_.values(zip.files), (file) => _.includes(DownloadBuildStatusCommand_1.applicationPackagesExtensions, Path.extname(file.name).toLowerCase())); } writeFile(buffer, extension, sourceBranch) { return __awaiter(this, void 0, void 0, function* () { debug("Preparing name for resulting file"); const fileName = yield this.generateNameForOutputFile(sourceBranch, extension); debug(`Writing file ${fileName}`); const filePath = Path.join(this.directory, fileName); yield Pfs.writeFile(filePath, buffer); return filePath; }); } unpackAndWriteDirectory(directoryZip, extension, sourceBranch, root) { return __awaiter(this, void 0, void 0, function* () { debug("Preparing name for resulting directory"); const directoryName = yield this.generateNameForOutputFile(sourceBranch, extension); debug(`Writing xcarchive directory ${directoryName}`); const directoryPath = Path.join(this.directory, directoryName); yield mkdirp(directoryPath); yield JsZipHelper.unpackZipToPath(directoryPath, directoryZip, root); return directoryPath; }); } }; DownloadBuildStatusCommand.applicationPackagesExtensions = [".apk", ".aar", ".ipa", ".xcarchive", ".aab"]; DownloadBuildStatusCommand.buildType = "build"; DownloadBuildStatusCommand.bundleType = "bundle"; DownloadBuildStatusCommand.logsType = "logs"; DownloadBuildStatusCommand.symbolsType = "symbols"; DownloadBuildStatusCommand.failedResult = "failed"; DownloadBuildStatusCommand.completedStatus = "completed"; __decorate([ commandline_1.help("ID of build to download"), commandline_1.shortName("i"), commandline_1.longName("id"), commandline_1.required, commandline_1.hasArg ], DownloadBuildStatusCommand.prototype, "buildId", void 0); __decorate([ commandline_1.help(`Type of download. '${DownloadBuildStatusCommand_1.buildType}', '${DownloadBuildStatusCommand_1.bundleType}', '${DownloadBuildStatusCommand_1.logsType}', and '${DownloadBuildStatusCommand_1.symbolsType}' are allowed values`), commandline_1.shortName("t"), commandline_1.longName("type"), commandline_1.required, commandline_1.hasArg ], DownloadBuildStatusCommand.prototype, "type", void 0); __decorate([ commandline_1.help("Destination path. Optional parameter to override the default destination path of the downloaded build"), commandline_1.shortName("d"), commandline_1.longName("dest"), commandline_1.hasArg ], DownloadBuildStatusCommand.prototype, "directory", void 0); __decorate([ commandline_1.help("Destination file. Optional parameter to override the default auto-generated file name"), commandline_1.shortName("f"), commandline_1.longName("file"), commandline_1.hasArg ], DownloadBuildStatusCommand.prototype, "file", void 0); DownloadBuildStatusCommand = DownloadBuildStatusCommand_1 = __decorate([ commandline_1.help("Download the binary, logs or symbols for a completed build") ], DownloadBuildStatusCommand); exports.default = DownloadBuildStatusCommand;