appcenter-cli
Version:
Command line tool for Visual Studio App Center
233 lines (232 loc) • 13.3 kB
JavaScript
;
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;