appcenter-cli
Version:
Command line tool for Visual Studio App Center
306 lines (305 loc) • 15.4 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) {
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;