UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

561 lines 30.6 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ReactNative = exports.DOCS_MANUAL_STEPS = exports.REACT_NATIVE_PACKAGE = exports.SENTRY_REACT_NATIVE_PACKAGE = exports.COMPATIBLE_SDK_VERSION = exports.COMPATIBLE_REACT_NATIVE_VERSIONS = void 0; /* eslint-disable max-lines */ var child_process_1 = require("child_process"); var fs = __importStar(require("fs")); var inquirer_1 = require("inquirer"); var _ = __importStar(require("lodash")); var path = __importStar(require("path")); var util_1 = require("util"); var File_1 = require("../../Helper/File"); var Logging_1 = require("../../Helper/Logging"); var Package_1 = require("../../Helper/Package"); var PackageManager_1 = require("../../Helper/PackageManager"); var SentryCli_1 = require("../../Helper/SentryCli"); var MobileProject_1 = require("./MobileProject"); var BottomBar_1 = require("../../Helper/BottomBar"); var url_1 = require("url"); var xcode = require('xcode'); exports.COMPATIBLE_REACT_NATIVE_VERSIONS = '>=0.69.0'; exports.COMPATIBLE_SDK_VERSION = '>= 5.0.0'; exports.SENTRY_REACT_NATIVE_PACKAGE = '@sentry/react-native'; exports.REACT_NATIVE_PACKAGE = 'react-native'; exports.DOCS_MANUAL_STEPS = 'https://docs.sentry.io/platforms/react-native/manual-setup/manual-setup/'; var ReactNative = exports.ReactNative = /** @class */ (function (_super) { __extends(ReactNative, _super); function ReactNative(_argv) { var _this = _super.call(this, _argv) || this; _this._argv = _argv; _this.url = _argv.url; _this._sentryCli = new SentryCli_1.SentryCli(_this._argv); return _this; } ReactNative.prototype.emit = function (answers) { return __awaiter(this, void 0, void 0, function () { var userAnswers, packageManager, hasCompatibleReactNativeVersion, hasCompatibleSentryReactNativeVersion, sentryCliProperties, promises, host, orgSlug, projectId, projectIssuesUrl; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this._argv.uninstall) { return [2 /*return*/, this.uninstall(answers)]; } return [4 /*yield*/, this.shouldEmit(answers)]; case 1: if (!(_a.sent())) { return [2 /*return*/, {}]; } (0, Logging_1.nl)(); userAnswers = { continue: true }; packageManager = (0, PackageManager_1.getPackageManagerChoice)(); hasCompatibleReactNativeVersion = (0, Package_1.checkPackageVersion)(this._readAppPackage(), exports.REACT_NATIVE_PACKAGE, exports.COMPATIBLE_REACT_NATIVE_VERSIONS, true); if (!(!hasCompatibleReactNativeVersion && !this._argv.quiet)) return [3 /*break*/, 3]; return [4 /*yield*/, (0, inquirer_1.prompt)({ message: 'Your version of React Native is not compatible with Sentry\'s React Native SDK. Do you want to continue?', name: 'continue', default: false, type: 'confirm', })]; case 2: userAnswers = _a.sent(); (0, Logging_1.nl)(); _a.label = 3; case 3: if (!userAnswers.continue) { throw new Error("Please upgrade to a version that is compatible with ".concat(exports.COMPATIBLE_REACT_NATIVE_VERSIONS, ". Or use ").concat(exports.DOCS_MANUAL_STEPS)); } if (!packageManager) return [3 /*break*/, 5]; BottomBar_1.BottomBar.show("Adding ".concat(exports.SENTRY_REACT_NATIVE_PACKAGE, "...")); return [4 /*yield*/, packageManager.installPackage(exports.SENTRY_REACT_NATIVE_PACKAGE)]; case 4: _a.sent(); BottomBar_1.BottomBar.hide(); (0, Logging_1.green)("\u2713 Added `".concat(exports.SENTRY_REACT_NATIVE_PACKAGE, "`")); _a.label = 5; case 5: hasCompatibleSentryReactNativeVersion = (0, Package_1.checkPackageVersion)(this._readAppPackage(), exports.SENTRY_REACT_NATIVE_PACKAGE, exports.COMPATIBLE_SDK_VERSION, true); if (!(!hasCompatibleSentryReactNativeVersion && !this._argv.quiet)) return [3 /*break*/, 7]; return [4 /*yield*/, (0, inquirer_1.prompt)({ message: "Your version of ".concat(exports.SENTRY_REACT_NATIVE_PACKAGE, " is not compatible with this wizard. Do you want to continue?"), name: 'continue', default: false, type: 'confirm', })]; case 6: userAnswers = _a.sent(); (0, Logging_1.nl)(); _a.label = 7; case 7: if (!userAnswers.continue) { throw new Error("Please upgrade to a version that is compatible with ".concat(exports.COMPATIBLE_SDK_VERSION, ".")); } sentryCliProperties = this._sentryCli.convertAnswersToProperties(answers); promises = this.getPlatforms(answers).map(function (platform) { return __awaiter(_this, void 0, void 0, function () { var e_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 8, , 9]); if (!(platform === 'ios')) return [3 /*break*/, 3]; return [4 /*yield*/, (0, File_1.patchMatchingFile)('ios/*.xcodeproj/project.pbxproj', this._patchXcodeProj.bind(this))]; case 1: _a.sent(); (0, Logging_1.green)('✓ Patched build script in Xcode project.'); BottomBar_1.BottomBar.show('Adding Sentry pods...'); return [4 /*yield*/, this._podInstall()]; case 2: _a.sent(); BottomBar_1.BottomBar.hide(); (0, Logging_1.green)('✓ Pods installed.'); return [3 /*break*/, 5]; case 3: return [4 /*yield*/, (0, File_1.patchMatchingFile)('**/app/build.gradle', this._patchBuildGradle.bind(this))]; case 4: _a.sent(); (0, Logging_1.green)('✓ Patched build.gradle file.'); _a.label = 5; case 5: return [4 /*yield*/, this._patchJsSentryInit(platform, answers)]; case 6: _a.sent(); return [4 /*yield*/, this._addSentryProperties(platform, sentryCliProperties)]; case 7: _a.sent(); (0, Logging_1.green)("\u2713 Added sentry.properties file to ".concat(platform)); return [3 /*break*/, 9]; case 8: e_1 = _a.sent(); (0, Logging_1.red)(e_1); return [3 /*break*/, 9]; case 9: return [2 /*return*/]; } }); }); }); return [4 /*yield*/, Promise.all(promises)]; case 8: _a.sent(); host = null; try { host = (new url_1.URL(this.url || '')).host; } catch (_error) { // ignore } orgSlug = _.get(answers, 'config.organization.slug', null); projectId = _.get(answers, 'config.project.id', null); projectIssuesUrl = host && orgSlug && projectId ? "https://".concat(orgSlug, ".").concat(host, "/issues/?project=").concat(projectId) : null; (0, Logging_1.l)("\nTo make sure everything is set up correctly, put the following code snippet into your application.\nThe snippet will create a button that, when tapped, sends a test event to Sentry.\n"); if (projectIssuesUrl) { (0, Logging_1.l)("After that check your project issues:"); (0, Logging_1.l)(projectIssuesUrl); (0, Logging_1.nl)(); } (0, Logging_1.l)("<Button title='Try!' onPress={ () => { Sentry.captureException(new Error('First error')) }}/>"); (0, Logging_1.nl)(); if (!!this._argv.quiet) return [3 /*break*/, 10]; return [4 /*yield*/, (0, inquirer_1.prompt)({ message: 'Have you successfully sent a test event?', name: 'snippet', default: true, type: 'confirm', })]; case 9: _a.sent(); _a.label = 10; case 10: return [2 /*return*/, answers]; } }); }); }; ReactNative.prototype.uninstall = function (_answers) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, (0, File_1.patchMatchingFile)('**/*.xcodeproj/project.pbxproj', this._unpatchXcodeProj.bind(this))]; case 1: _a.sent(); return [4 /*yield*/, (0, File_1.patchMatchingFile)('**/app/build.gradle', this._unpatchBuildGradle.bind(this))]; case 2: _a.sent(); return [2 /*return*/, {}]; } }); }); }; // eslint-disable-next-line @typescript-eslint/require-await ReactNative.prototype._shouldConfigurePlatform = function (platform) { return __awaiter(this, void 0, void 0, function () { var result, regex; return __generator(this, function (_a) { result = false; if (!(0, File_1.exists)("".concat(platform, "/sentry.properties"))) { result = true; this.debug("".concat(platform, "/sentry.properties not exists")); } if (!(0, File_1.matchesContent)('**/*.xcodeproj/project.pbxproj', /sentry-cli/gi)) { result = true; this.debug('**/*.xcodeproj/project.pbxproj not matched'); } if (!(0, File_1.matchesContent)('**/app/build.gradle', /sentry\.gradle/gi)) { result = true; this.debug('**/app/build.gradle not matched'); } regex = /Sentry/gi; if ((0, File_1.exists)("index.".concat(platform, ".js")) && !(0, File_1.matchesContent)("index.".concat(platform, ".js"), regex)) { result = true; this.debug("index.".concat(platform, ".js not matched")); } if ((0, File_1.exists)('App.js') && !(0, File_1.matchesContent)('App.js', regex)) { result = true; this.debug('index.js or App.js not matched'); } if (this._argv.uninstall) { // if we uninstall we need to invert the result so we remove already patched // but leave untouched platforms as they are return [2 /*return*/, !result]; } return [2 /*return*/, result]; }); }); }; ReactNative.prototype._readAppPackage = function () { var appPackage = {}; try { appPackage = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')); } catch (_a) { // We don't need to have this } return appPackage; }; ReactNative.prototype._podInstall = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, (0, util_1.promisify)(child_process_1.exec)('npx --yes pod-install --non-interactive --quiet')]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; ReactNative.prototype._patchJsSentryInit = function (platform, answers) { return __awaiter(this, void 0, void 0, function () { var prefixGlob, suffixGlob, platformGlob, universalGlob, jsFileGlob, jsFileToPatch; return __generator(this, function (_a) { switch (_a.label) { case 0: prefixGlob = '{.,./src}'; suffixGlob = '@(j|t|cj|mj)s?(x)'; platformGlob = "index.".concat(platform, ".").concat(suffixGlob); universalGlob = "App.".concat(suffixGlob); jsFileGlob = "".concat(prefixGlob, "/+(").concat(platformGlob, "|").concat(universalGlob, ")"); jsFileToPatch = (0, File_1.matchFiles)(jsFileGlob); if (!(jsFileToPatch.length !== 0)) return [3 /*break*/, 2]; return [4 /*yield*/, (0, File_1.patchMatchingFile)(jsFileGlob, this._patchJs.bind(this), answers, platform)]; case 1: _a.sent(); (0, Logging_1.green)("\u2713 Patched ".concat(jsFileToPatch.join(', '), " file(s).")); return [3 /*break*/, 3]; case 2: (0, Logging_1.red)("\u2717 Could not find ".concat(platformGlob, " nor ").concat(universalGlob, " files.")); (0, Logging_1.red)('✗ Please, visit https://docs.sentry.io/platforms/react-native'); _a.label = 3; case 3: return [2 /*return*/]; } }); }); }; ReactNative.prototype._addSentryProperties = function (platform, properties) { var _this = this; var rv = Promise.resolve(); // This will create the ios/android folder before trying to write // sentry.properties in it which would fail otherwise if (!fs.existsSync(platform)) { (0, Logging_1.dim)("".concat(platform, " folder did not exist, creating it.")); fs.mkdirSync(platform); } var fn = path.join(platform, 'sentry.properties'); if (platform === 'android' && properties['cli/executable']) { // We don't need to write the sentry-cli path in the properties file // since our gradle plugins already pick it up on the correct spot delete properties['cli/executable']; } rv = rv.then(function () { return fs.writeFileSync(fn, _this._sentryCli.dumpProperties(properties)); }); return rv; }; ReactNative.prototype._patchJs = function (contents, _filename, answers, platform) { // since the init call could live in other places too, we really only // want to do this if we managed to patch any of the other files as well. if (contents.match(/Sentry.config\(/)) { return Promise.resolve(null); } // if we match @sentry\/react-native somewhere, we already patched the file // and no longer need to if (contents.match('@sentry/react-native')) { return Promise.resolve(contents); } var dsn = '__DSN__'; this.getPlatforms(answers).forEach(function (selectedPlatform) { if (platform && selectedPlatform === platform) { dsn = _.get(answers, 'config.dsn.public', null); } else if (platform === undefined) { dsn = _.get(answers, 'config.dsn.public', null); } }); return Promise.resolve(contents.replace(/^([^]*)(import\s+[^;]*?;$)/m, function (match) { // eslint-disable-next-line prefer-template return match + "\n\nimport * as Sentry from '@sentry/react-native';\n\n" + 'Sentry.init({ \n' + " dsn: '".concat(dsn, "', \n") + '});\n'; })); }; // ANDROID ----------------------------------------- ReactNative.prototype._patchBuildGradle = function (contents) { var applyFrom = 'apply from: "../../node_modules/@sentry/react-native/sentry.gradle"'; if (contents.indexOf(applyFrom) >= 0) { return Promise.resolve(null); } return Promise.resolve(contents.replace(ReactNative._buildGradleAndroidSectionBeginning, // eslint-disable-next-line prefer-template function (match) { return applyFrom + '\n' + match; })); }; ReactNative.prototype._unpatchBuildGradle = function (contents) { return Promise.resolve(contents.replace(/^\s*apply from: ["']..\/..\/node_modules\/@sentry\/react-native\/sentry.gradle["'];?\s*?\r?\n/m, '')); }; // IOS ----------------------------------------- ReactNative.prototype._patchExistingXcodeBuildScripts = function (buildScripts) { for (var _i = 0, buildScripts_1 = buildScripts; _i < buildScripts_1.length; _i++) { var script = buildScripts_1[_i]; if (!script.shellScript.match(/\/scripts\/react-native-xcode\.sh/i) || script.shellScript.match(/sentry-cli\s+react-native\s+xcode/i)) { continue; } var code = JSON.parse(script.shellScript); code = // eslint-disable-next-line prefer-template, @typescript-eslint/restrict-plus-operands 'export SENTRY_PROPERTIES=sentry.properties\n' + 'export EXTRA_PACKAGER_ARGS="--sourcemap-output $DERIVED_FILE_DIR/main.jsbundle.map"\n' + code.replace('$REACT_NATIVE_XCODE', function () { // eslint-disable-next-line no-useless-escape return '\\"../node_modules/@sentry/cli/bin/sentry-cli react-native xcode $REACT_NATIVE_XCODE\\"'; }) + '\n/bin/sh ../node_modules/@sentry/react-native/scripts/collect-modules.sh\n'; script.shellScript = JSON.stringify(code); } }; ReactNative.prototype._addNewXcodeBuildPhaseForSymbols = function (buildScripts, proj) { for (var _i = 0, buildScripts_2 = buildScripts; _i < buildScripts_2.length; _i++) { var script = buildScripts_2[_i]; if (script.shellScript.match(/sentry-cli\s+(upload-dsym|debug-files upload)/)) { return; } } proj.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', null, { shellPath: '/bin/sh', shellScript: "\nexport SENTRY_PROPERTIES=sentry.properties\n[[ $SENTRY_INCLUDE_NATIVE_SOURCES == \"true\" ]] && INCLUDE_SOURCES_FLAG=\"--include-sources\" || INCLUDE_SOURCES_FLAG=\"\"\n../node_modules/@sentry/cli/bin/sentry-cli debug-files upload \"$INCLUDE_SOURCES_FLAG\" \"$DWARF_DSYM_FOLDER_PATH\"\n", }); }; ReactNative.prototype._patchXcodeProj = function (contents, filename) { var _this = this; var proj = xcode.project(filename); return new Promise(function (resolve, reject) { proj.parse(function (err) { if (err) { reject(err); return; } var buildScripts = []; for (var key in proj.hash.project.objects.PBXShellScriptBuildPhase || {}) { if ( // eslint-disable-next-line no-prototype-builtins proj.hash.project.objects.PBXShellScriptBuildPhase.hasOwnProperty(key)) { var val = proj.hash.project.objects.PBXShellScriptBuildPhase[key]; if (val.isa) { buildScripts.push(val); } } } try { _this._patchExistingXcodeBuildScripts(buildScripts); } catch (e) { (0, Logging_1.red)(e); } try { _this._addNewXcodeBuildPhaseForSymbols(buildScripts, proj); } catch (e) { (0, Logging_1.red)(e); } // we always modify the xcode file in memory but we only want to save it // in case the user wants configuration for ios. This is why we check // here first if changes are made before we might prompt the platform // continue prompt. var newContents = proj.writeSync(); if (newContents === contents) { resolve(undefined); } else { resolve(newContents); } }); }); }; ReactNative.prototype._unpatchXcodeBuildScripts = function (proj) { var scripts = proj.hash.project.objects.PBXShellScriptBuildPhase || {}; var firstTarget = proj.getFirstTarget().uuid; var nativeTargets = proj.hash.project.objects.PBXNativeTarget; // scripts to patch partially. Run this first so that we don't // accidentally delete some scripts later entirely that we only want to // rewrite. for (var _i = 0, _a = Object.keys(scripts); _i < _a.length; _i++) { var key = _a[_i]; var script = scripts[key]; // ignore comments if (typeof script === 'string') { continue; } // ignore scripts that do not invoke the react-native-xcode command. if (!script.shellScript.match(/sentry-cli\s+react-native\s+xcode/i)) { continue; } script.shellScript = JSON.stringify(JSON.parse(script.shellScript) // remove sentry properties export .replace(/^export SENTRY_PROPERTIES=sentry.properties\r?\n/m, '') .replace(/^\/bin\/sh ..\/node_modules\/@sentry\/react-native\/scripts\/collect-modules.sh\r?\n/m, '') // unwrap react-native-xcode.sh command. In case someone replaced it // entirely with the sentry-cli command we need to put the original // version back in. .replace(/\.\.\/node_modules\/@sentry\/cli\/bin\/sentry-cli\s+react-native\s+xcode\s+\$REACT_NATIVE_XCODE/i, '$REACT_NATIVE_XCODE')); } // scripts to kill entirely. for (var _b = 0, _c = Object.keys(scripts); _b < _c.length; _b++) { var key = _c[_b]; var script = scripts[key]; // ignore comments and keys that got deleted if (typeof script === 'string' || script === undefined) { continue; } if (script.shellScript.match(/@sentry\/cli\/bin\/sentry-cli\s+(upload-dsym|debug-files upload)\b/)) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete scripts[key]; // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete scripts["".concat(key, "_comment")]; var phases = nativeTargets[firstTarget].buildPhases; if (phases) { for (var i = 0; i < phases.length; i++) { if (phases[i].value === key) { phases.splice(i, 1); break; } } } continue; } } }; ReactNative.prototype._unpatchXcodeProj = function (_contents, filename) { var _this = this; var proj = xcode.project(filename); return new Promise(function (resolve, reject) { proj.parse(function (err) { if (err) { reject(err); return; } _this._unpatchXcodeBuildScripts(proj); resolve(proj.writeSync()); }); }); }; /** * All React Native versions have app/build.gradle with android section. */ ReactNative._buildGradleAndroidSectionBeginning = /^android {/m; return ReactNative; }(MobileProject_1.MobileProject)); //# sourceMappingURL=ReactNative.js.map