UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

279 lines (276 loc) 13.3 kB
"use strict"; 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.doesContainSentryWrap = exports.replaceDefaultExport = exports.wrapWithSentry = exports.getDefaultExport = exports.checkAndWrapRootComponent = exports.SentryWrapResult = exports.wrapRootComponent = exports.getSentryIntegrationsPlainTextSnippet = exports.getSentryInitPlainTextSnippet = exports.getSentryInitColoredCodeSnippet = exports.doesJsCodeIncludeSdkSentryImport = exports.addSentryInitWithSdkImport = exports.addSentryInit = exports.sessionReplayOnErrorSampleRate = exports.sessionReplaySampleRate = void 0; /* eslint-disable max-lines */ // @ts-expect-error - clack is ESM and TS complains about that. It works though const prompts_1 = __importDefault(require("@clack/prompts")); const chalk_1 = __importDefault(require("chalk")); const path = __importStar(require("path")); const process = __importStar(require("process")); const fs = __importStar(require("fs")); const Sentry = __importStar(require("@sentry/node")); const telemetry_1 = require("../telemetry"); const clack_1 = require("../utils/clack"); const glob_1 = require("./glob"); const react_native_wizard_1 = require("./react-native-wizard"); // @ts-expect-error - magicast is ESM and TS complains about that. It works though const magicast_1 = require("magicast"); const t = __importStar(require("@babel/types")); exports.sessionReplaySampleRate = 0.1; exports.sessionReplayOnErrorSampleRate = 1.0; async function addSentryInit({ dsn, enableSessionReplay = false, enableFeedbackWidget = false, }) { const jsPath = getMainAppFilePath(); Sentry.setTag('app-js-file-status', jsPath ? 'found' : 'not-found'); if (!jsPath) { prompts_1.default.log.warn(`Could not find main App file. Place the following code snippet close to the Apps Root component.`); Sentry.captureException('Could not find main App file.'); await (0, clack_1.showCopyPasteInstructions)({ filename: 'App.js or _layout.tsx', codeSnippet: getSentryInitColoredCodeSnippet(dsn, enableSessionReplay, enableFeedbackWidget), hint: 'This ensures the Sentry SDK is ready to capture errors.', }); return; } const jsRelativePath = path.relative(process.cwd(), jsPath); const js = fs.readFileSync(jsPath, 'utf-8'); const includesSentry = doesJsCodeIncludeSdkSentryImport(js, { sdkPackageName: react_native_wizard_1.RN_SDK_PACKAGE, }); if (includesSentry) { Sentry.setTag('app-js-file-status', 'already-includes-sentry'); prompts_1.default.log.warn(`${chalk_1.default.cyan(jsRelativePath)} already includes Sentry. We wont't add it again.`); return; } (0, telemetry_1.traceStep)('add-sentry-init', () => { const newContent = addSentryInitWithSdkImport(js, { dsn, enableSessionReplay, enableFeedbackWidget, }); try { fs.writeFileSync(jsPath, newContent, 'utf-8'); prompts_1.default.log.success(`Added ${chalk_1.default.cyan('Sentry.init')} to ${chalk_1.default.cyan(jsRelativePath)}.`); } catch (error) { prompts_1.default.log.error(`Error while writing ${jsPath}`); Sentry.captureException('Error while writing app.js'); } }); Sentry.setTag('app-js-file-status', 'added-sentry-init'); prompts_1.default.log.success(chalk_1.default.green(`${chalk_1.default.cyan(jsRelativePath)} changes saved.`)); } exports.addSentryInit = addSentryInit; function addSentryInitWithSdkImport(js, { dsn, enableSessionReplay = false, enableFeedbackWidget = false, }) { return js.replace(/^([^]*)(import\s+[^;]*?;$)/m, (match) => `${match} ${getSentryInitPlainTextSnippet(dsn, enableSessionReplay, enableFeedbackWidget)}`); } exports.addSentryInitWithSdkImport = addSentryInitWithSdkImport; function doesJsCodeIncludeSdkSentryImport(js, { sdkPackageName }) { return !!js.match(sdkPackageName); } exports.doesJsCodeIncludeSdkSentryImport = doesJsCodeIncludeSdkSentryImport; function getSentryInitColoredCodeSnippet(dsn, enableSessionReplay = false, enableFeedbackWidget = false) { return (0, clack_1.makeCodeSnippet)(true, (_unchanged, plus, _minus) => { return plus(getSentryInitPlainTextSnippet(dsn, enableSessionReplay, enableFeedbackWidget)); }); } exports.getSentryInitColoredCodeSnippet = getSentryInitColoredCodeSnippet; function getSentryInitPlainTextSnippet(dsn, enableSessionReplay = false, enableFeedbackWidget = false) { return `import * as Sentry from '@sentry/react-native'; Sentry.init({ dsn: '${dsn}', // Adds more context data to events (IP address, cookies, user, etc.) // For more information, visit: https://docs.sentry.io/platforms/react-native/data-management/data-collected/ sendDefaultPii: true, ${enableSessionReplay ? ` // Configure Session Replay replaysSessionSampleRate: ${exports.sessionReplaySampleRate}, replaysOnErrorSampleRate: ${exports.sessionReplayOnErrorSampleRate}, ` : ''}${getSentryIntegrationsPlainTextSnippet(enableSessionReplay, enableFeedbackWidget)} // uncomment the line below to enable Spotlight (https://spotlightjs.com) // spotlight: __DEV__, });`; } exports.getSentryInitPlainTextSnippet = getSentryInitPlainTextSnippet; function getSentryIntegrationsPlainTextSnippet(enableSessionReplay = false, enableFeedbackWidget = false) { if (!enableSessionReplay && !enableFeedbackWidget) { return ''; } return ` integrations: [${enableSessionReplay ? 'Sentry.mobileReplayIntegration()' : ''}${enableSessionReplay && enableFeedbackWidget ? ', ' : ''}${enableFeedbackWidget ? 'Sentry.feedbackIntegration()' : ''}], `; } exports.getSentryIntegrationsPlainTextSnippet = getSentryIntegrationsPlainTextSnippet; function getMainAppFilePath() { const prefixGlob = '{.,./src,./app}'; const suffixGlob = '@(j|t|cj|mj)s?(x)'; const universalGlob = `@(App|_layout).${suffixGlob}`; const jsFileGlob = `${prefixGlob}/+(${universalGlob})`; const jsPath = (0, telemetry_1.traceStep)('find-app-js-file', () => (0, glob_1.getFirstMatchedPath)(jsFileGlob)); return jsPath; } /** * This step should be executed after `addSentryInit` */ async function wrapRootComponent() { const showInstructions = () => (0, clack_1.showCopyPasteInstructions)({ filename: 'App.js or _layout.tsx', codeSnippet: getSentryWrapColoredCodeSnippet(), }); const jsPath = getMainAppFilePath(); Sentry.setTag('app-js-file-status', jsPath ? 'found' : 'not-found'); if (!jsPath) { prompts_1.default.log.warn(`Could not find main App file. Please wrap your App's Root component.`); await showInstructions(); return; } const jsRelativePath = path.relative(process.cwd(), jsPath); const js = fs.readFileSync(jsPath, 'utf-8'); const mod = (0, magicast_1.parseModule)(js); const result = checkAndWrapRootComponent(mod); if (result === SentryWrapResult.AlreadyWrapped) { Sentry.setTag('app-js-file-status', 'already-includes-sentry-wrap'); prompts_1.default.log.warn(`${chalk_1.default.cyan(jsRelativePath)} already includes Sentry.wrap. We wont't add it again.`); return; } if (result === SentryWrapResult.NotFound) { prompts_1.default.log.warn(`Could not find your App's Root component. Please wrap your App's Root component manually.`); await showInstructions(); return; } (0, telemetry_1.traceStep)('add-sentry-wrap', () => { try { fs.writeFileSync(jsPath, (0, magicast_1.generateCode)(mod.$ast).code, 'utf-8'); prompts_1.default.log.success(`Added ${chalk_1.default.cyan('Sentry.wrap')} to ${chalk_1.default.cyan(jsRelativePath)}.`); } catch (error) { prompts_1.default.log.error(`Error while writing ${jsPath}`); Sentry.captureException('Error while writing app.js'); return; } }); Sentry.setTag('app-js-file-status', 'added-sentry-wrap'); prompts_1.default.log.success(chalk_1.default.green(`${chalk_1.default.cyan(jsRelativePath)} changes saved.`)); } exports.wrapRootComponent = wrapRootComponent; var SentryWrapResult; (function (SentryWrapResult) { SentryWrapResult["NotFound"] = "RootComponentNotFound"; SentryWrapResult["AlreadyWrapped"] = "AlreadyWrapped"; SentryWrapResult["Success"] = "Success"; })(SentryWrapResult = exports.SentryWrapResult || (exports.SentryWrapResult = {})); function checkAndWrapRootComponent(mod) { if (doesContainSentryWrap(mod.$ast)) { return SentryWrapResult.AlreadyWrapped; } const defaultExport = getDefaultExport(mod.$ast); if (!defaultExport) { return SentryWrapResult.NotFound; } const wrappedConfig = wrapWithSentry(defaultExport); const replacedDefaultExport = replaceDefaultExport(mod.$ast, wrappedConfig); if (!replacedDefaultExport) { return SentryWrapResult.NotFound; } return SentryWrapResult.Success; } exports.checkAndWrapRootComponent = checkAndWrapRootComponent; function getDefaultExport(program) { for (const node of program.body) { if (t.isExportDefaultDeclaration(node) && (t.isIdentifier(node.declaration) || t.isCallExpression(node.declaration) || t.isObjectExpression(node.declaration) || t.isFunctionDeclaration(node.declaration) || t.isArrowFunctionExpression(node.declaration) || t.isClassDeclaration(node.declaration))) { Sentry.setTag('app-js-file-status', 'default-export'); return node.declaration; } } Sentry.setTag('app-js-file-status', 'default-export-not-found'); return undefined; } exports.getDefaultExport = getDefaultExport; function wrapWithSentry(configObj) { if (t.isFunctionDeclaration(configObj)) { return t.callExpression(t.memberExpression(t.identifier('Sentry'), t.identifier('wrap')), [ t.functionExpression(configObj.id, configObj.params, configObj.body, configObj.generator, configObj.async), ]); } if (t.isArrowFunctionExpression(configObj)) { return t.callExpression(t.memberExpression(t.identifier('Sentry'), t.identifier('wrap')), [configObj]); } if (t.isClassDeclaration(configObj)) { return t.callExpression(t.memberExpression(t.identifier('Sentry'), t.identifier('wrap')), [ t.classExpression(configObj.id, configObj.superClass, configObj.body, configObj.decorators), ]); } return t.callExpression(t.memberExpression(t.identifier('Sentry'), t.identifier('wrap')), [configObj]); } exports.wrapWithSentry = wrapWithSentry; function replaceDefaultExport(program, wrappedDefaultExport) { for (const node of program.body) { if (t.isExportDefaultDeclaration(node)) { node.declaration = wrappedDefaultExport; return true; } } return false; } exports.replaceDefaultExport = replaceDefaultExport; function doesContainSentryWrap(program) { for (const node of program.body) { if (t.isExportDefaultDeclaration(node)) { const declaration = node.declaration; if (t.isCallExpression(declaration)) { const callExpr = declaration; if (t.isMemberExpression(callExpr.callee)) { const callee = callExpr.callee; if (t.isIdentifier(callee.object) && callee.object.name === 'Sentry' && t.isIdentifier(callee.property) && callee.property.name === 'wrap') { return true; } } } } } return false; } exports.doesContainSentryWrap = doesContainSentryWrap; function getSentryWrapColoredCodeSnippet() { return (0, clack_1.makeCodeSnippet)(true, (_unchanged, plus, _minus) => { return plus(`import * as Sentry from '@sentry/react-native'; export default Sentry.wrap(App);`); }); } //# sourceMappingURL=javascript.js.map