UNPKG

@ionic/cli

Version:

A tool for creating and developing Ionic Framework mobile apps.

133 lines (132 loc) 6.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MonitoringSyncSourcemapsCommand = void 0; const tslib_1 = require("tslib"); const utils_fs_1 = require("@ionic/utils-fs"); const utils_terminal_1 = require("@ionic/utils-terminal"); const debug_1 = require("debug"); const path = tslib_1.__importStar(require("path")); const guards_1 = require("../../guards"); const color_1 = require("../../lib/color"); const command_1 = require("../../lib/command"); const errors_1 = require("../../lib/errors"); const debug = (0, debug_1.debug)('ionic:commands:monitoring:syncmaps'); const SOURCEMAP_DIRECTORY = '.sourcemaps'; class MonitoringSyncSourcemapsCommand extends command_1.Command { async getMetadata() { return { name: 'syncmaps', type: 'project', summary: 'Build & upload sourcemaps to Ionic Appflow Monitoring service', description: ` By default, ${(0, color_1.input)('ionic monitoring syncmaps')} will upload the sourcemap files within ${(0, color_1.strong)(SOURCEMAP_DIRECTORY)}. To optionally perform a production build before uploading sourcemaps, specify the ${(0, color_1.input)('--build')} flag. `, inputs: [ { name: 'snapshot_id', summary: `Specify a Snapshot ID to associate the uploaded sourcemaps with`, }, ], options: [ { name: 'build', summary: 'Invoke a production Ionic build', type: Boolean, }, ], }; } async run(inputs, options) { const { loadCordovaConfig } = await Promise.resolve().then(() => tslib_1.__importStar(require('../../lib/integrations/cordova/config'))); if (!this.project) { throw new errors_1.FatalException(`Cannot run ${(0, color_1.input)('ionic monitoring syncmaps')} outside a project directory.`); } const token = await this.env.session.getUserToken(); const appflowId = await this.project.requireAppflowId(); const [snapshotId] = inputs; const doBuild = options.build ? true : false; const cordova = this.project.requireIntegration('cordova'); const conf = await loadCordovaConfig(cordova); const cordovaInfo = conf.getProjectInfo(); const appVersion = cordovaInfo.version; const commitHash = (await this.env.shell.output('git', ['rev-parse', 'HEAD'], { cwd: this.project.directory })).trim(); debug(`Commit hash: ${(0, color_1.strong)(commitHash)}`); const sourcemapsDir = path.resolve(this.project.directory, SOURCEMAP_DIRECTORY); let sourcemapsExist = await (0, utils_fs_1.pathExists)(sourcemapsDir); if (doBuild || !sourcemapsExist) { const runner = await this.project.requireBuildRunner(); const runnerOpts = runner.createOptionsFromCommandLine([], { _: [], prod: true }); await runner.run(runnerOpts); } sourcemapsExist = await (0, utils_fs_1.pathExists)(sourcemapsDir); if (sourcemapsExist) { this.env.log.msg(`Using existing sourcemaps in ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(sourcemapsDir))}`); } else { // TODO: this is hard-coded for ionic-angular, make it work for all project types throw new errors_1.FatalException(`Cannot find directory: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(sourcemapsDir))}.\n` + `Make sure you have the latest ${(0, color_1.strong)('@ionic/app-scripts')}. Then, re-run this command.`); } let count = 0; const tasks = this.createTaskChain(); const syncTask = tasks.next('Syncing sourcemaps'); const sourcemapFiles = (await (0, utils_fs_1.readdirSafe)(sourcemapsDir)).filter(f => f.endsWith('.js.map')); debug(`Found ${sourcemapFiles.length} sourcemap files: ${sourcemapFiles.map(f => (0, color_1.strong)(f)).join(', ')}`); await Promise.all(sourcemapFiles.map(async (f) => { await this.syncSourcemap(path.resolve(sourcemapsDir, f), snapshotId, appVersion, commitHash, appflowId, token); count += 1; syncTask.msg = `Syncing sourcemaps: ${(0, color_1.strong)(`${count} / ${sourcemapFiles.length}`)}`; })); syncTask.msg = `Syncing sourcemaps: ${(0, color_1.strong)(`${sourcemapFiles.length} / ${sourcemapFiles.length}`)}`; tasks.end(); const details = (0, utils_terminal_1.columnar)([ ['App ID', (0, color_1.strong)(appflowId)], ['Version', (0, color_1.strong)(appVersion)], ['Package ID', (0, color_1.strong)(cordovaInfo.id)], ['Snapshot ID', snapshotId ? (0, color_1.strong)(snapshotId) : (0, color_1.weak)('not set')], ], { vsep: ':' }); this.env.log.ok(`Sourcemaps synced!\n` + details + '\n\n' + `See the Error Monitoring docs for usage information and next steps: ${(0, color_1.strong)('https://ionicframework.com/docs/appflow/monitoring')}`); } async syncSourcemap(file, snapshotId, appVersion, commitHash, appflowId, token) { const { req } = await this.env.client.make('POST', `/monitoring/${appflowId}/sourcemaps`); req .set('Authorization', `Bearer ${token}`) .send({ name: path.basename(file), version: appVersion, commit: commitHash, snapshot_id: snapshotId, }); try { const res = await this.env.client.do(req); return this.uploadSourcemap(res, file); } catch (e) { if ((0, guards_1.isSuperAgentError)(e)) { this.env.log.error(`Unable to sync map ${file}: ` + e.message); if (e.response.status === 401) { this.env.log.error('Try logging out and back in again.'); } } else { throw e; } } } async uploadSourcemap(sourcemap, file) { const { createRequest } = await Promise.resolve().then(() => tslib_1.__importStar(require('../../lib/utils/http'))); const sm = sourcemap; const fileData = await (0, utils_fs_1.readFile)(file, { encoding: 'utf8' }); const sourcemapPost = sm.data.sourcemap_post; const { req } = await createRequest('POST', sourcemapPost.url, this.env.config.getHTTPConfig()); req .field(sourcemapPost.fields) .field('file', fileData); const res = await req; if (res.status !== 204) { throw new errors_1.FatalException(`Unexpected status code from AWS: ${res.status}`); } } } exports.MonitoringSyncSourcemapsCommand = MonitoringSyncSourcemapsCommand;