UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

275 lines 14.6 kB
"use strict"; /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ 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.instrumentHandleError = exports.instrumentHandleRequest = exports.instrumentServerEntry = void 0; const recast = __importStar(require("recast")); // @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")); // @ts-expect-error - magicast is ESM and TS complains about that. It works though const magicast_1 = require("magicast"); const debug_1 = require("../../utils/debug"); const ast_utils_1 = require("../../utils/ast-utils"); const utils_1 = require("./utils"); async function instrumentServerEntry(serverEntryPath) { const serverEntryAst = await (0, magicast_1.loadFile)(serverEntryPath); if (!(0, ast_utils_1.hasSentryContent)(serverEntryAst.$ast)) { serverEntryAst.imports.$add({ from: '@sentry/react-router', imported: '*', local: 'Sentry', }); } instrumentHandleError(serverEntryAst); instrumentHandleRequest(serverEntryAst); await (0, magicast_1.writeFile)(serverEntryAst.$ast, serverEntryPath); } exports.instrumentServerEntry = instrumentServerEntry; function instrumentHandleRequest(originalEntryServerMod) { const originalEntryServerModAST = originalEntryServerMod.$ast; const defaultServerEntryExport = originalEntryServerModAST.body.find((node) => { return node.type === 'ExportDefaultDeclaration'; }); if (!defaultServerEntryExport) { prompts_1.default.log.warn(`Could not find function ${chalk_1.default.cyan('handleRequest')} in your server entry file. Creating one for you.`); let foundServerRouterImport = false; let foundRenderToPipeableStreamImport = false; let foundCreateReadableStreamFromReadableImport = false; originalEntryServerMod.imports.$items.forEach((item) => { if (item.imported === 'ServerRouter' && item.from === 'react-router') { foundServerRouterImport = true; } if (item.imported === 'renderToPipeableStream' && item.from === 'react-dom/server') { foundRenderToPipeableStreamImport = true; } if (item.imported === 'createReadableStreamFromReadable' && item.from === '@react-router/node') { foundCreateReadableStreamFromReadableImport = true; } }); if (!foundServerRouterImport) { originalEntryServerMod.imports.$add({ from: 'react-router', imported: 'ServerRouter', local: 'ServerRouter', }); } if (!foundRenderToPipeableStreamImport) { originalEntryServerMod.imports.$add({ from: 'react-dom/server', imported: 'renderToPipeableStream', local: 'renderToPipeableStream', }); } if (!foundCreateReadableStreamFromReadableImport) { originalEntryServerMod.imports.$add({ from: '@react-router/node', imported: 'createReadableStreamFromReadable', local: 'createReadableStreamFromReadable', }); } const implementation = recast.parse(`const handleRequest = Sentry.createSentryHandleRequest({ ServerRouter, renderToPipeableStream, createReadableStreamFromReadable, })`).program.body[0]; try { originalEntryServerModAST.body.splice((0, utils_1.getAfterImportsInsertionIndex)(originalEntryServerModAST), 0, implementation); originalEntryServerModAST.body.push({ type: 'ExportDefaultDeclaration', declaration: { type: 'Identifier', name: 'handleRequest', }, }); } catch (error) { (0, debug_1.debug)('Failed to insert handleRequest implementation:', error); throw new Error('Could not automatically instrument handleRequest. Please add it manually.'); } } else if (defaultServerEntryExport && // @ts-expect-error - StatementKind works here because the AST is proxified by magicast (0, magicast_1.generateCode)(defaultServerEntryExport).code.includes('wrapSentryHandleRequest')) { (0, debug_1.debug)('wrapSentryHandleRequest is already used, skipping wrapping again'); prompts_1.default.log.info('Sentry handleRequest wrapper already detected, skipping instrumentation.'); } else { let defaultExportNode = null; const defaultExportIndex = originalEntryServerModAST.body.findIndex((node) => { const found = node.type === 'ExportDefaultDeclaration'; if (found) { defaultExportNode = node; } return found; }); if (defaultExportIndex !== -1 && defaultExportNode !== null) { recast.visit(defaultExportNode, { visitCallExpression(path) { if ((0, ast_utils_1.safeCalleeIdentifierMatch)(path.value.callee, 'pipe') && path.value.arguments.length && path.value.arguments[0].type === 'Identifier' && (0, ast_utils_1.safeGetIdentifierName)(path.value.arguments[0]) === 'body') { const wrapped = recast.types.builders.callExpression(recast.types.builders.memberExpression(recast.types.builders.identifier('Sentry'), recast.types.builders.identifier('getMetaTagTransformer')), [path.value.arguments[0]]); path.value.arguments[0] = wrapped; } this.traverse(path); }, }); // Replace the existing default export with the wrapped one originalEntryServerModAST.body.splice(defaultExportIndex, 1, // @ts-expect-error - declaration works here because the AST is proxified by magicast defaultExportNode.declaration); // Adding our wrapped export originalEntryServerModAST.body.push(recast.types.builders.exportDefaultDeclaration(recast.types.builders.callExpression(recast.types.builders.memberExpression(recast.types.builders.identifier('Sentry'), recast.types.builders.identifier('wrapSentryHandleRequest')), [recast.types.builders.identifier('handleRequest')]))); } } } exports.instrumentHandleRequest = instrumentHandleRequest; function instrumentHandleError(originalEntryServerMod) { const originalEntryServerModAST = originalEntryServerMod.$ast; const handleErrorFunctionExport = originalEntryServerModAST.body.find((node) => { return (node.type === 'ExportNamedDeclaration' && node.declaration?.type === 'FunctionDeclaration' && node.declaration.id?.name === 'handleError'); }); const handleErrorFunctionVariableDeclarationExport = originalEntryServerModAST.body.find((node) => { if (node.type !== 'ExportNamedDeclaration' || node.declaration?.type !== 'VariableDeclaration') { return false; } const declarations = node.declaration.declarations; if (!declarations || declarations.length === 0) { return false; } const firstDeclaration = declarations[0]; if (!firstDeclaration || firstDeclaration.type !== 'VariableDeclarator') { return false; } const id = firstDeclaration.id; return id && id.type === 'Identifier' && id.name === 'handleError'; }); if (!handleErrorFunctionExport && !handleErrorFunctionVariableDeclarationExport) { prompts_1.default.log.warn(`Could not find function ${chalk_1.default.cyan('handleError')} in your server entry file. Creating one for you.`); const implementation = recast.parse(`const handleError = Sentry.createSentryHandleError({ logErrors: false })`).program.body[0]; originalEntryServerModAST.body.splice((0, utils_1.getAfterImportsInsertionIndex)(originalEntryServerModAST), 0, recast.types.builders.exportNamedDeclaration(implementation)); } else if ((handleErrorFunctionExport && // @ts-expect-error - StatementKind works here because the AST is proxified by magicast (0, magicast_1.generateCode)(handleErrorFunctionExport).code.includes('captureException')) || (handleErrorFunctionVariableDeclarationExport && // @ts-expect-error - StatementKind works here because the AST is proxified by magicast (0, magicast_1.generateCode)(handleErrorFunctionVariableDeclarationExport).code.includes('captureException'))) { (0, debug_1.debug)('Found captureException inside handleError, skipping adding it again'); } else if ((handleErrorFunctionExport && // @ts-expect-error - StatementKind works here because the AST is proxified by magicast (0, magicast_1.generateCode)(handleErrorFunctionExport).code.includes('createSentryHandleError')) || (handleErrorFunctionVariableDeclarationExport && // @ts-expect-error - StatementKind works here because the AST is proxified by magicast (0, magicast_1.generateCode)(handleErrorFunctionVariableDeclarationExport).code.includes('createSentryHandleError'))) { (0, debug_1.debug)('createSentryHandleError is already used, skipping adding it again'); } else if (handleErrorFunctionExport) { // Create the Sentry captureException call as an IfStatement const sentryCall = recast.parse(`if (!request.signal.aborted) { Sentry.captureException(error); }`).program.body[0]; // Safely insert the Sentry call at the beginning of the handleError function body // @ts-expect-error - declaration works here because the AST is proxified by magicast const declaration = handleErrorFunctionExport.declaration; if (declaration && declaration.body && declaration.body.body && Array.isArray(declaration.body.body)) { declaration.body.body.unshift(sentryCall); } else { (0, debug_1.debug)('Cannot safely access handleError function body, skipping instrumentation'); } } else if (handleErrorFunctionVariableDeclarationExport) { // Create the Sentry captureException call as an IfStatement const sentryCall = recast.parse(`if (!request.signal.aborted) { Sentry.captureException(error); }`).program.body[0]; // Safe access to existing handle error implementation with proper null checks // We know this is ExportNamedDeclaration with VariableDeclaration from the earlier find const exportDeclaration = handleErrorFunctionVariableDeclarationExport; if (!exportDeclaration.declaration || exportDeclaration.declaration.type !== 'VariableDeclaration' || !exportDeclaration.declaration.declarations || exportDeclaration.declaration.declarations.length === 0) { (0, debug_1.debug)('Cannot safely access handleError variable declaration, skipping instrumentation'); return; } const firstDeclaration = exportDeclaration.declaration.declarations[0]; if (!firstDeclaration || firstDeclaration.type !== 'VariableDeclarator' || !firstDeclaration.init) { (0, debug_1.debug)('Cannot safely access handleError variable declarator init, skipping instrumentation'); return; } const existingHandleErrorImplementation = firstDeclaration.init; const existingParams = existingHandleErrorImplementation.params; const existingBody = existingHandleErrorImplementation.body; const requestParam = { ...recast.types.builders.property('init', recast.types.builders.identifier('request'), // key recast.types.builders.identifier('request')), shorthand: true, }; // Add error and {request} parameters to handleError function if not present // When none of the parameters exist if (existingParams.length === 0) { existingParams.push(recast.types.builders.identifier('error'), recast.types.builders.objectPattern([requestParam])); // When only error parameter exists } else if (existingParams.length === 1) { existingParams.push(recast.types.builders.objectPattern([requestParam])); // When both parameters exist, but request is not destructured } else if (existingParams[1].type === 'ObjectPattern' && !existingParams[1].properties.some((prop) => (0, ast_utils_1.safeGetIdentifierName)(prop.key) === 'request')) { existingParams[1].properties.push(requestParam); } // Add the Sentry call to the function body existingBody.body.push(sentryCall); } } exports.instrumentHandleError = instrumentHandleError; //# sourceMappingURL=server-entry.js.map