UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

306 lines (305 loc) 15.4 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) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const commandline_1 = require("../../util/commandline"); const apis_1 = require("../../util/apis"); const interaction_1 = require("../../util/interaction"); const util_1 = require("util"); const _ = require("lodash"); const Request = require("request"); const Path = require("path"); const Pfs = require("../../util/misc/promisfied-fs"); const distribute_util_1 = require("./lib/distribute-util"); const debug = require("debug")("appcenter-cli:commands:distribute:release"); let ReleaseBinaryCommand = class ReleaseBinaryCommand extends commandline_1.AppCommand { run(client) { return __awaiter(this, void 0, void 0, function* () { const app = this.app; debug("Check that user hasn't selected both --release-notes and --release-notes-file"); this.validateParameters(); debug("Loading prerequisites"); const [distributionGroupUsersCount, releaseBinaryFileBuffer, releaseNotesString] = yield interaction_1.out.progress("Loading prerequisites...", this.getPrerequisites(client)); debug("Creating release upload"); const createdReleaseUpload = yield this.createReleaseUpload(client, app); const uploadUri = createdReleaseUpload.uploadUrl; const uploadId = createdReleaseUpload.uploadId; let releaseUrl; try { debug("Uploading release binary"); yield interaction_1.out.progress("Uploading release binary...", this.uploadFileToUri(uploadUri, releaseBinaryFileBuffer, Path.basename(this.filePath))); debug("Finishing release upload"); releaseUrl = yield this.finishReleaseUpload(client, app, uploadId); } catch (error) { try { interaction_1.out.text("Release upload failed"); yield this.abortReleaseUpload(client, app, uploadId); interaction_1.out.text("Release upload was aborted"); } catch (abortError) { debug("Failed to abort release upload"); } throw error; } debug("Extracting release ID from the release URL"); const releaseId = this.extractReleaseId(releaseUrl); debug("Distributing the release"); yield this.distributeRelease(client, app, releaseId, releaseNotesString); debug("Retrieving the release"); const releaseDetails = yield this.getDistributeRelease(client, app, releaseId); if (releaseDetails) { if (_.isNull(distributionGroupUsersCount)) { interaction_1.out.text((rd) => `Release ${rd.shortVersion} (${rd.version}) was successfully released to ${this.distributionGroup}`, releaseDetails); } else { interaction_1.out.text((rd) => `Release ${rd.shortVersion} (${rd.version}) was successfully released to ${distributionGroupUsersCount} testers in ${this.distributionGroup}`, releaseDetails); } } else { interaction_1.out.text(`Release was successfully released.`); } return commandline_1.success(); }); } validateParameters() { if (!_.isNil(this.releaseNotes) && !_.isNil(this.releaseNotesFile)) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, "'--release-notes' and '--release-notes-file' switches are mutually exclusive"); } } getPrerequisites(client) { // load release binary file const fileBuffer = this.getReleaseFileBuffer(); // load release notes file or use provided release notes if none was specified const releaseNotesString = this.getReleaseNotesString(); // get number of distribution group users (and check distribution group existence) // return null if request has failed because of any reason except non-existing group name. const distributionGroupUsersNumber = this.getDistributionGroupUsersNumber(client); return Promise.all([distributionGroupUsersNumber, fileBuffer, releaseNotesString]); } getReleaseFileBuffer() { return __awaiter(this, void 0, void 0, function* () { try { return yield Pfs.readFile(this.filePath); } catch (error) { if (error.code === "ENOENT") { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `binary file '${this.filePath}' doesn't exist`); } else { throw error; } } }); } getReleaseNotesString() { return __awaiter(this, void 0, void 0, function* () { if (!_.isNil(this.releaseNotesFile)) { try { return yield Pfs.readFile(this.releaseNotesFile, "utf8"); } catch (error) { if (error.code === "ENOENT") { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `release notes file '${this.releaseNotesFile}' doesn't exist`); } else { throw error; } } } else { return this.releaseNotes; } }); } getDistributionGroupUsersNumber(client) { return __awaiter(this, void 0, void 0, function* () { let distributionGroupUsersRequestResponse; try { distributionGroupUsersRequestResponse = yield apis_1.clientRequest((cb) => client.distributionGroups.listUsers(this.app.ownerName, this.app.appName, this.distributionGroup, cb)); const statusCode = distributionGroupUsersRequestResponse.response.statusCode; if (statusCode >= 400) { throw statusCode; } } catch (error) { if (error === 404) { throw commandline_1.failure(commandline_1.ErrorCodes.InvalidParameter, `distribution group ${this.distributionGroup} was not found`); } else { debug(`Failed to get users of distribution group ${this.distributionGroup}, returning null - ${util_1.inspect(error)}`); return null; } } return distributionGroupUsersRequestResponse.result.length; }); } createReleaseUpload(client, app) { return __awaiter(this, void 0, void 0, function* () { let createReleaseUploadRequestResponse; try { createReleaseUploadRequestResponse = yield interaction_1.out.progress("Creating release upload...", apis_1.clientRequest((cb) => client.releaseUploads.create(app.ownerName, app.appName, cb))); } catch (error) { throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to create release upload for ${this.filePath}`); } return createReleaseUploadRequestResponse.result; }); } uploadFileToUri(uploadUrl, fileBuffer, filename) { debug("Uploading the release binary"); return new Promise((resolve, reject) => { Request.post({ formData: { ipa: { options: { filename, contentType: "application/octet-stream" }, value: fileBuffer } }, url: uploadUrl }) .on("error", (error) => { reject(commandline_1.failure(commandline_1.ErrorCodes.Exception, `release binary uploading failed: ${error.message}`)); }) .on("response", (response) => { if (response.statusCode < 400) { resolve(); } else { reject(commandline_1.failure(commandline_1.ErrorCodes.Exception, `release binary file uploading failed: HTTP ${response.statusCode} ${response.statusMessage}`)); } }); }); } finishReleaseUpload(client, app, uploadId) { return __awaiter(this, void 0, void 0, function* () { let finishReleaseUploadRequestResponse; try { finishReleaseUploadRequestResponse = yield interaction_1.out.progress("Finishing release upload...", apis_1.clientRequest((cb) => client.releaseUploads.complete(uploadId, app.ownerName, app.appName, "committed", cb))); } catch (error) { throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to finish release upload for ${this.filePath}`); } return finishReleaseUploadRequestResponse.result.releaseUrl; }); } abortReleaseUpload(client, app, uploadId) { return __awaiter(this, void 0, void 0, function* () { let abortReleaseUploadRequestResponse; try { abortReleaseUploadRequestResponse = yield interaction_1.out.progress("Aborting release upload...", apis_1.clientRequest((cb) => client.releaseUploads.complete(uploadId, app.ownerName, app.appName, "aborted", cb))); } catch (error) { throw new Error(`HTTP ${abortReleaseUploadRequestResponse.response.statusCode} - ${abortReleaseUploadRequestResponse.response.statusMessage}`); } }); } extractReleaseId(releaseUrl) { const releaseId = Number(_(releaseUrl).split("/").last()); console.assert(Number.isSafeInteger(releaseId) && releaseId > 0, `API returned unexpected release URL: ${releaseUrl}`); return releaseId; } getDistributeRelease(client, app, releaseId) { return __awaiter(this, void 0, void 0, function* () { let releaseRequestResponse; try { releaseRequestResponse = yield interaction_1.out.progress(`Retrieving the release...`, apis_1.clientRequest((cb) => __awaiter(this, void 0, void 0, function* () { return client.releases.getLatestByUser(releaseId.toString(), app.ownerName, app.appName, cb); }))); } catch (error) { if (error === 400) { throw commandline_1.failure(commandline_1.ErrorCodes.Exception, "release_id is not an integer or the string latest"); } else if (error === 404) { throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `The release ${releaseId} can't be found`); } else { return null; } } return releaseRequestResponse.result; }); } putReleaseDetails(client, app, releaseId, releaseNotesString) { return __awaiter(this, void 0, void 0, function* () { try { const { result, response } = yield interaction_1.out.progress(`Updating release details...`, apis_1.clientRequest((cb) => __awaiter(this, void 0, void 0, function* () { return client.releases.updateDetails(releaseId, app.ownerName, app.appName, { releaseNotes: releaseNotesString, }, cb); }))); const statusCode = response.statusCode; if (statusCode >= 400) { throw statusCode; } return result; } catch (error) { if (error === 400) { throw commandline_1.failure(commandline_1.ErrorCodes.Exception, "changing distribution group is not supported"); } else { debug(`Failed to distribute the release - ${util_1.inspect(error)}`); throw commandline_1.failure(commandline_1.ErrorCodes.Exception, `failed to set distribution group and release notes for release ${releaseId}`); } } }); } distributeRelease(client, app, releaseId, releaseNotesString) { return __awaiter(this, void 0, void 0, function* () { yield this.putReleaseDetails(client, app, releaseId, releaseNotesString); const distributionGroupResponse = yield distribute_util_1.getDistributionGroup({ client, releaseId, app: this.app, destination: this.distributionGroup, destinationType: "group" }); yield distribute_util_1.addGroupToRelease({ client, releaseId, distributionGroup: distributionGroupResponse, app: this.app, destination: this.distributionGroup, destinationType: "group", mandatory: false, silent: false }); }); } }; __decorate([ commandline_1.help("Path to binary file"), commandline_1.shortName("f"), commandline_1.longName("file"), commandline_1.required, commandline_1.hasArg ], ReleaseBinaryCommand.prototype, "filePath", void 0); __decorate([ commandline_1.help("Distribution group name"), commandline_1.shortName("g"), commandline_1.longName("group"), commandline_1.required, commandline_1.hasArg ], ReleaseBinaryCommand.prototype, "distributionGroup", void 0); __decorate([ commandline_1.help("Release notes text"), commandline_1.shortName("r"), commandline_1.longName("release-notes"), commandline_1.hasArg ], ReleaseBinaryCommand.prototype, "releaseNotes", void 0); __decorate([ commandline_1.help("Path to release notes file"), commandline_1.shortName("R"), commandline_1.longName("release-notes-file"), commandline_1.hasArg ], ReleaseBinaryCommand.prototype, "releaseNotesFile", void 0); ReleaseBinaryCommand = __decorate([ commandline_1.help("Upload release binary and trigger distribution") ], ReleaseBinaryCommand); exports.default = ReleaseBinaryCommand;