syncmaps.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.MonitoringSyncSourcemapsCommand = void 0;
  4. const tslib_1 = require("tslib");
  5. const utils_fs_1 = require("@ionic/utils-fs");
  6. const utils_terminal_1 = require("@ionic/utils-terminal");
  7. const debug_1 = require("debug");
  8. const path = tslib_1.__importStar(require("path"));
  9. const guards_1 = require("../../guards");
  10. const color_1 = require("../../lib/color");
  11. const command_1 = require("../../lib/command");
  12. const errors_1 = require("../../lib/errors");
  13. const debug = (0, debug_1.debug)('ionic:commands:monitoring:syncmaps');
  14. const SOURCEMAP_DIRECTORY = '.sourcemaps';
  15. class MonitoringSyncSourcemapsCommand extends command_1.Command {
  16. async getMetadata() {
  17. return {
  18. name: 'syncmaps',
  19. type: 'project',
  20. summary: 'Build & upload sourcemaps to Ionic Appflow Monitoring service',
  21. description: `
  22. 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.
  23. `,
  24. inputs: [
  25. {
  26. name: 'snapshot_id',
  27. summary: `Specify a Snapshot ID to associate the uploaded sourcemaps with`,
  28. },
  29. ],
  30. options: [
  31. {
  32. name: 'build',
  33. summary: 'Invoke a production Ionic build',
  34. type: Boolean,
  35. },
  36. ],
  37. };
  38. }
  39. async run(inputs, options) {
  40. const { loadCordovaConfig } = await Promise.resolve().then(() => tslib_1.__importStar(require('../../lib/integrations/cordova/config')));
  41. if (!this.project) {
  42. throw new errors_1.FatalException(`Cannot run ${(0, color_1.input)('ionic monitoring syncmaps')} outside a project directory.`);
  43. }
  44. const token = await this.env.session.getUserToken();
  45. const appflowId = await this.project.requireAppflowId();
  46. const [snapshotId] = inputs;
  47. const doBuild = options.build ? true : false;
  48. const cordova = this.project.requireIntegration('cordova');
  49. const conf = await loadCordovaConfig(cordova);
  50. const cordovaInfo = conf.getProjectInfo();
  51. const appVersion = cordovaInfo.version;
  52. const commitHash = (await this.env.shell.output('git', ['rev-parse', 'HEAD'], { cwd: this.project.directory })).trim();
  53. debug(`Commit hash: ${(0, color_1.strong)(commitHash)}`);
  54. const sourcemapsDir = path.resolve(this.project.directory, SOURCEMAP_DIRECTORY);
  55. let sourcemapsExist = await (0, utils_fs_1.pathExists)(sourcemapsDir);
  56. if (doBuild || !sourcemapsExist) {
  57. const runner = await this.project.requireBuildRunner();
  58. const runnerOpts = runner.createOptionsFromCommandLine([], { _: [], prod: true });
  59. await runner.run(runnerOpts);
  60. }
  61. sourcemapsExist = await (0, utils_fs_1.pathExists)(sourcemapsDir);
  62. if (sourcemapsExist) {
  63. this.env.log.msg(`Using existing sourcemaps in ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(sourcemapsDir))}`);
  64. }
  65. else { // TODO: this is hard-coded for ionic-angular, make it work for all project types
  66. throw new errors_1.FatalException(`Cannot find directory: ${(0, color_1.strong)((0, utils_terminal_1.prettyPath)(sourcemapsDir))}.\n` +
  67. `Make sure you have the latest ${(0, color_1.strong)('@ionic/app-scripts')}. Then, re-run this command.`);
  68. }
  69. let count = 0;
  70. const tasks = this.createTaskChain();
  71. const syncTask = tasks.next('Syncing sourcemaps');
  72. const sourcemapFiles = (await (0, utils_fs_1.readdirSafe)(sourcemapsDir)).filter(f => f.endsWith('.js.map'));
  73. debug(`Found ${sourcemapFiles.length} sourcemap files: ${sourcemapFiles.map(f => (0, color_1.strong)(f)).join(', ')}`);
  74. await Promise.all(sourcemapFiles.map(async (f) => {
  75. await this.syncSourcemap(path.resolve(sourcemapsDir, f), snapshotId, appVersion, commitHash, appflowId, token);
  76. count += 1;
  77. syncTask.msg = `Syncing sourcemaps: ${(0, color_1.strong)(`${count} / ${sourcemapFiles.length}`)}`;
  78. }));
  79. syncTask.msg = `Syncing sourcemaps: ${(0, color_1.strong)(`${sourcemapFiles.length} / ${sourcemapFiles.length}`)}`;
  80. tasks.end();
  81. const details = (0, utils_terminal_1.columnar)([
  82. ['App ID', (0, color_1.strong)(appflowId)],
  83. ['Version', (0, color_1.strong)(appVersion)],
  84. ['Package ID', (0, color_1.strong)(cordovaInfo.id)],
  85. ['Snapshot ID', snapshotId ? (0, color_1.strong)(snapshotId) : (0, color_1.weak)('not set')],
  86. ], { vsep: ':' });
  87. this.env.log.ok(`Sourcemaps synced!\n` +
  88. details + '\n\n' +
  89. `See the Error Monitoring docs for usage information and next steps: ${(0, color_1.strong)('https://ionicframework.com/docs/appflow/monitoring')}`);
  90. }
  91. async syncSourcemap(file, snapshotId, appVersion, commitHash, appflowId, token) {
  92. const { req } = await this.env.client.make('POST', `/monitoring/${appflowId}/sourcemaps`);
  93. req
  94. .set('Authorization', `Bearer ${token}`)
  95. .send({
  96. name: path.basename(file),
  97. version: appVersion,
  98. commit: commitHash,
  99. snapshot_id: snapshotId,
  100. });
  101. try {
  102. const res = await this.env.client.do(req);
  103. return this.uploadSourcemap(res, file);
  104. }
  105. catch (e) {
  106. if ((0, guards_1.isSuperAgentError)(e)) {
  107. this.env.log.error(`Unable to sync map ${file}: ` + e.message);
  108. if (e.response.status === 401) {
  109. this.env.log.error('Try logging out and back in again.');
  110. }
  111. }
  112. else {
  113. throw e;
  114. }
  115. }
  116. }
  117. async uploadSourcemap(sourcemap, file) {
  118. const { createRequest } = await Promise.resolve().then(() => tslib_1.__importStar(require('../../lib/utils/http')));
  119. const sm = sourcemap;
  120. const fileData = await (0, utils_fs_1.readFile)(file, { encoding: 'utf8' });
  121. const sourcemapPost = sm.data.sourcemap_post;
  122. const { req } = await createRequest('POST', sourcemapPost.url, this.env.config.getHTTPConfig());
  123. req
  124. .field(sourcemapPost.fields)
  125. .field('file', fileData);
  126. const res = await req;
  127. if (res.status !== 204) {
  128. throw new errors_1.FatalException(`Unexpected status code from AWS: ${res.status}`);
  129. }
  130. }
  131. }
  132. exports.MonitoringSyncSourcemapsCommand = MonitoringSyncSourcemapsCommand;