Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/nitro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"nitro": ">=3.0.260415-beta"
},
"dependencies": {
"@sentry/bundler-plugin-core": "^5.2.0",
"@sentry/core": "10.48.0",
"@sentry/node": "10.48.0",
"otel-tracing-channel": "^0.2.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/nitro/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default [
makeBaseNPMConfig({
entrypoints: ['src/index.ts', 'src/runtime/plugins/server.ts'],
packageSpecificConfig: {
external: [/^nitro/, 'otel-tracing-channel', /^h3/, /^srvx/],
external: [/^nitro/, 'otel-tracing-channel', /^h3/, /^srvx/, '@sentry/bundler-plugin-core'],
},
}),
),
Expand Down
35 changes: 25 additions & 10 deletions packages/nitro/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,49 @@
import type { Options as SentryBundlerPluginOptions } from '@sentry/bundler-plugin-core';
import type { NitroConfig } from 'nitro/types';
import { createNitroModule } from './module';
import { configureSourcemapSettings } from './sourceMaps';

type SentryNitroOptions = {
// TODO: Add options
};
export type SentryNitroOptions = Pick<
SentryBundlerPluginOptions,
Comment on lines +6 to +7
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can/should use BuildTimeOptionsBase here. That way we have a second layer of type versioning. The underlying build-time options from the bundler plugins might have breaking changes (that's why unstable_sentryBundlerPluginOptions uses the directly exported type of the bundler plugins).

The BuildTimeOptionsBase follows the semantic versioning of this repository.

| 'org'
| 'project'
| 'authToken'
| 'url'
| 'headers'
| 'debug'
| 'silent'
| 'errorHandler'
| 'telemetry'
| 'disable'
| 'sourcemaps'
| 'release'
| 'bundleSizeOptimizations'
| '_metaOptions'
>;

/**
* 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;
}

configureSourcemapSettings(config, moduleOptions);

config.modules = config.modules || [];
config.modules.push(createNitroModule());
config.modules.push(createNitroModule(moduleOptions));

return config;
}
5 changes: 4 additions & 1 deletion packages/nitro/src/module.ts
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): NitroModule {
return {
name: 'sentry',
setup: nitro => {
instrumentServer(nitro);
setupSourceMaps(nitro, sentryOptions);
},
};
}
121 changes: 121 additions & 0 deletions packages/nitro/src/sourceMaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { Options } from '@sentry/bundler-plugin-core';
import { createSentryBuildPluginManager } from '@sentry/bundler-plugin-core';
import { debug } from '@sentry/core';
import type { Nitro, NitroConfig } from 'nitro/types';
import type { SentryNitroOptions } from './config';
Comment on lines +1 to +5

/**
* Registers a `compiled` hook to upload source maps after the build completes.
*/
export function setupSourceMaps(nitro: Nitro, options?: SentryNitroOptions): 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 (nitro.options.dev) {
return;
}

// Respect user's explicit disable
if (options?.sourcemaps?.disable === true || options?.disable === true) {
return;
}

nitro.hooks.hook('compiled', async (_nitro: Nitro) => {
await handleSourceMapUpload(_nitro, options);
});
}

/**
* Handles the actual source map upload after the build completes.
*/
async function handleSourceMapUpload(nitro: Nitro, options?: SentryNitroOptions): Promise<void> {
const outputDir = nitro.options.output.serverDir;
const pluginOptions = getPluginOptions(options);

const sentryBuildPluginManager = createSentryBuildPluginManager(pluginOptions, {
buildTool: 'nitro',
loggerPrefix: '[@sentry/nitro]',
});

await sentryBuildPluginManager.telemetry.emitBundlerPluginExecutionSignal();
await sentryBuildPluginManager.createRelease();

if (options?.sourcemaps?.disable !== 'disable-upload') {
await sentryBuildPluginManager.injectDebugIds([outputDir]);
await sentryBuildPluginManager.uploadSourcemaps([outputDir], {
// We don't prepare the artifacts because we injected debug IDs manually before
prepareArtifacts: false,
});
}
Comment on lines +42 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: When sourcemaps.disable is set to 'disable-upload', the code incorrectly skips injecting debug IDs, not just the source map upload, breaking manual upload workflows.
Severity: HIGH

Suggested Fix

Move the injectDebugIds() call out of the conditional block that checks options?.sourcemaps?.disable !== 'disable-upload'. This check should only wrap the uploadSourcemaps() call, ensuring debug IDs are injected even when automatic uploads are disabled.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/nitro/src/sourceMaps.ts#L42-L48

Potential issue: The logic in `handleSourceMapUpload` incorrectly skips debug ID
injection when source map uploads are disabled. The condition `if
(options?.sourcemaps?.disable !== 'disable-upload')` wraps both the `injectDebugIds()`
and `uploadSourcemaps()` calls. According to documentation, when
`options.sourcemaps.disable` is set to `'disable-upload'`, the plugin should still
inject debug IDs to support manual uploads, but skip the automatic upload step. The
current implementation skips both actions, which means build artifacts will lack the
necessary debug IDs for users relying on a manual upload workflow.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +42 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disable-upload incorrectly skips debug ID injection

High Severity

When sourcemaps.disable is 'disable-upload', the condition options?.sourcemaps?.disable !== 'disable-upload' causes both injectDebugIds and uploadSourcemaps to be skipped. According to the @sentry/bundler-plugin-core documentation, disable-upload means "will not upload source maps but will inject debug IDs." The injectDebugIds call needs to happen outside this conditional so it still runs in disable-upload mode. The Next.js SDK handles this correctly by separating the two operations.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 973bf37. Configure here.


await sentryBuildPluginManager.deleteArtifacts();
Comment on lines +48 to +50
}

/**
* Normalizes the beginning of a path from e.g. ../../../ to ./
*/
function normalizePath(path: string): string {
return path.replace(/^(\.\.\/)+/, './');
}

/**
* Builds the plugin options for `createSentryBuildPluginManager` from the Sentry Nitro options.
*
* Only exported for testing purposes.
*/
export function getPluginOptions(options?: SentryNitroOptions): Options {
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?.url ?? 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 ?? ['**/*.map'],
rewriteSources: (source: string) => normalizePath(source),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User-provided rewriteSources option silently ignored

Medium Severity

The rewriteSources property in getPluginOptions is hardcoded to normalizePath and ignores any user-provided options?.sourcemaps?.rewriteSources. Since SentryNitroOptions picks the full sourcemaps type (which includes rewriteSources), users can set this option but it's silently dropped. The Nuxt SDK handles this correctly with sourcemapsOptions.rewriteSources ?? normalizePath, falling back to the default only when the user doesn't provide one.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 973bf37. Configure here.

},
release: options?.release,
bundleSizeOptimizations: options?.bundleSizeOptimizations,
_metaOptions: {
telemetry: {
metaFramework: 'nitro',
},
...options?._metaOptions,
Comment on lines +86 to +89
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_metaOptions spread can override metaFramework: 'nitro'

Low Severity

The spread ...options?._metaOptions comes after the telemetry property, so any user-provided _metaOptions with a telemetry key will completely overwrite metaFramework: 'nitro'. The corresponding test asserts this value is "always" set to 'nitro', but the implementation doesn't guarantee it. The spread and default should be ordered to preserve the metaFramework value.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 973bf37. Configure here.

};
}

/**
* Configures the Nitro config to enable source map generation.
*/
export function configureSourcemapSettings(config: NitroConfig, moduleOptions?: SentryNitroOptions): void {
const sourcemapUploadDisabled = moduleOptions?.sourcemaps?.disable === true || moduleOptions?.disable === true;
if (sourcemapUploadDisabled) {
return;
}

if (config.sourcemap === false) {
debug.warn(
'[Sentry] You have explicitly disabled source maps (`sourcemap: false`). Sentry is overriding this to `true` so that errors can be un-minified in Sentry. To disable Sentry source map uploads entirely, use `sourcemaps: { disable: true }` in your Sentry options instead.',
);
}

config.sourcemap = true;
Comment on lines +103 to +109
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the source map is explicitly disabled, we should not mess with this option, but just log the warning.

Only when it's undefined, we'll update it to "hidden" but we should never set it to true to avoid unwanted security problems.

The rules about how we handle source maps are e.g. described here:

/* There are multiple ways to set up source maps (https://github.com/getsentry/sentry-javascript/issues/13993 and https://github.com/getsentry/sentry-javascript/pull/15859)
1. User explicitly disabled source maps
- keep this setting (emit a warning that errors won't be unminified in Sentry)
- We will not upload anything
2. users enabled source map generation (true, hidden, inline).
- keep this setting (don't do anything - like deletion - besides uploading)
3. users did not set source maps generation
- we enable 'hidden' source maps generation
- configure `filesToDeleteAfterUpload` to delete all .map files (we emit a log about this)
Users only have to explicitly enable client source maps. Sentry only overwrites the base Nuxt source map settings as they propagate.
*/


// 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.
// FIXME: Not sure about this one, it works either way?
Comment on lines +113 to +114
config.experimental = config.experimental || {};
config.experimental.sourcemapMinify = false;

if (moduleOptions?.debug) {
debug.log('[Sentry] Enabled source map generation and configured build settings for Sentry source map uploads.');
}
}
Loading
Loading