UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

299 lines 15.8 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.runReactRouterWizard = void 0; // @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 Sentry = __importStar(require("@sentry/node")); const telemetry_1 = require("../telemetry"); const clack_1 = require("../utils/clack"); const mcp_config_1 = require("../utils/clack/mcp-config"); const package_json_1 = require("../utils/package-json"); const debug_1 = require("../utils/debug"); const sdk_example_1 = require("./sdk-example"); const sdk_setup_1 = require("./sdk-setup"); const templates_1 = require("./templates"); const abort_if_sportlight_not_supported_1 = require("../utils/abort-if-sportlight-not-supported"); async function runReactRouterWizard(options) { return (0, telemetry_1.withTelemetry)({ enabled: options.telemetryEnabled, integration: 'reactRouter', wizardOptions: options, }, () => runReactRouterWizardWithTelemetry(options)); } exports.runReactRouterWizard = runReactRouterWizard; async function runReactRouterWizardWithTelemetry(options) { (0, clack_1.printWelcome)({ wizardName: 'Sentry React Router Wizard', promoCode: options.promoCode, }); const packageJson = await (0, clack_1.getPackageDotJson)(); if (!packageJson) { prompts_1.default.log.error('Could not find a package.json file in the current directory'); return; } const typeScriptDetected = (0, clack_1.isUsingTypeScript)(); if (!(0, sdk_setup_1.isReactRouterV7)(packageJson)) { prompts_1.default.log.error('This wizard requires React Router v7. Please upgrade your React Router version to v7.0.0 or higher.\n\nFor upgrade instructions, visit: https://react-router.dev/upgrade/v7'); return; } await (0, clack_1.confirmContinueIfNoOrDirtyGitRepo)({ ignoreGitChanges: options.ignoreGitChanges, cwd: undefined, }); const sentryAlreadyInstalled = (0, package_json_1.hasPackageInstalled)('@sentry/react-router', packageJson); const projectData = await (0, clack_1.getOrAskForProjectData)(options, 'javascript-react-router'); if (projectData.spotlight) { return (0, abort_if_sportlight_not_supported_1.abortIfSpotlightNotSupported)('React Router'); } const { selectedProject, authToken, selfHosted, sentryUrl } = projectData; await (0, clack_1.installPackage)({ packageName: '@sentry/react-router', alreadyInstalled: sentryAlreadyInstalled, }); const featureSelection = await (0, clack_1.featureSelectionPrompt)([ { id: 'performance', prompt: `Do you want to enable ${chalk_1.default.bold('Tracing')} to track the performance of your application?`, enabledHint: 'recommended', }, { id: 'replay', prompt: `Do you want to enable ${chalk_1.default.bold('Session Replay')} to get a video-like reproduction of errors during a user session?`, enabledHint: 'recommended, but increases bundle size', }, { id: 'logs', prompt: `Do you want to enable ${chalk_1.default.bold('Logs')} to send your application logs to Sentry?`, enabledHint: 'recommended', }, { id: 'profiling', prompt: `Do you want to enable ${chalk_1.default.bold('Profiling')} to track application performance in detail?`, enabledHint: 'recommended for production debugging', }, ]); // Only ask about Instrumentation API when Tracing is enabled, since it depends on it let instrumentationAPISelected = false; if (featureSelection.performance) { const instrumentationAPISelection = await (0, clack_1.featureSelectionPrompt)([ { id: 'instrumentationAPI', prompt: `Do you want to use the ${chalk_1.default.bold('Instrumentation API')} for automatic tracing of loaders, actions, and middleware?`, enabledHint: 'recommended', }, ]); instrumentationAPISelected = instrumentationAPISelection.instrumentationAPI; } if (featureSelection.profiling) { const profilingAlreadyInstalled = (0, package_json_1.hasPackageInstalled)('@sentry/profiling-node', packageJson); await (0, clack_1.installPackage)({ packageName: '@sentry/profiling-node', alreadyInstalled: profilingAlreadyInstalled, }); } const createExamplePageSelection = await (0, clack_1.askShouldCreateExamplePage)(); (0, telemetry_1.traceStep)('Reveal missing entry files', () => { try { (0, sdk_setup_1.runReactRouterReveal)(); prompts_1.default.log.success('Entry files are ready for instrumentation'); } catch (e) { prompts_1.default.log.warn(`Could not run 'npx react-router reveal'. Please create your entry files manually using React Router v7 commands.`); (0, debug_1.debug)(e); } }); Sentry.setTag('instrumentation-api-selected', instrumentationAPISelected); let useInstrumentationAPI = instrumentationAPISelected; if (useInstrumentationAPI && !(0, sdk_setup_1.supportsInstrumentationAPI)(packageJson)) { Sentry.setTag('instrumentation-api-version-guard', true); const detectedVersion = (0, sdk_setup_1.getReactRouterVersion)(packageJson) ?? 'unknown'; prompts_1.default.log.warn(`The Instrumentation API requires React Router ${chalk_1.default.cyan('>=7.9.5')} (detected ${chalk_1.default.cyan(detectedVersion)}). Your version does not meet this requirement.\n` + `Continuing without the Instrumentation API. Please upgrade React Router to use this feature:\n` + chalk_1.default.dim('https://docs.sentry.io/platforms/javascript/guides/react-router/')); useInstrumentationAPI = false; } Sentry.setTag('use-instrumentation-api', useInstrumentationAPI); await (0, telemetry_1.traceStep)('Initialize Sentry on client entry', async () => { try { await (0, sdk_setup_1.initializeSentryOnEntryClient)(selectedProject.keys[0].dsn.public, featureSelection.performance, featureSelection.replay, featureSelection.logs, typeScriptDetected, useInstrumentationAPI); } catch (e) { prompts_1.default.log.warn(`Could not initialize Sentry on client entry automatically.`); const clientEntryFilename = `entry.client.${typeScriptDetected ? 'tsx' : 'jsx'}`; const manualClientContent = (0, templates_1.getManualClientEntryContent)(selectedProject.keys[0].dsn.public, featureSelection.performance, featureSelection.replay, featureSelection.logs, useInstrumentationAPI); await (0, clack_1.showCopyPasteInstructions)({ filename: clientEntryFilename, codeSnippet: manualClientContent, hint: 'This enables error tracking and performance monitoring for your React Router app', }); (0, debug_1.debug)(e); } }); await (0, telemetry_1.traceStep)('Instrument root route', async () => { try { await (0, sdk_setup_1.instrumentRootRoute)(typeScriptDetected); } catch (e) { prompts_1.default.log.warn(`Could not instrument root route automatically.`); const rootFilename = `app/root.${typeScriptDetected ? 'tsx' : 'jsx'}`; const manualRootContent = (0, templates_1.getManualRootContent)(typeScriptDetected); await (0, clack_1.showCopyPasteInstructions)({ filename: rootFilename, codeSnippet: manualRootContent, hint: 'This adds error boundary integration to capture exceptions in your React Router app', }); (0, debug_1.debug)(e); } }); await (0, telemetry_1.traceStep)('Instrument server entry', async () => { try { await (0, sdk_setup_1.instrumentSentryOnEntryServer)(typeScriptDetected, useInstrumentationAPI); } catch (e) { prompts_1.default.log.warn(`Could not initialize Sentry on server entry automatically.`); const serverEntryFilename = `entry.server.${typeScriptDetected ? 'tsx' : 'jsx'}`; const manualServerContent = (0, templates_1.getManualServerEntryContent)(useInstrumentationAPI); await (0, clack_1.showCopyPasteInstructions)({ filename: serverEntryFilename, codeSnippet: manualServerContent, hint: 'This configures server-side request handling and error tracking', }); (0, debug_1.debug)(e); } }); await (0, telemetry_1.traceStep)('Create server instrumentation file', async () => { try { (0, sdk_setup_1.createServerInstrumentationFile)(selectedProject.keys[0].dsn.public, { performance: featureSelection.performance, replay: featureSelection.replay, logs: featureSelection.logs, profiling: featureSelection.profiling, }); } catch (e) { prompts_1.default.log.warn('Could not create a server instrumentation file automatically.'); const manualServerInstrumentContent = (0, templates_1.getManualServerInstrumentContent)(selectedProject.keys[0].dsn.public, featureSelection.performance, featureSelection.profiling, featureSelection.logs); await (0, clack_1.showCopyPasteInstructions)({ filename: 'instrument.server.mjs', codeSnippet: manualServerInstrumentContent, hint: 'Create the file if it does not exist - this initializes Sentry before your application starts', }); (0, debug_1.debug)(e); } }); await (0, telemetry_1.traceStep)('Update package.json scripts', async () => { try { await (0, sdk_setup_1.updatePackageJsonScripts)(); } catch (e) { prompts_1.default.log.warn('Could not update start script automatically.'); await (0, clack_1.showCopyPasteInstructions)({ filename: 'package.json', codeSnippet: (0, clack_1.makeCodeSnippet)(true, (unchanged, plus, minus) => { return unchanged(`{ scripts: { ${minus('"start": "react-router dev"')} ${plus('"start": "NODE_ENV=production NODE_OPTIONS=\'--import ./instrument.server.mjs\' react-router-serve ./build/server/index.js"')} ${minus('"dev": "react-router dev"')} ${plus('"dev": "NODE_OPTIONS=\'--import ./instrument.server.mjs\' react-router dev"')} }, // ... rest of your package.json }`); }), }); (0, debug_1.debug)(e); } }); await (0, telemetry_1.traceStep)('Create build plugin env file', async () => { try { await (0, clack_1.addDotEnvSentryBuildPluginFile)(authToken); } catch (e) { prompts_1.default.log.warn('Could not create .env.sentry-build-plugin file. Please create it manually.'); (0, debug_1.debug)(e); } }); // Validate auth token before configuring sourcemap uploads if (!authToken) { prompts_1.default.log.warn(`${chalk_1.default.yellow('Warning:')} No auth token found. Sourcemap uploads will not work without ${chalk_1.default.cyan('SENTRY_AUTH_TOKEN')}.\n` + `Please set ${chalk_1.default.cyan('SENTRY_AUTH_TOKEN')} in your ${chalk_1.default.cyan('.env.sentry-build-plugin')} file or environment variables.`); } // Configure Vite plugin for sourcemap uploads await (0, telemetry_1.traceStep)('Configure Vite plugin for sourcemap uploads', async () => { try { await (0, sdk_setup_1.configureReactRouterVitePlugin)(selectedProject.organization.slug, selectedProject.slug); } catch (e) { prompts_1.default.log.warn(`Could not configure Vite plugin for sourcemap uploads automatically.`); await (0, clack_1.showCopyPasteInstructions)({ filename: `vite.config.${typeScriptDetected ? 'ts' : 'js'}`, codeSnippet: (0, templates_1.getManualViteConfigContent)(selectedProject.organization.slug, selectedProject.slug), hint: 'This enables automatic sourcemap uploads during build for better error tracking', }); (0, debug_1.debug)(e); } }); // Configure React Router config for build hook await (0, telemetry_1.traceStep)('Configure React Router build hook', async () => { try { await (0, sdk_setup_1.configureReactRouterConfig)(typeScriptDetected); } catch (e) { prompts_1.default.log.warn(`Could not configure React Router build hook automatically.`); await (0, clack_1.showCopyPasteInstructions)({ filename: `react-router.config.${typeScriptDetected ? 'ts' : 'js'}`, codeSnippet: (0, templates_1.getManualReactRouterConfigContent)(typeScriptDetected), hint: 'This enables automatic sourcemap uploads at the end of the build process', }); (0, debug_1.debug)(e); } }); // Create example page if requested if (createExamplePageSelection) { await (0, telemetry_1.traceStep)('Create example page', async () => { await (0, sdk_example_1.createExamplePage)({ selfHosted, orgSlug: selectedProject.organization.slug, projectId: selectedProject.id, url: sentryUrl, isTS: typeScriptDetected, projectDir: process.cwd(), }); }); } await (0, clack_1.runPrettierIfInstalled)({ cwd: undefined }); // Offer optional project-scoped MCP config for Sentry with org and project scope await (0, mcp_config_1.offerProjectScopedMcpConfig)(selectedProject.organization.slug, selectedProject.slug); prompts_1.default.outro(`${chalk_1.default.green('Successfully installed the Sentry React Router SDK!')}${createExamplePageSelection ? `\n\nYou can validate your setup by visiting ${chalk_1.default.cyan('"/sentry-example-page"')} in your application.` : ''}`); } //# sourceMappingURL=react-router-wizard.js.map