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
5 changes: 3 additions & 2 deletions packages/nitro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@
"nitro": ">=3.0.0-0 <4.0.0 || 3.0.260311-beta || 3.0.260415-beta"
},
"dependencies": {
"@sentry/bundler-plugin-core": "^5.2.0",
"@sentry/core": "10.49.0",
"@sentry/node": "10.49.0",
"@sentry/opentelemetry": "10.49.0"
},
"devDependencies": {
"h3": "^2.0.1-rc.13",
"nitro": "^3.0.260415-beta"
"nitro": "^3.0.260415-beta",
"h3": "^2.0.1-rc.13"
},
"scripts": {
"build": "run-p build:transpile build:types",
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/, /^h3/, /^srvx/, /^@sentry\/opentelemetry/],
external: [/^nitro/, /^h3/, /^srvx/, /^@sentry\/opentelemetry/, '@sentry/bundler-plugin-core'],
},
}),
),
Expand Down
19 changes: 9 additions & 10 deletions packages/nitro/src/config.ts
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;
}
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, sentryEnabledSourcemaps?: boolean): NitroModule {
return {
name: 'sentry',
setup: nitro => {
instrumentServer(nitro);
setupSourceMaps(nitro, sentryOptions, sentryEnabledSourcemaps);
},
};
}
158 changes: 158 additions & 0 deletions packages/nitro/src/sourceMaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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 (nitro.options.dev) {
return;
}

// Nitro spawns a nested Nitro instance for prerendering with the user's `modules` re-installed.
// Uploading here would double-upload source maps and create a duplicate release.
if (nitro.options.preset === 'nitro-prerender') {
return;
}

// Respect user's explicit disable
if (options?.sourcemaps?.disable === true) {
return;
}
Comment thread
sentry[bot] marked this conversation as resolved.

nitro.hooks.hook('compiled', async (_nitro: Nitro) => {
await handleSourceMapUpload(_nitro, options, sentryEnabledSourcemaps);
});
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
sentry[bot] marked this conversation as resolved.
Comment on lines +27 to +29
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: The compiled hook in setupSourceMaps is registered even when config.sourcemap is false, causing source map processing to run unnecessarily on builds without sourcemaps.
Severity: MEDIUM

Suggested Fix

Add an early return in setupSourceMaps to prevent the hook from being registered when sourcemaps are disabled. This can be done by checking the sentryEnabledSourcemaps parameter at the beginning of the function and returning if it's false.

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#L27-L29

Potential issue: When a user sets `config.sourcemap = false` in their Nitro
configuration, the `setupSourceMaps` function still registers a `compiled` hook. As a
result, when the build completes, `handleSourceMapUpload` is executed. This function
then calls `injectDebugIds` on a build output directory that contains no sourcemap
files, because their generation was disabled. This can lead to errors, unnecessary
processing, or confusing log output. The function lacks an early exit condition to check
if sourcemaps are disabled.

}

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

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();
}
Comment thread
sentry[bot] marked this conversation as resolved.
Comment thread
logaretm marked this conversation as resolved.
Comment thread
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(/^(\.\.\/)+/, './');
}

/**
* Builds the plugin options for `createSentryBuildPluginManager` from the Sentry Nitro options.
*
* Only exported for testing purposes.
*/
export function getPluginOptions(
options?: SentryNitroOptions,
sentryEnabledSourcemaps?: boolean,
): BundlerPluginOptions {
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 ?? (sentryEnabledSourcemaps ? ['**/*.map'] : undefined),
rewriteSources: options?.sourcemaps?.rewriteSources ?? ((source: string) => normalizePath(source)),
},
release: options?.release,
bundleSizeOptimizations: options?.bundleSizeOptimizations,
_metaOptions: {
telemetry: {
metaFramework: 'nitro',
},
},
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
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;
Comment thread
cursor[bot] marked this conversation as resolved.
sentryEnabledSourcemaps = true;
if (moduleOptions?.debug) {
// eslint-disable-next-line no-console
console.log(
Comment thread
sentry[bot] marked this conversation as resolved.
'[@sentry/nitro] Enabled source map generation for Sentry. Source map files will be deleted after upload.',
);
}
}
Comment thread
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 };
}
Loading
Loading