UNPKG

@splunk/rum-cli

Version:

Tools for handling symbol and mapping files for symbolication

308 lines (307 loc) 16.9 kB
"use strict"; /* * Copyright Splunk Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.androidCommand = void 0; const commander_1 = require("commander"); const androidManifestUtils_1 = require("../utils/androidManifestUtils"); const inputValidations_1 = require("../utils/inputValidations"); const constants_1 = require("../utils/constants"); const userFriendlyErrors_1 = require("../utils/userFriendlyErrors"); const logger_1 = require("../utils/logger"); const httpUtils_1 = require("../utils/httpUtils"); const axios_1 = __importDefault(require("axios")); const spinner_1 = require("../utils/spinner"); const metadataFormatUtils_1 = require("../utils/metadataFormatUtils"); const path_1 = __importDefault(require("path")); const apiInterceptor_1 = require("../utils/apiInterceptor"); exports.androidCommand = new commander_1.Command('android'); const generateURL = (type, realm, appId, versionCode, splunkBuildId) => { const baseUrl = `${constants_1.BASE_URL_PREFIX}.${realm}.signalfx.com/${constants_1.API_VERSION_STRING}/${constants_1.ANDROID_CONSTANTS.PATH_FOR_UPLOAD}`; if (type === 'upload') { if (!versionCode) throw new Error('Version code is required for uploading.'); let uploadUrl = `${baseUrl}/${appId}/${versionCode}`; if (splunkBuildId) { uploadUrl += `/${splunkBuildId}`; } return uploadUrl; } if (type === 'list') { return `${baseUrl}/${appId}/metadatas`; } throw new Error('Invalid URL type specified.'); }; const androidUploadDescription = ` This command uploads the provided mapping.txt file. You need to provide the Application ID and version code of the app, and the path to the mapping file. Optionally, you can also include a Splunk Build ID to identify the different pre-production app builds. `; const androidUploadWithManifestDescription = ` This command uploads the provided file using the packaged AndroidManifest.xml provided. You need to provide the path to the mapping file, and the path to the AndroidManifest.xml file. The application ID, version code, and optional Splunk Build ID will be extracted from the manifest file. This command is recommended if you want to automate the upload process without manually specifying the application details. `; const listProguardDescription = ` This command retrieves and lists the metadata of the uploaded ProGuard mapping files. By default, it will return the last 100 ProGuard mapping files uploaded, sorted in reverse chronological order based on the upload timestamp. `; const shortDescription = 'Upload and list zipped or unzipped Proguard/R8 mapping.txt files'; const detailedHelp = `For each respective command listed below under 'Commands', please run 'splunk-rum android <command> --help' for an overview of its usage and options`; exports.androidCommand .description(shortDescription) .usage('[command] [options]'); exports.androidCommand.configureHelp({ commandDescription: (cmd) => { return `${cmd.description()}\n\n${detailedHelp}`; } }); exports.androidCommand .command('upload') .showHelpAfterError(inputValidations_1.COMMON_ERROR_MESSAGES.HELP_MESSAGE_AFTER_ERROR) .usage('--app-id <value> --version-code <int> --path <path> [--splunk-build-id <value>]') .description(androidUploadDescription) .summary(`Uploads the Android mapping.txt file with the provided application ID, version code, and optional Splunk Build ID`) .requiredOption('--app-id <value>', 'Application ID') .requiredOption('--version-code <int>', 'Version code') .requiredOption('--path <path>', 'Path to the mapping file') .requiredOption('--realm <value>', 'Realm for your organization (example: us0). Can also be set using the environment variable SPLUNK_REALM', process.env.SPLUNK_REALM) .option('--token <value>', 'API access token. Can also be set using the environment variable SPLUNK_ACCESS_TOKEN') .option('--splunk-build-id <value>', 'Optional Splunk Build ID for the upload') .option('--dry-run', 'Preview the file that will be uploaded') .option('--debug', 'Enable debug logs') .action((options) => __awaiter(void 0, void 0, void 0, function* () { const spinner = (0, spinner_1.createSpinner)(); const logger = (0, logger_1.createLogger)(options.debug ? 1 /* LogLevel.DEBUG */ : 2 /* LogLevel.INFO */, spinner); try { const token = (0, inputValidations_1.validateAndPrepareToken)(options); if (!options.realm || options.realm.trim() === '') { exports.androidCommand.error(inputValidations_1.COMMON_ERROR_MESSAGES.REALM_NOT_SPECIFIED); } logger.debug(`Validating App ID: ${options.appId}`); if (!(0, inputValidations_1.isValidAppId)(options.appId)) { throw new userFriendlyErrors_1.UserFriendlyError(null, 'Invalid Application ID. It must be a non-empty string.'); } logger.debug(`Validating Version Code: ${options.versionCode}`); if (!(0, inputValidations_1.isValidVersionCode)(options.versionCode)) { throw new userFriendlyErrors_1.UserFriendlyError(null, 'Invalid Version Code. It must be an integer.'); } logger.debug(`Validating Mapping File Path: ${options.path}`); if (!(0, inputValidations_1.isValidFile)(options.path)) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Invalid mapping file path: ${options.path}.`); } logger.debug(`Validating Mapping File Extension`); if (!(0, inputValidations_1.hasValidExtension)(options.path, '.txt', '.gz')) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Mapping file does not have correct extension: ${options.path}.`); } logger.debug(`Validating optional Splunk Build ID: ${options.splunkBuildId}`); if (options.splunkBuildId && !(0, inputValidations_1.isValidSplunkBuildId)(options.splunkBuildId)) { throw new userFriendlyErrors_1.UserFriendlyError(null, 'Error: Invalid Splunk Build ID. It must be a non-empty string.'); } logger.info(`Preparing to upload Android mapping file: File: ${options.path} App ID: ${options.appId} Version Code: ${options.versionCode} Splunk Build ID: ${options.splunkBuildId || 'Not provided'}`); if (options.dryRun) { logger.info('Dry Run complete - No file will be uploaded.'); return; } const url = generateURL('upload', options.realm, options.appId, options.versionCode, options.splunkBuildId); logger.debug(`URL Endpoint: ${url}`); spinner.start(`Uploading Android mapping file: ${options.path}`); const axiosInstance = axios_1.default.create(); (0, apiInterceptor_1.attachApiInterceptor)(axiosInstance, logger, url, { userFriendlyMessage: 'An error occurred during mapping file upload.' }); yield (0, httpUtils_1.uploadFile)({ url: url, file: { filePath: options.path, fieldName: 'file' }, token: token, parameters: { filename: path_1.default.basename(options.path) }, onProgress: options.debug ? (progressData) => { spinner.updateText(`Uploading: ${Math.round(progressData.progress)}%`); } : undefined, }, axiosInstance); spinner.stop(); logger.info(`Upload complete`); } catch (err) { spinner.stop(); if (err instanceof userFriendlyErrors_1.UserFriendlyError) { logger.error(err.message); if (options.debug && err.originalError) { logger.debug('Error details:', err.originalError); } } else { logger.error('An unexpected error occurred:'); logger.error(err); } process.exit(1); } })); exports.androidCommand .command('upload-with-manifest') .showHelpAfterError(inputValidations_1.COMMON_ERROR_MESSAGES.HELP_MESSAGE_AFTER_ERROR) .usage('--manifest <path> --path <path>') .summary(`Uploads the Android mapping.txt file with metadata extracted from the AndroidManifest.xml file`) .description(androidUploadWithManifestDescription) .requiredOption('--manifest <path>', 'Path to the packaged AndroidManifest.xml file') .requiredOption('--path <path>', 'Path to the mapping.txt file') .requiredOption('--realm <value>', 'Realm for your organization (example: us0). Can also be set using the environment variable SPLUNK_REALM', process.env.SPLUNK_REALM) .option('--token <value>', 'API access token. Can also be set using the environment variable SPLUNK_ACCESS_TOKEN') .option('--dry-run', 'Preview the file that will be uploaded and the parameters extracted from the AndroidManifest.xml file') .option('--debug', 'Enable debug logs') .action((options) => __awaiter(void 0, void 0, void 0, function* () { const spinner = (0, spinner_1.createSpinner)(); const logger = (0, logger_1.createLogger)(options.debug ? 1 /* LogLevel.DEBUG */ : 2 /* LogLevel.INFO */, spinner); try { const token = (0, inputValidations_1.validateAndPrepareToken)(options); if (!options.realm || options.realm.trim() === '') { exports.androidCommand.error(inputValidations_1.COMMON_ERROR_MESSAGES.REALM_NOT_SPECIFIED); } logger.debug(`Validating Mapping File Path: ${options.path}`); if (!(0, inputValidations_1.isValidFile)(options.path)) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Invalid mapping file path: ${options.path}.`); } logger.debug(`Validating Mapping File Extension`); if (!(0, inputValidations_1.hasValidExtension)(options.path, '.txt', '.gz')) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Mapping file does not have correct extension: ${options.path}.`); } logger.debug(`Validating Manifest File Path: ${options.manifest}`); if (!(0, inputValidations_1.isValidFile)(options.manifest)) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Invalid manifest file path: ${options.manifest}.`); } logger.debug(`Validating Mapping File Extension`); if (!(0, inputValidations_1.hasValidExtension)(options.manifest, '.xml')) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Manifest file does not have correct extension: ${options.manifest}.`); } logger.info(`Preparing to extract parameters from ${options.manifest}`); const { package: appId, versionCode, splunkBuildId } = yield (0, androidManifestUtils_1.extractManifestData)(options.manifest); logger.debug(`Validating App ID: ${appId}`); if (!(0, inputValidations_1.isValidAppId)(appId)) { throw new userFriendlyErrors_1.UserFriendlyError(null, 'Invalid Application ID extracted from the manifest.'); } logger.debug(`Validating Version Code: ${versionCode}`); if (!(0, inputValidations_1.isValidVersionCode)(versionCode)) { throw new userFriendlyErrors_1.UserFriendlyError(null, 'Invalid Version Code extracted from the manifest.'); } logger.debug(`Validating optional Splunk Build ID: ${splunkBuildId}`); if (splunkBuildId && !(0, inputValidations_1.isValidSplunkBuildId)(splunkBuildId)) { throw new userFriendlyErrors_1.UserFriendlyError(null, `Invalid Splunk Build ID extracted from the manifest: ${splunkBuildId}.`); } logger.info(`Preparing to upload Android mapping file: File: ${options.path} Extracted parameters from the AndroidManifest.xml: - Splunk Build ID: ${splunkBuildId || 'Not provided'} - App ID: ${appId} - Version Code: ${versionCode}`); if (options.dryRun) { logger.info('Dry Run complete - No file will be uploaded.'); return; } const url = generateURL('upload', options.realm, appId, versionCode, splunkBuildId); logger.debug(`URL Endpoint: ${url}`); spinner.start(`Uploading Android mapping file: ${options.path}`); const axiosInstance = axios_1.default.create(); (0, apiInterceptor_1.attachApiInterceptor)(axiosInstance, logger, url, { userFriendlyMessage: 'An error occurred during mapping file upload.' }); yield (0, httpUtils_1.uploadFile)({ url: url, file: { filePath: options.path, fieldName: 'file' }, token: token, parameters: { filename: path_1.default.basename(options.path) }, onProgress: options.debug ? (progressData) => { spinner.updateText(`Uploading: ${Math.round(progressData.progress)}%`); } : undefined }, axiosInstance); spinner.stop(); logger.info(`Upload complete`); } catch (err) { spinner.stop(); if (err instanceof userFriendlyErrors_1.UserFriendlyError) { logger.error(err.message); if (options.debug && err.originalError) { logger.debug('Error details:', err.originalError); } } else { logger.error('An unexpected error occurred:'); logger.error(err); } process.exit(1); } })); exports.androidCommand .command('list') .usage('--app-id <value>') .summary(`Retrieves list of metadata of all uploaded Proguard/R8 mapping files`) .requiredOption('--app-id <value>', 'Application ID') .requiredOption('--realm <value>', 'Realm for your organization (example: us0). Can also be set using the environment variable SPLUNK_REALM', process.env.SPLUNK_REALM) .option('--token <value>', 'API access token. Can also be set using the environment variable SPLUNK_ACCESS_TOKEN') .showHelpAfterError(inputValidations_1.COMMON_ERROR_MESSAGES.HELP_MESSAGE_AFTER_ERROR) .description(listProguardDescription) .option('--debug', 'Enable debug logs') .action((options) => __awaiter(void 0, void 0, void 0, function* () { const logger = (0, logger_1.createLogger)(options.debug ? 1 /* LogLevel.DEBUG */ : 2 /* LogLevel.INFO */); try { const token = (0, inputValidations_1.validateAndPrepareToken)(options); if (!options.realm || options.realm.trim() === '') { exports.androidCommand.error(inputValidations_1.COMMON_ERROR_MESSAGES.REALM_NOT_SPECIFIED); } const url = generateURL('list', options.realm, options.appId); logger.debug(`URL Endpoint: ${url}`); const axiosInstance = axios_1.default.create(); (0, apiInterceptor_1.attachApiInterceptor)(axiosInstance, logger, url, { userFriendlyMessage: 'An error occurred while retrieving mapping file metadata.' }); const responseData = yield (0, httpUtils_1.fetchAndroidMappingMetadata)({ url, token, axiosInstance }); logger.info((0, metadataFormatUtils_1.formatAndroidMappingMetadata)(responseData)); } catch (err) { if (err instanceof userFriendlyErrors_1.UserFriendlyError) { logger.error(err.message); if (options.debug && err.originalError) { logger.debug('Error details:', err.originalError); } } else { logger.error('Failed to fetch metadata:'); logger.error(err); } process.exit(1); } }));