@sentry/wizard
Version:
Sentry wizard helping you to configure your project
279 lines (276 loc) • 13.3 kB
JavaScript
;
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