UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

367 lines (327 loc) 11.1 kB
// @ts-ignore - clack is ESM and TS complains about that. It works though import clack from '@clack/prompts'; import chalk from 'chalk'; import * as Sentry from '@sentry/node'; import { abort, abortIfCancelled, confirmContinueIfNoOrDirtyGitRepo, SENTRY_DOT_ENV_FILE, printWelcome, SENTRY_CLI_RC_FILE, getOrAskForProjectData, } from '../utils/clack-utils'; import { isUnicodeSupported } from '../utils/vendor/is-unicorn-supported'; import { SourceMapUploadToolConfigurationOptions } from './tools/types'; import { configureVitePlugin } from './tools/vite'; import { setupNpmScriptInCI, configureSentryCLI } from './tools/sentry-cli'; import { configureWebPackPlugin } from './tools/webpack'; import { configureTscSourcemapGenerationFlow } from './tools/tsc'; import { configureRollupPlugin } from './tools/rollup'; import { configureEsbuildPlugin } from './tools/esbuild'; import { WizardOptions } from '../utils/types'; import { configureCRASourcemapGenerationFlow } from './tools/create-react-app'; import { ensureMinimumSdkVersionIsInstalled } from './utils/sdk-version'; import { traceStep, withTelemetry } from '../telemetry'; import { checkIfMoreSuitableWizardExistsAndAskForRedirect } from './utils/other-wizards'; import { configureAngularSourcemapGenerationFlow } from './tools/angular'; import { detectUsedTool, SupportedTools } from './utils/detect-tool'; import { configureNextJsSourceMapsUpload } from './tools/nextjs'; import { configureRemixSourceMapsUpload } from './tools/remix'; import { detectPackageManger } from '../utils/package-manager'; import { getIssueStreamUrl } from '../utils/url'; export async function runSourcemapsWizard( options: WizardOptions, ): Promise<void> { return withTelemetry( { enabled: options.telemetryEnabled, integration: 'sourcemaps', }, () => runSourcemapsWizardWithTelemetry(options), ); } async function runSourcemapsWizardWithTelemetry( options: WizardOptions, ): Promise<void> { printWelcome({ wizardName: 'Sentry Source Maps Upload Configuration Wizard', message: `This wizard will help you upload source maps to Sentry as part of your build. Thank you for using Sentry :)${ options.telemetryEnabled ? ` (This setup wizard sends telemetry data and crash reports to Sentry. You can turn this off by running the wizard with the '--disable-telemetry' flag.)` : '' }`, promoCode: options.promoCode, }); const moreSuitableWizard = await traceStep( 'check-framework-wizard', checkIfMoreSuitableWizardExistsAndAskForRedirect, ); if (moreSuitableWizard) { await traceStep('run-framework-wizard', () => moreSuitableWizard(options)); return; } await confirmContinueIfNoOrDirtyGitRepo(); await traceStep('check-sdk-version', ensureMinimumSdkVersionIsInstalled); const { selfHosted, selectedProject, sentryUrl, authToken } = await getOrAskForProjectData(options); const wizardOptionsWithPreSelectedProject = { ...options, preSelectedProject: { project: selectedProject, authToken, selfHosted, }, }; const selectedTool = await traceStep('select-tool', askForUsedBundlerTool); Sentry.setTag('selected-tool', selectedTool); if (selectedTool === 'no-tool') { clack.log.info( "No Problem! But in this case, there's nothing to configure :)", ); await abort('Exiting, have a great day!', 0); return; } await traceStep('tool-setup', () => startToolSetupFlow( selectedTool, { orgSlug: selectedProject.organization.slug, projectSlug: selectedProject.slug, selfHosted, url: sentryUrl, authToken, }, wizardOptionsWithPreSelectedProject, ), ); await traceStep('ci-setup', () => configureCI(selectedTool, authToken)); traceStep('outro', () => printOutro( sentryUrl, selectedProject.organization.slug, selectedProject.id, ), ); } async function askForUsedBundlerTool(): Promise<SupportedTools> { const selectedTool = await abortIfCancelled( clack.select({ message: 'Which framework, bundler or build tool are you using?', options: [ { label: 'Angular', value: 'angular', hint: 'Select this option if you are using Angular.', }, { label: 'Create React App', value: 'create-react-app', hint: 'Select this option if you set up your app with Create React App.', }, { label: 'Next.js', value: 'nextjs', hint: 'Select this option if you want to set up source maps in a NextJS project.', }, { label: 'Remix', value: 'remix', hint: 'Select this option if you want to set up source maps in a Remix project.', }, { label: 'Webpack', value: 'webpack', hint: 'Select this if you are using Webpack and you have access to your Webpack config.', }, { label: 'Vite', value: 'vite', hint: 'Select this if you are using Vite and you have access to your Vite config.', }, { label: 'esbuild', value: 'esbuild', hint: 'Select this if you are using esbuild and you have access to your esbuild config.', }, { label: 'Rollup', value: 'rollup', hint: 'Select this if you are using Rollup and you have access to your Rollup config.', }, { label: 'tsc', value: 'tsc', hint: 'Configure source maps when using tsc as build tool', }, { label: 'I use another tool', value: 'sentry-cli', hint: 'This will configure source maps upload for you using sentry-cli', }, { label: "I don't minify, transpile or bundle my code", value: 'no-tool', hint: 'This will exit the wizard', }, ], initialValue: await detectUsedTool(), }), ); return selectedTool; } async function startToolSetupFlow( selctedTool: SupportedTools, options: SourceMapUploadToolConfigurationOptions, wizardOptions: WizardOptions, ): Promise<void> { switch (selctedTool) { case 'webpack': await configureWebPackPlugin(options); break; case 'vite': await configureVitePlugin(options); break; case 'esbuild': await configureEsbuildPlugin(options); break; case 'rollup': await configureRollupPlugin(options); break; case 'tsc': await configureSentryCLI(options, configureTscSourcemapGenerationFlow); break; case 'create-react-app': await configureSentryCLI(options, configureCRASourcemapGenerationFlow); break; case 'angular': await configureSentryCLI( options, configureAngularSourcemapGenerationFlow, ); break; case 'nextjs': await configureNextJsSourceMapsUpload(options, wizardOptions); break; case 'remix': await configureRemixSourceMapsUpload(options, wizardOptions); break; default: await configureSentryCLI(options); break; } } export async function configureCI( selectedTool: SupportedTools, authToken: string, ): Promise<void> { const isUsingCI = await abortIfCancelled( clack.select({ message: `Are you using a CI/CD tool to build and deploy your application?`, options: [ { label: 'Yes', hint: 'I use a tool like GitHub Actions, GitLab, CircleCI, TravisCI, Jenkins, Vercel, ...', value: true, }, { label: 'No', hint: 'I build and deploy my application manually', value: false, }, ], initialValue: true, }), ); Sentry.setTag('using-ci', isUsingCI); const isCliBasedFlowTool = [ 'sentry-cli', 'tsc', 'angular', 'create-react-app', ].includes(selectedTool); // some non-cli-based flows also use the .sentryclirc file const usesSentryCliRc = selectedTool === 'nextjs'; const authTokenFile = isCliBasedFlowTool || usesSentryCliRc ? SENTRY_CLI_RC_FILE : SENTRY_DOT_ENV_FILE; if (!isUsingCI) { clack.log.info( `No Problem! Just make sure that the Sentry auth token from ${chalk.cyan( authTokenFile, )} is available whenever you build and deploy your app.`, ); return; } if (isCliBasedFlowTool) { await traceStep('ci-npm-script-setup', setupNpmScriptInCI); } await traceStep('ci-auth-token-setup', () => setupAuthTokenInCI(authToken)); } async function setupAuthTokenInCI(authToken: string) { clack.log.step( 'Add the Sentry authentication token as an environment variable to your CI setup:', ); // Intentially logging directly to console here so that the code can be copied/pasted directly // eslint-disable-next-line no-console console.log( chalk.greenBright(` SENTRY_AUTH_TOKEN=${authToken} `), ); clack.log.warn( chalk.yellow('DO NOT commit this auth token to your repository!'), ); const addedEnvVarToCI = await abortIfCancelled( clack.select({ message: 'Did you configure CI as shown above?', options: [ { label: 'Yes, continue!', value: true }, { label: "I'll do it later...", value: false, hint: chalk.yellow( 'You need to set the auth token to upload source maps in CI', ), }, ], initialValue: true, }), ); Sentry.setTag('added-env-var-to-ci', addedEnvVarToCI); if (!addedEnvVarToCI) { clack.log.info("Don't forget! :)"); } } function printOutro(url: string, orgSlug: string, projectId: string) { const packageManager = detectPackageManger(); const buildCommand = packageManager?.buildCommand ?? 'npm run build'; const issueStreamUrl = getIssueStreamUrl({ url, orgSlug, projectId }); const arrow = isUnicodeSupported() ? '→' : '->'; clack.outro(`${chalk.green("That's it - everything is set up!")} ${chalk.cyan(`Test and validate your setup locally with the following Steps: 1. Build your application in ${chalk.bold('production mode')}. ${chalk.gray(`${arrow} For example, run ${chalk.bold(buildCommand)}.`)} ${chalk.gray( `${arrow} You should see source map upload logs in your console.`, )} 2. Run your application and throw a test error. ${chalk.gray(`${arrow} The error should appear in Sentry:`)} ${chalk.gray(`${arrow} ${issueStreamUrl}`)} 3. Open the error in Sentry and verify that it's source-mapped. ${chalk.gray( `${arrow} The stack trace should show your original source code.`, )} `)} ${chalk.dim( `If you encounter any issues, please refer to the Troubleshooting Guide: https://docs.sentry.io/platforms/javascript/sourcemaps/troubleshooting_js If the guide doesn't help or you encounter a bug, please let us know: https://github.com/getsentry/sentry-javascript/issues`, )} `); }