-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat(nitro): Handle sourcemap preparation and upload #19304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: awad/create-nitro-sdk-metadata
Are you sure you want to change the base?
Changes from all commits
572a830
d3b0f0f
06d6b67
5117570
ea8606e
d114ac3
af91304
4a4a873
2f93472
4195464
80bc2ee
fb181b5
14b31ea
52626ee
1c13825
36ddac0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,33 @@ | ||
| import type { BuildTimeOptionsBase } from '@sentry/core'; | ||
| import type { NitroConfig } from 'nitro/types'; | ||
| import { createNitroModule } from './module'; | ||
| import { configureSourcemapSettings } from './sourceMaps'; | ||
|
|
||
| type SentryNitroOptions = { | ||
| // TODO: Add options | ||
| }; | ||
| export type SentryNitroOptions = BuildTimeOptionsBase; | ||
|
|
||
| /** | ||
| * Modifies the passed in Nitro configuration with automatic build-time instrumentation. | ||
| * | ||
| * @param config A Nitro configuration object, as usually exported in `nitro.config.ts` or `nitro.config.mjs`. | ||
| * @returns The modified config to be exported | ||
| */ | ||
| export function withSentryConfig(config: NitroConfig, moduleOptions?: SentryNitroOptions): NitroConfig { | ||
| return setupSentryNitroModule(config, moduleOptions); | ||
| export function withSentryConfig(config: NitroConfig, sentryOptions?: SentryNitroOptions): NitroConfig { | ||
| return setupSentryNitroModule(config, sentryOptions); | ||
| } | ||
|
|
||
| /** | ||
| * Sets up the Sentry Nitro module, useful for meta framework integrations. | ||
| */ | ||
| export function setupSentryNitroModule( | ||
| config: NitroConfig, | ||
| _moduleOptions?: SentryNitroOptions, | ||
| moduleOptions?: SentryNitroOptions, | ||
| _serverConfigFile?: string, | ||
| ): NitroConfig { | ||
| if (!config.tracingChannel) { | ||
| config.tracingChannel = true; | ||
| } | ||
|
|
||
| const { sentryEnabledSourcemaps } = configureSourcemapSettings(config, moduleOptions); | ||
|
|
||
| config.modules = config.modules || []; | ||
| config.modules.push(createNitroModule()); | ||
| config.modules.push(createNitroModule(moduleOptions, sentryEnabledSourcemaps)); | ||
|
|
||
| return config; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,17 @@ | ||
| import type { NitroModule } from 'nitro/types'; | ||
| import type { SentryNitroOptions } from './config'; | ||
| import { instrumentServer } from './instruments/instrumentServer'; | ||
| import { setupSourceMaps } from './sourceMaps'; | ||
|
|
||
| /** | ||
| * Creates a Nitro module to setup the Sentry SDK. | ||
| */ | ||
| export function createNitroModule(): NitroModule { | ||
| export function createNitroModule(sentryOptions?: SentryNitroOptions, sentryEnabledSourcemaps?: boolean): NitroModule { | ||
| return { | ||
| name: 'sentry', | ||
| setup: nitro => { | ||
| instrumentServer(nitro); | ||
| setupSourceMaps(nitro, sentryOptions, sentryEnabledSourcemaps); | ||
| }, | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| import type { Options as BundlerPluginOptions } from '@sentry/bundler-plugin-core'; | ||
| import { createSentryBuildPluginManager } from '@sentry/bundler-plugin-core'; | ||
| import type { Nitro, NitroConfig } from 'nitro/types'; | ||
| import type { SentryNitroOptions } from './config'; | ||
|
|
||
| /** | ||
| * Registers a `compiled` hook to upload source maps after the build completes. | ||
| */ | ||
| export function setupSourceMaps(nitro: Nitro, options?: SentryNitroOptions, sentryEnabledSourcemaps?: boolean): void { | ||
| // The `compiled` hook fires on EVERY rebuild during `nitro dev` watch mode. | ||
| // nitro.options.dev is reliably set by the time module setup runs. | ||
| if (shouldSkipSourcemapUpload(nitro, options)) { | ||
| return; | ||
| } | ||
|
|
||
| nitro.hooks.hook('compiled', async (_nitro: Nitro) => { | ||
| await handleSourceMapUpload(_nitro, options, sentryEnabledSourcemaps); | ||
| }); | ||
|
sentry[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Determines if sourcemap uploads should be skipped. | ||
| */ | ||
| function shouldSkipSourcemapUpload(nitro: Nitro, options?: SentryNitroOptions): boolean { | ||
| return !!( | ||
| nitro.options.dev || | ||
| nitro.options.preset === 'nitro-prerender' || | ||
| nitro.options.sourcemap === false || | ||
| options?.sourcemaps?.disable === true | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Handles the actual source map upload after the build completes. | ||
| */ | ||
| async function handleSourceMapUpload( | ||
| nitro: Nitro, | ||
| options?: SentryNitroOptions, | ||
| sentryEnabledSourcemaps?: boolean, | ||
| ): Promise<void> { | ||
| const outputDir = nitro.options.output.serverDir; | ||
| const pluginOptions = getPluginOptions(options, sentryEnabledSourcemaps, outputDir); | ||
|
|
||
| const sentryBuildPluginManager = createSentryBuildPluginManager(pluginOptions, { | ||
| buildTool: 'nitro', | ||
| loggerPrefix: '[@sentry/nitro]', | ||
| }); | ||
|
|
||
| await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal(); | ||
| await sentryBuildPluginManager.createRelease(); | ||
|
|
||
| await sentryBuildPluginManager.injectDebugIds([outputDir]); | ||
|
|
||
| if (options?.sourcemaps?.disable !== 'disable-upload') { | ||
| await sentryBuildPluginManager.uploadSourcemaps([outputDir], { | ||
| // We don't prepare the artifacts because we injected debug IDs manually before | ||
| prepareArtifacts: false, | ||
| }); | ||
| await sentryBuildPluginManager.deleteArtifacts(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you manually also check if this deletes the correct source maps - based on the user-set source map setting?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried 3 scenarios locally:
I did miss that it should only delete nitro's sourcemaps (fixed now in 1c13825) but not sure what other scenarios to take into account. Also checked that it respects the |
||
| } | ||
|
sentry[bot] marked this conversation as resolved.
logaretm marked this conversation as resolved.
cursor[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** | ||
| * Normalizes the beginning of a path from e.g. ../../../ to ./ | ||
| */ | ||
| function normalizePath(path: string): string { | ||
| return path.replace(/^(\.\.\/)+/, './'); | ||
| } | ||
|
|
||
| /** | ||
| * Removes a trailing slash from a path so glob patterns can be appended cleanly. | ||
| */ | ||
| function removeTrailingSlash(path: string): string { | ||
| return path.replace(/\/$/, ''); | ||
| } | ||
|
|
||
| /** | ||
| * Builds the plugin options for `createSentryBuildPluginManager` from the Sentry Nitro options. | ||
| * | ||
| * Only exported for testing purposes. | ||
| */ | ||
| // oxlint-disable-next-line complexity | ||
| export function getPluginOptions( | ||
| options?: SentryNitroOptions, | ||
| sentryEnabledSourcemaps?: boolean, | ||
| outputDir?: string, | ||
| ): BundlerPluginOptions { | ||
| const defaultFilesToDelete = | ||
| sentryEnabledSourcemaps && outputDir ? [`${removeTrailingSlash(outputDir)}/**/*.map`] : undefined; | ||
|
|
||
| if (options?.debug && defaultFilesToDelete && options?.sourcemaps?.filesToDeleteAfterUpload === undefined) { | ||
| // eslint-disable-next-line no-console | ||
| console.log( | ||
| `[@sentry/nitro] Setting \`sourcemaps.filesToDeleteAfterUpload: ["${defaultFilesToDelete[0]}"]\` to delete generated source maps after they were uploaded to Sentry.`, | ||
| ); | ||
| } | ||
|
|
||
| return { | ||
| org: options?.org ?? process.env.SENTRY_ORG, | ||
| project: options?.project ?? process.env.SENTRY_PROJECT, | ||
| authToken: options?.authToken ?? process.env.SENTRY_AUTH_TOKEN, | ||
| url: options?.sentryUrl ?? process.env.SENTRY_URL, | ||
| headers: options?.headers, | ||
| telemetry: options?.telemetry ?? true, | ||
| debug: options?.debug ?? false, | ||
| silent: options?.silent ?? false, | ||
| errorHandler: options?.errorHandler, | ||
| sourcemaps: { | ||
| disable: options?.sourcemaps?.disable, | ||
| assets: options?.sourcemaps?.assets, | ||
| ignore: options?.sourcemaps?.ignore, | ||
| filesToDeleteAfterUpload: options?.sourcemaps?.filesToDeleteAfterUpload ?? defaultFilesToDelete, | ||
| rewriteSources: options?.sourcemaps?.rewriteSources ?? ((source: string) => normalizePath(source)), | ||
| }, | ||
| release: options?.release, | ||
| bundleSizeOptimizations: options?.bundleSizeOptimizations, | ||
| _metaOptions: { | ||
| telemetry: { | ||
| metaFramework: 'nitro', | ||
| }, | ||
| }, | ||
|
cursor[bot] marked this conversation as resolved.
sentry[bot] marked this conversation as resolved.
|
||
| }; | ||
| } | ||
|
|
||
| /* Source map configuration rules: | ||
| 1. User explicitly disabled source maps (sourcemap: false) | ||
| - Keep their setting, emit a warning that errors won't be unminified in Sentry | ||
| - We will not upload anything | ||
| 2. User enabled source map generation (true) | ||
| - Keep their setting (don't modify besides uploading) | ||
| 3. User did not set source maps (undefined) | ||
| - We enable source maps for Sentry | ||
| - Configure `filesToDeleteAfterUpload` to clean up .map files after upload | ||
| */ | ||
| export function configureSourcemapSettings( | ||
| config: NitroConfig, | ||
| moduleOptions?: SentryNitroOptions, | ||
| ): { sentryEnabledSourcemaps: boolean } { | ||
| const sourcemapUploadDisabled = moduleOptions?.sourcemaps?.disable === true; | ||
| if (sourcemapUploadDisabled) { | ||
| return { sentryEnabledSourcemaps: false }; | ||
| } | ||
|
|
||
| if (config.sourcemap === false) { | ||
| // eslint-disable-next-line no-console | ||
| console.warn( | ||
| '[@sentry/nitro] You have explicitly disabled source maps (`sourcemap: false`). Sentry will not upload source maps, and errors will not be unminified. To let Sentry handle source maps, remove the `sourcemap` option from your Nitro config, or use `sourcemaps: { disable: true }` in your Sentry options to silence this warning.', | ||
| ); | ||
| return { sentryEnabledSourcemaps: false }; | ||
| } | ||
|
|
||
| let sentryEnabledSourcemaps = false; | ||
| if (config.sourcemap === true) { | ||
| if (moduleOptions?.debug) { | ||
| // eslint-disable-next-line no-console | ||
| console.log('[@sentry/nitro] Source maps are already enabled. Sentry will upload them for error unminification.'); | ||
| } | ||
| } else { | ||
| // User did not explicitly set sourcemap — enable it for Sentry | ||
| config.sourcemap = true; | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| sentryEnabledSourcemaps = true; | ||
| if (moduleOptions?.debug) { | ||
|
Comment on lines
+158
to
+162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The function only checks for boolean Suggested FixUpdate the conditional logic to correctly handle string values for Prompt for AI Agent |
||
| // eslint-disable-next-line no-console | ||
| console.log( | ||
|
sentry[bot] marked this conversation as resolved.
|
||
| '[@sentry/nitro] Enabled source map generation for Sentry. Source map files will be deleted after upload.', | ||
| ); | ||
| } | ||
| } | ||
|
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| // Nitro v3 has a `sourcemapMinify` plugin that destructively deletes `sourcesContent`, | ||
| // `x_google_ignoreList`, and clears `mappings` for any chunk containing `node_modules`. | ||
| // This makes sourcemaps unusable for Sentry. | ||
| config.experimental = config.experimental || {}; | ||
| config.experimental.sourcemapMinify = false; | ||
|
|
||
| return { sentryEnabledSourcemaps }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.