@sentry/wizard
Version:
Sentry wizard helping you to configure your project
254 lines • 13.1 kB
JavaScript
"use strict";
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 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");
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) {
prompts_1.default.log.warn('Spotlight mode is not yet supported for React Router.');
prompts_1.default.log.info('Spotlight is currently only available for Next.js.');
await (0, clack_1.abort)('Exiting wizard', 0);
return;
}
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',
},
]);
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)(typeScriptDetected);
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);
}
});
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);
}
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);
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);
}
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)();
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_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