appcenter-cli
Version:
Command line tool for Visual Studio App Center
286 lines (285 loc) • 15.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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const commandline_1 = require("../../util/commandline");
const commandline_2 = require("../../util/commandline");
const commandline_3 = require("../../util/commandline");
const interaction_1 = require("../../util/interaction");
const util_1 = require("util");
const symbols_uploading_helper_1 = require("./lib/symbols-uploading-helper");
const subfolder_symbols_helper_1 = require("./lib/subfolder-symbols-helper");
const temp_zip_file_helper_1 = require("./lib/temp-zip-file-helper");
const symbols_uploading_helper_2 = require("./lib/symbols-uploading-helper");
const Fs = require("fs");
const Pfs = require("../../util/misc/promisfied-fs");
const Path = require("path");
const JsZip = require("jszip");
const JsZipHelper = require("../../util/misc/jszip-helper");
const _ = require("lodash");
const debug = require("debug")("appcenter-cli:commands:apps:crashes:upload-symbols");
var SymbolFsEntryType;
(function (SymbolFsEntryType) {
SymbolFsEntryType[SymbolFsEntryType["Unknown"] = 0] = "Unknown";
SymbolFsEntryType[SymbolFsEntryType["DsymFolder"] = 1] = "DsymFolder";
SymbolFsEntryType[SymbolFsEntryType["DsymParentFolder"] = 2] = "DsymParentFolder";
SymbolFsEntryType[SymbolFsEntryType["XcArchive"] = 3] = "XcArchive";
SymbolFsEntryType[SymbolFsEntryType["ZipFile"] = 4] = "ZipFile";
})(SymbolFsEntryType || (SymbolFsEntryType = {}));
let UploadSymbols = class UploadSymbols extends commandline_1.AppCommand {
run(client) {
return __awaiter(this, void 0, void 0, function* () {
const app = this.app;
this.validateParameters();
let zip; // it is either JsZip object or path to ZIP file
let symbolType;
if (!_.isNil(this.symbolsPath)) {
// processing -s switch value
zip = yield interaction_1.out.progress("Preparing ZIP with symbols...", this.prepareZipFromSymbols(this.symbolsPath));
symbolType = symbols_uploading_helper_2.SymbolType.Apple;
}
else if (!_.isNil(this.breakpadPath)) {
zip = yield interaction_1.out.progress("Preparing ZIP with Breakpad symbols...", this.prepareZipFromSymbols(this.breakpadPath));
symbolType = symbols_uploading_helper_2.SymbolType.Breakpad;
}
else if (!_.isNil(this.appxSymPath)) {
if (this.getLowerCasedFileExtension(this.appxSymPath) !== ".appxsym") {
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `${this.appxSymPath} is not a valid appxsym file`);
}
zip = this.appxSymPath;
symbolType = symbols_uploading_helper_2.SymbolType.UWP;
}
else {
// process -x switch value
zip = yield interaction_1.out.progress("Preparing ZIP with symbols from xcarchive...", this.prepareZipFromXcArchive(this.xcarchivePath));
symbolType = symbols_uploading_helper_2.SymbolType.Apple;
}
// process -m switch if specified
if (!_.isNil(this.sourceMapPath)) {
// load current ZIP, add/replace symbol file, return stream to new zip
zip = yield interaction_1.out.progress("Adding source map file to ZIP...", this.addSourceMapFileToZip(this.sourceMapPath, zip));
}
let pathToZipToUpload;
if (typeof zip === "string") {
// path to zip can be passed as it is
pathToZipToUpload = zip;
}
else {
// JsZip object should be written to temp file first
pathToZipToUpload = yield temp_zip_file_helper_1.createTempFileFromZip(zip);
}
// upload symbols
yield interaction_1.out.progress("Uploading symbols...", new symbols_uploading_helper_1.default(client, app, debug).uploadSymbolsArtifact(pathToZipToUpload, { symbolType }));
return commandline_2.success();
});
}
getStatsForFsPath(filePath) {
// take fs entry stats (and check it's existence BTW)
try {
debug(`Getting FS statistics for ${filePath}`);
return Fs.statSync(filePath);
}
catch (error) {
if (error.code === "ENOENT") {
// path points to non-existing file system entry
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `path ${filePath} points to non-existent item`);
}
else {
// other errors
debug(`Failed to get statistics for file system entry ${filePath} - ${util_1.inspect(error)}`);
throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `failed to get statistics for file system entry ${filePath}`);
}
}
}
detectSymbolsFsEntryType(filePath, fsEntryStats) {
if (fsEntryStats.isDirectory()) {
// check if it is a dSYM or xcarchive directory
switch (this.getLowerCasedFileExtension(filePath)) {
case ".dsym":
return SymbolFsEntryType.DsymFolder;
case ".xcarchive":
return SymbolFsEntryType.XcArchive;
default:
// test if folder contains .dsym sub-folders
return subfolder_symbols_helper_1.getChildrenDsymFolderPaths(filePath, debug).length > 0
? SymbolFsEntryType.DsymParentFolder
: SymbolFsEntryType.Unknown;
}
}
else if (fsEntryStats.isFile()) {
// check if it is a ZIP file
return this.getLowerCasedFileExtension(filePath) === ".zip" ? SymbolFsEntryType.ZipFile : SymbolFsEntryType.Unknown;
}
// everything else
return SymbolFsEntryType.Unknown;
}
getLowerCasedFileExtension(filePath) {
return Path.extname(filePath).toLowerCase();
}
packDsymFolder(pathToFolder) {
return __awaiter(this, void 0, void 0, function* () {
debug(`Compressing the specified folder ${pathToFolder} to the in-memory ZIP archive`);
const zipArchive = new JsZip();
try {
yield JsZipHelper.addFolderToZipRecursively(pathToFolder, zipArchive);
}
catch (error) {
debug(`Unable to add folder ${pathToFolder} to the ZIP archive - ${util_1.inspect(error)}`);
throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `unable to add folder ${pathToFolder} to the ZIP archive`);
}
return zipArchive;
});
}
prepareZipFromSymbols(path) {
return __awaiter(this, void 0, void 0, function* () {
debug("Trying to prepare ZIP file from symbols");
const fsEntryStats = this.getStatsForFsPath(path);
const symbolsType = this.detectSymbolsFsEntryType(path, fsEntryStats);
switch (symbolsType) {
case SymbolFsEntryType.DsymFolder:
// dSYM Folder needs to be packed to the temp ZIP before uploading
return yield this.packDsymFolder(path);
case SymbolFsEntryType.DsymParentFolder:
// only child DSYM folders should be compressed
return yield subfolder_symbols_helper_1.packDsymParentFolderContents(path, debug);
case SymbolFsEntryType.ZipFile:
// *.ZIP file can be uploaded as it is
return path;
default:
// file doesn't points to correct symbols
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `${path} is not a valid symbols file/directory`);
}
});
}
validateParameters() {
const joinArgs = (args, cnj) => {
args = args.map((arg) => `'--${this.commandOptions[arg].longName}'`);
const commaJoinedArgs = args.slice(0, _.findLastIndex(args)).join(", ");
return `${commaJoinedArgs} ${cnj} ${_.last(args)}`;
};
const mutuallyExclusivePropsNames = ["symbolsPath", "breakpadPath", "xcarchivePath", "appxSymPath"];
const mutuallyExclusiveProps = mutuallyExclusivePropsNames.reduce((prev, next) => {
prev[next] = this[next];
return prev;
}, {});
const providedOptions = _.keys(_.omitBy(mutuallyExclusiveProps, _.isNil));
if (providedOptions.length === 0) {
const args = joinArgs(mutuallyExclusivePropsNames, "or");
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `specify either ${args} switch`);
}
if (providedOptions.length > 1) {
const args = joinArgs(providedOptions, "and");
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `${args} switches are mutually exclusive`);
}
if (!_.isNil(this.breakpadPath) && !_.isNil(this.sourceMapPath)) {
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, "'--breakpad' and '--sourcemap' switches are mutually exclusive");
}
}
addSourceMapFileToZip(path, zip) {
return __awaiter(this, void 0, void 0, function* () {
debug("Checking if the specified mappings file is valid");
// checking if it points to the *.map file
if (this.getLowerCasedFileExtension(path) !== ".map") {
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `${path} is not a map file`);
}
// getting statistics for the map file
const sourceMapFileStats = this.getStatsForFsPath(path);
// checking if source map file is actually a file
if (!sourceMapFileStats.isFile()) {
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `${path} is not a file`);
}
let zipToChange;
if (typeof zip === "string") {
// loading ZIP to add file to it
debug("Loading ZIP into the memory to add files");
try {
const mapFileContent = yield Pfs.readFile(zip);
zipToChange = yield new JsZip().loadAsync(mapFileContent);
}
catch (error) {
debug(`Failed to load ZIP ${zip} - ${util_1.inspect(error)}`);
throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `failed to load ZIP ${zip}`);
}
}
else {
// ZIP is already loaded, working with it
zipToChange = zip;
}
// adding (or replacing) source map file
const sourceMapFileBaseName = Path.basename(path);
debug(zipToChange.file(sourceMapFileBaseName)
? "Replacing existing mappings file with the same name in the ZIP"
: "Adding the specified mappings file to the ZIP");
try {
const sourceMapFileBuffer = yield Pfs.readFile(path);
zipToChange.file(sourceMapFileBaseName, sourceMapFileBuffer);
return zipToChange;
}
catch (error) {
debug(`Unable to add file ${path} to the ZIP - ${util_1.inspect(error)}`);
throw commandline_2.failure(commandline_2.ErrorCodes.Exception, `unable to add file ${path} to the ZIP`);
}
});
}
prepareZipFromXcArchive(path) {
return __awaiter(this, void 0, void 0, function* () {
debug(`Trying to prepare the ZIP archive with symbols from .xcarchive folder`);
const fsEntryStats = this.getStatsForFsPath(path);
const symbolsType = this.detectSymbolsFsEntryType(path, fsEntryStats);
switch (symbolsType) {
case SymbolFsEntryType.XcArchive:
// the DSYM folders from "*.xcarchive/dSYMs" should be compressed
return yield subfolder_symbols_helper_1.getSymbolsZipFromXcarchive(path, debug);
default:
// file doesn't points to correct .xcarchive
throw commandline_2.failure(commandline_2.ErrorCodes.InvalidParameter, `${path} is not a valid XcArchive folder`);
}
});
}
};
__decorate([
commandline_3.help("Path to a dSYM package, a directory containing dSYM packages, or a zip file containing the dSYM packages."),
commandline_3.shortName("s"),
commandline_3.longName("symbol"),
commandline_3.hasArg
], UploadSymbols.prototype, "symbolsPath", void 0);
__decorate([
commandline_3.help("Path to a xcarchive package"),
commandline_3.shortName("x"),
commandline_3.longName("xcarchive"),
commandline_3.hasArg
], UploadSymbols.prototype, "xcarchivePath", void 0);
__decorate([
commandline_3.help("Path to a React Native sourcemap file. Only supported in combination with --symbol or --xcarchive."),
commandline_3.shortName("m"),
commandline_3.longName("sourcemap"),
commandline_3.hasArg
], UploadSymbols.prototype, "sourceMapPath", void 0);
__decorate([
commandline_3.help("Path to a zip file containing Breakpad symbols, or native binaries from which to generate Breakpad symbols."),
commandline_3.shortName("b"),
commandline_3.longName("breakpad"),
commandline_3.hasArg
], UploadSymbols.prototype, "breakpadPath", void 0);
__decorate([
commandline_3.help("Path to an appxsym file containing UWP symbols."),
commandline_3.longName("appxsym"),
commandline_3.hasArg
], UploadSymbols.prototype, "appxSymPath", void 0);
UploadSymbols = __decorate([
commandline_3.help("Upload the crash symbols for the application")
], UploadSymbols);
exports.default = UploadSymbols;