UNPKG

appcenter-cli

Version:

Command line tool for Visual Studio App Center

286 lines (285 loc) 15.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()); }); }; 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;