Skip to content
Merged
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
18 changes: 18 additions & 0 deletions packages/nextjs/src/config/buildLogger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type BuildLogger = Pick<Console, 'debug' | 'error' | 'log' | 'warn'>;

const noop = (): void => {
// noop
};

const SILENT_BUILD_LOGGER: BuildLogger = { debug: noop, error: noop, log: noop, warn: noop };

/**
* Returns the logger for the SDK's own build-time output, honoring the `silent` build option.
*
* The bundler plugin gates its own logs on `silent` internally, so this only covers messages the
* Next.js SDK prints itself.
*/
export function getBuildLogger(silent: boolean | undefined): BuildLogger {
// eslint-disable-next-line no-console
return silent ? SILENT_BUILD_LOGGER : console;
}
6 changes: 4 additions & 2 deletions packages/nextjs/src/config/getBuildPluginOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Options as SentryBuildPluginOptions } from '@sentry/bundler-plugins/core';
import * as fs from 'fs';
import * as path from 'path';
import { getBuildLogger } from './buildLogger';
import type { SentryBuildOptions } from './types';

const LOGGER_PREFIXES = {
Expand Down Expand Up @@ -277,6 +278,8 @@ export function getBuildPluginOptions({
buildTool: BuildTool;
useRunAfterProductionCompileHook?: boolean; // Whether the user has opted into using the experimental hook
}): SentryBuildPluginOptions {
const logger = getBuildLogger(sentryBuildOptions.silent);

// We need to convert paths to posix because Glob patterns use `\` to escape
// glob characters. This clashes with Windows path separators.
// See: https://www.npmjs.com/package/glob
Expand All @@ -298,8 +301,7 @@ export function getBuildPluginOptions({
const userFilesToDeleteAfterUpload = sentryBuildOptions.sourcemaps?.filesToDeleteAfterUpload;

if (sentryBuildOptions.debug && userFilesToDeleteAfterUpload !== undefined) {
// eslint-disable-next-line no-console
console.debug(
logger.debug(
'[@sentry/nextjs] Skipping auto-deletion of source maps as user has provided filesToDeleteAfterUpload:',
userFilesToDeleteAfterUpload,
);
Expand Down
34 changes: 19 additions & 15 deletions packages/nextjs/src/config/handleRunAfterProductionCompile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { createSentryBuildPluginManager as createSentryBuildPluginManagerTy
import { loadModule } from '@sentry/core';
import * as fs from 'fs';
import * as path from 'path';
import { getBuildLogger } from './buildLogger';
import { getBuildPluginOptions } from './getBuildPluginOptions';
import type { SentryBuildOptions } from './types';

Expand All @@ -25,9 +26,10 @@ export async function handleRunAfterProductionCompile(
},
sentryBuildOptions: SentryBuildOptions,
): Promise<void> {
const logger = getBuildLogger(sentryBuildOptions.silent);

if (sentryBuildOptions.debug) {
// eslint-disable-next-line no-console
console.debug('[@sentry/nextjs] Running runAfterProductionCompile logic.');
logger.debug('[@sentry/nextjs] Running runAfterProductionCompile logic.');
}

const { createSentryBuildPluginManager } =
Expand All @@ -37,10 +39,7 @@ export async function handleRunAfterProductionCompile(
) ?? {};

if (!createSentryBuildPluginManager) {
// eslint-disable-next-line no-console
console.warn(
'[@sentry/nextjs] Could not load build manager package. Will not run runAfterProductionCompile logic.',
);
logger.warn('[@sentry/nextjs] Could not load build manager package. Will not run runAfterProductionCompile logic.');
return;
}

Expand Down Expand Up @@ -80,7 +79,11 @@ export async function handleRunAfterProductionCompile(
!sentryBuildOptions.sourcemaps?.assets &&
options.sourcemaps?.disable !== true
) {
await warnAboutUncoveredSourcemaps(path.join(distDir, 'static'), options.sourcemaps?.assets);
await warnAboutUncoveredSourcemaps(
path.join(distDir, 'static'),
options.sourcemaps?.assets,
sentryBuildOptions.silent,
);
}

await sentryBuildPluginManager.deleteArtifacts();
Expand All @@ -93,12 +96,11 @@ export async function handleRunAfterProductionCompile(
// When SRI is enabled, we must skip this step because Next.js computes integrity
// hashes during the build — modifying files afterward invalidates those hashes.
if (deleteSourcemapsAfterUpload && buildTool === 'turbopack' && !sriEnabled) {
await stripSourceMappingURLComments(path.join(distDir, 'static'), sentryBuildOptions.debug);
await stripSourceMappingURLComments(path.join(distDir, 'static'), sentryBuildOptions);
}

if (deleteSourcemapsAfterUpload && buildTool === 'turbopack' && sriEnabled && sentryBuildOptions.debug) {
// eslint-disable-next-line no-console
console.debug(
logger.debug(
'[@sentry/nextjs] Skipping sourceMappingURL comment stripping because Subresource Integrity (SRI) is enabled.',
);
}
Expand All @@ -107,6 +109,7 @@ export async function handleRunAfterProductionCompile(
async function warnAboutUncoveredSourcemaps(
staticDir: string,
uploadAssets: string | string[] | undefined,
silent: boolean | undefined,
): Promise<void> {
let entries: string[];
try {
Expand All @@ -126,8 +129,7 @@ async function warnAboutUncoveredSourcemaps(
.filter(mapPath => !assetPaths.some(assetPath => mapPath === assetPath || mapPath.startsWith(`${assetPath}/`)));

if (uncovered.length > 0) {
// eslint-disable-next-line no-console
console.warn(
getBuildLogger(silent).warn(
`[@sentry/nextjs] Found ${uncovered.length} source map file(s) under "${staticDir}" (e.g. "${
uncovered[0]
}") that are not covered by the source map upload patterns and will be deleted without having been uploaded to Sentry. Stack traces for the affected files will not be symbolicated. Set the \`sourcemaps.assets\` option in \`withSentryConfig\` to cover these files.`,
Expand All @@ -142,7 +144,10 @@ const CSS_SOURCEMAPPING_URL_COMMENT_REGEX = /\n?\/\*[#@] sourceMappingURL=[^\n]+
* Strips sourceMappingURL comments from all JS/MJS/CJS/CSS files in the given directory.
* This prevents browsers from requesting deleted .map files.
*/
export async function stripSourceMappingURLComments(staticDir: string, debug?: boolean): Promise<void> {
export async function stripSourceMappingURLComments(
staticDir: string,
{ debug, silent }: Pick<SentryBuildOptions, 'debug' | 'silent'> = {},
): Promise<void> {
let entries: string[];
try {
entries = await fs.promises.readdir(staticDir, { recursive: true }).then(e => e.map(f => String(f)));
Expand Down Expand Up @@ -179,8 +184,7 @@ export async function stripSourceMappingURLComments(staticDir: string, debug?: b
const strippedCount = results.filter(Boolean).length;

if (debug && strippedCount > 0) {
// eslint-disable-next-line no-console
console.debug(
getBuildLogger(silent).debug(
`[@sentry/nextjs] Stripped sourceMappingURL comments from ${String(strippedCount)} file(s) to prevent requests for deleted source maps.`,
);
}
Expand Down
19 changes: 14 additions & 5 deletions packages/nextjs/src/config/loaders/wrappingLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ const serverComponentWrapperTemplateCode = fs.readFileSync(serverComponentWrappe
const routeHandlerWrapperTemplatePath = path.resolve(__dirname, '..', 'templates', 'routeHandlerWrapperTemplate.js');
const routeHandlerWrapperTemplateCode = fs.readFileSync(routeHandlerWrapperTemplatePath, { encoding: 'utf8' });

// NOTE: This file must not import anything from outside `src/config/loaders`. The loaders are their
// own rollup entry point built with `preserveModules`, so emitted paths are relative to the module
// graph's common root - an outside import moves that root up and nests every loader one directory
// deeper, breaking the `path.resolve(__dirname, 'loaders', ...)` lookups in `webpack.ts`. That is why
// `silent` is checked inline here rather than via the shared `getBuildLogger` helper.
export type WrappingLoaderOptions = {
pagesDir: string | undefined;
appDir: string | undefined;
Expand All @@ -44,6 +49,7 @@ export type WrappingLoaderOptions = {
vercelCronsConfig?: VercelCronsConfig;
nextjsRequestAsyncStorageModulePath?: string;
isDev?: boolean;
silent?: boolean;
};

/**
Expand All @@ -68,6 +74,7 @@ export default function wrappingLoader(
vercelCronsConfig,
nextjsRequestAsyncStorageModulePath,
isDev,
silent,
} = 'getOptions' in this ? this.getOptions() : this.query;

this.async();
Expand Down Expand Up @@ -163,7 +170,7 @@ export default function wrappingLoader(
nextjsRequestAsyncStorageModulePath,
);
} else {
if (!showedMissingAsyncStorageModuleWarning) {
if (!showedMissingAsyncStorageModuleWarning && !silent) {
// eslint-disable-next-line no-console
console.warn(
"[@sentry/nextjs] The Sentry SDK could not access the 'RequestAsyncStorage' module. Certain features may not work. There is nothing you can do to fix this yourself, but future SDK updates may resolve this.",
Expand Down Expand Up @@ -227,10 +234,12 @@ export default function wrappingLoader(
this.callback(null, wrappedCode, wrappedCodeSourceMap);
})
.catch(err => {
// eslint-disable-next-line no-console
console.warn(
`[@sentry/nextjs] Could not instrument ${this.resourcePath}. An error occurred while auto-wrapping:\n${err}`,
);
if (!silent) {
// eslint-disable-next-line no-console
console.warn(
`[@sentry/nextjs] Could not instrument ${this.resourcePath}. An error occurred while auto-wrapping:\n${err}`,
);
}
this.callback(null, userCode, userModuleSourceMap);
});
}
Expand Down
12 changes: 9 additions & 3 deletions packages/nextjs/src/config/manifest/createRouteManifest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import { getBuildLogger } from '../buildLogger';
import type { RouteInfo, RouteManifest } from './types';

/**
Expand All @@ -25,6 +26,10 @@ export type CreateRouteManifestOptions = {
* Pass an empty array to disable optional prefix matching entirely.
*/
localeParamNames?: string[];
/**
* Suppresses the SDK's own build-time logs.
*/
silent?: boolean;
};

let manifestCache: RouteManifest | null = null;
Expand Down Expand Up @@ -149,6 +154,7 @@ function scanAppDirectory(
basePath: string = '',
includeRouteGroups: boolean = false,
localeParamNames: string[] = DEFAULT_LOCALE_PARAM_NAMES,
silent?: boolean,
): RouteManifest {
const dynamicRoutes: RouteInfo[] = [];
const staticRoutes: RouteInfo[] = [];
Expand Down Expand Up @@ -207,16 +213,15 @@ function scanAppDirectory(
}

const newBasePath = routeSegment ? `${basePath}/${routeSegment}` : basePath;
const subRoutes = scanAppDirectory(fullPath, newBasePath, includeRouteGroups, localeParamNames);
const subRoutes = scanAppDirectory(fullPath, newBasePath, includeRouteGroups, localeParamNames, silent);

dynamicRoutes.push(...subRoutes.dynamicRoutes);
staticRoutes.push(...subRoutes.staticRoutes);
isrRoutes.push(...subRoutes.isrRoutes);
}
}
} catch (error) {
// eslint-disable-next-line no-console
console.warn('Error building route manifest:', error);
getBuildLogger(silent).warn('[@sentry/nextjs] Error building route manifest:', error);
}

return { dynamicRoutes, staticRoutes, isrRoutes };
Expand Down Expand Up @@ -267,6 +272,7 @@ export function createRouteManifest(options?: CreateRouteManifestOptions): Route
options?.basePath,
options?.includeRouteGroups,
localeParamNames,
options?.silent,
);

const manifest: RouteManifest = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
serializeInstrumentations,
} from '@sentry/server-utils/orchestrion/webpack';
import type { VercelCronsConfig } from '../../common/types';
import { getBuildLogger } from '../buildLogger';
import type { RouteManifest } from '../manifest/types';
import type {
JSONValue,
Expand Down Expand Up @@ -124,8 +125,7 @@ export function constructTurbopackConfig({
} else {
// Without this warning the option silently no-ops, which is indistinguishable from
// annotation being broken.
// eslint-disable-next-line no-console
console.warn(
getBuildLogger(userSentryOptions?.silent).warn(
`[@sentry/nextjs] \`reactComponentAnnotation\` is enabled but React component annotation requires Next.js 16+ on Turbopack builds${
nextJsVersion ? ` (detected ${nextJsVersion})` : ''
}. Your components will not be annotated.`,
Expand Down
1 change: 0 additions & 1 deletion packages/nextjs/src/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,6 @@ export type SentryBuildWebpackOptions = {
reactComponentAnnotation?: ReactComponentAnnotationOptions; // TODO(v12): remove this option
};

// TODO: `silent` is only forwarded to the bundler plugin - the SDK's own build-time logging ignores it.
/**
* Build-time options for the Sentry Next.js SDK, passed as the second argument to `withSentryConfig`.
*
Expand Down
Loading
Loading