diff --git a/MIGRATION.md b/MIGRATION.md index 533170fb8b77..880c38f99275 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -546,10 +546,43 @@ The following previously deprecated API has been removed from the `@sentry/nextj - `IS_BUILD` - `isBuild` +#### Merging of the Sentry Webpack Plugin options and SDK Build options + +With version 8 of the Sentry Next.js SDK, `withSentryConfig` will no longer accept 3 arguments. The second argument +(holding options for the Sentry Webpack plugin) and the third argument (holding options for SDK build-time +configuration) should now be passed as one: + +```ts +// OLD +const nextConfig = { + // Your Next.js options... +}; + +module.exports = withSentryConfig( + nextConfig, + { + // Your Sentry Webpack Plugin Options... + }, + { + // Your Sentry SDK options... + }, +); + +// NEW +const nextConfig = { + // Your Next.js options... +}; + +module.exports = withSentryConfig(nextConfig, { + // Your Sentry Webpack Plugin Options... + // AND your Sentry SDK options... +}); +``` + #### Removal of the `sentry` property in your Next.js options (next.config.js) With version 8 of the Sentry Next.js SDK, the SDK will no longer support passing Next.js options with a `sentry` -property to `withSentryConfig`. Please use the third argument of `withSentryConfig` to configure the SDK instead: +property to `withSentryConfig`. Please use the second argument of `withSentryConfig` to configure the SDK instead: ```ts // v7 @@ -572,21 +605,25 @@ const nextConfig = { // Your Next.js options... }; -module.exports = withSentryConfig( - nextConfig, - { - // Your Sentry Webpack Plugin Options... - }, - { - // Your Sentry SDK options... - }, -); +module.exports = withSentryConfig(nextConfig, { + // Your Sentry Webpack Plugin Options... + // AND your Sentry SDK options... +}); ``` The reason for this change is to have one consistent way of defining the SDK options. We hope that this change will reduce confusion when setting up the SDK, with the upside that the explicit option is properly typed and will therefore have code completion. +#### Updated the `@sentry/webpack-plugin` dependency to version 2 + +We bumped the internal usage of `@sentry/webpack-plugin` to a new major version. This comes with multiple upsides like a +simpler configuration interface and the use of new state of the art Debug ID technology. Debug IDs will simplify the +setup for source maps in Sentry and will not require you to match stack frame paths to uploaded artifacts anymore. + +To see the new options, check out the docs at https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/, +or look at the TypeScript type definitions of `withSentryConfig`. + ### Astro SDK #### Removal of `trackHeaders` option for Astro middleware diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 2bf624f7374b..5776422ce5e8 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -42,7 +42,7 @@ "@sentry/types": "8.0.0-alpha.2", "@sentry/utils": "8.0.0-alpha.2", "@sentry/vercel-edge": "8.0.0-alpha.2", - "@sentry/webpack-plugin": "1.21.0", + "@sentry/webpack-plugin": "2.14.3", "chalk": "3.0.0", "resolve": "1.22.8", "rollup": "2.78.0", diff --git a/packages/nextjs/src/config/index.ts b/packages/nextjs/src/config/index.ts index f537125a75f3..c4032ee44d46 100644 --- a/packages/nextjs/src/config/index.ts +++ b/packages/nextjs/src/config/index.ts @@ -1,2 +1 @@ -export type { SentryWebpackPluginOptions } from './types'; export { withSentryConfig } from './withSentryConfig'; diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index 751969f17529..75d4c87ac963 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -1,10 +1,7 @@ import type { GLOBAL_OBJ } from '@sentry/utils'; -import type { SentryCliPluginOptions } from '@sentry/webpack-plugin'; +import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; import type { DefinePlugin, WebpackPluginInstance } from 'webpack'; -export type SentryWebpackPluginOptions = SentryCliPluginOptions; -export type SentryWebpackPlugin = WebpackPluginInstance & { options: SentryWebpackPluginOptions }; - // Export this from here because importing something from Webpack (the library) in `webpack.ts` confuses the heck out of // madge, which we use for circular dependency checking. We've manually excluded this file from the check (which is // safe, since it only includes types), so we can import it here without causing madge to fail. See @@ -48,18 +45,284 @@ export type NextConfigObject = { >; }; -export type SentryBuildtimeOptions = { +export type SentryBuildOptions = { + /** + * The slug of the Sentry organization associated with the app. + * + * This value can also be specified via the `SENTRY_ORG` environment variable. + */ + org?: string; + + /** + * The slug of the Sentry project associated with the app. + * + * This value can also be specified via the `SENTRY_PROJECT` environment variable. + */ + project?: string; + + /** + * The authentication token to use for all communication with Sentry. + * Can be obtained from https://sentry.io/orgredirect/organizations/:orgslug/settings/auth-tokens/. + * + * This value can also be specified via the `SENTRY_AUTH_TOKEN` environment variable. + */ + authToken?: string; + + /** + * The base URL of your Sentry instance. Use this if you are using a self-hosted + * or Sentry instance other than sentry.io. + * + * This value can also be set via the `SENTRY_URL` environment variable. + * + * Defaults to https://sentry.io/, which is the correct value for SaaS customers. + */ + sentryUrl?: string; + + /** + * Headers added to every outgoing network request. + */ + headers?: Record; + + /** + * If set to true, internal plugin errors and performance data will be sent to Sentry. + * + * At Sentry we like to use Sentry ourselves to deliver faster and more stable products. + * We're very careful of what we're sending. We won't collect anything other than error + * and high-level performance data. We will never collect your code or any details of the + * projects in which you're using this plugin. + * + * Defaults to `true`. + */ + telemetry?: boolean; + + /** + * Suppresses all Sentry SDK build logs. + * + * Defaults to `false`. + */ + // TODO: Actually implement this for the non-plugin code. + silent?: boolean; + + /** + * Prints additional debug information about the SDK and uploading source maps when building the application. + * + * Defaults to `false`. + */ + // TODO: Actually implement this for the non-plugin code. + debug?: boolean; + /** - * Override the SDK's default decision about whether or not to enable to the Sentry webpack plugin for server files. - * Note that `false` forces the plugin to be enabled, even in situations where it's not recommended. + * Options for source maps uploading. */ - disableServerWebpackPlugin?: boolean; + sourcemaps?: { + /** + * Disable any functionality related to source maps upload. + */ + disable?: boolean; + + /** + * A glob or an array of globs that specifies the build artifacts that should be uploaded to Sentry. + * + * If this option is not specified, the plugin will try to upload all JavaScript files and source map files that are created during build. + * + * The globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob) + * + * Use the `debug` option to print information about which files end up being uploaded. + */ + assets?: string | string[]; + + /** + * A glob or an array of globs that specifies which build artifacts should not be uploaded to Sentry. + * + * Default: `[]` + * + * The globbing patterns follow the implementation of the `glob` package. (https://www.npmjs.com/package/glob) + * + * Use the `debug` option to print information about which files end up being uploaded. + */ + ignore?: string | string[]; + + /** + * Toggle whether generated source maps within your Next.js build folder should be automatically deleted after being uploaded to Sentry. + * + * Defaults to `false`. + */ + // TODO: Add this option + // deleteSourcemapsAfterUpload?: boolean; + }; /** - * Override the SDK's default decision about whether or not to enable to the Sentry webpack plugin for client files. - * Note that `false` forces the plugin to be enabled, even in situations where it's not recommended. + * Options related to managing the Sentry releases for a build. + * + * More info: https://docs.sentry.io/product/releases/ */ - disableClientWebpackPlugin?: boolean; + release?: { + /** + * Unique identifier for the release you want to create. + * + * This value can also be specified via the `SENTRY_RELEASE` environment variable. + * + * Defaults to automatically detecting a value for your environment. + * This includes values for Cordova, Heroku, AWS CodeBuild, CircleCI, Xcode, and Gradle, and otherwise uses the git `HEAD`'s commit SHA. + * (the latter requires access to git CLI and for the root directory to be a valid repository) + * + * If you didn't provide a value and the plugin can't automatically detect one, no release will be created. + */ + name?: string; + + /** + * Whether the plugin should create a release on Sentry during the build. + * Note that a release may still appear in Sentry even if this is value is `false` because any Sentry event that has a release value attached will automatically create a release. + * (for example via the `inject` option) + * + * Defaults to `true`. + */ + create?: boolean; + + /** + * Whether the Sentry release should be automatically finalized (meaning an end timestamp is added) after the build ends. + * + * Defaults to `true`. + */ + finalize?: boolean; + + /** + * Unique identifier for the distribution, used to further segment your release. + * Usually your build number. + */ + dist?: string; + + /** + * Version control system remote name. + * + * This value can also be specified via the `SENTRY_VSC_REMOTE` environment variable. + * + * Defaults to 'origin'. + */ + vcsRemote?: string; + + /** + * Associates the release with its commits in Sentry. + */ + setCommits?: ( + | { + /** + * Automatically sets `commit` and `previousCommit`. Sets `commit` to `HEAD` + * and `previousCommit` as described in the option's documentation. + * + * If you set this to `true`, manually specified `commit` and `previousCommit` + * options will be overridden. It is best to not specify them at all if you + * set this option to `true`. + */ + auto: true; + + repo?: undefined; + commit?: undefined; + } + | { + auto?: false | undefined; + + /** + * The full repo name as defined in Sentry. + * + * Required if the `auto` option is not set to `true`. + */ + repo: string; + + /** + * The current (last) commit in the release. + * + * Required if the `auto` option is not set to `true`. + */ + commit: string; + } + ) & { + /** + * The commit before the beginning of this release (in other words, + * the last commit of the previous release). + * + * Defaults to the last commit of the previous release in Sentry. + * + * If there was no previous release, the last 10 commits will be used. + */ + previousCommit?: string; + + /** + * If the flag is to `true` and the previous release commit was not found + * in the repository, the plugin creates a release with the default commits + * count instead of failing the command. + * + * Defaults to `false`. + */ + ignoreMissing?: boolean; + + /** + * If this flag is set, the setCommits step will not fail and just exit + * silently if no new commits for a given release have been found. + * + * Defaults to `false`. + */ + ignoreEmpty?: boolean; + }; + + /** + * Adds deployment information to the release in Sentry. + */ + deploy?: { + /** + * Environment for this release. Values that make sense here would + * be `production` or `staging`. + */ + env: string; + + /** + * Deployment start time in Unix timestamp (in seconds) or ISO 8601 format. + */ + started?: number | string; + + /** + * Deployment finish time in Unix timestamp (in seconds) or ISO 8601 format. + */ + finished?: number | string; + + /** + * Deployment duration (in seconds). Can be used instead of started and finished. + */ + time?: number; + + /** + * Human readable name for the deployment. + */ + name?: string; + + /** + * URL that points to the deployment. + */ + url?: string; + }; + }; + + /** + * Options related to react component name annotations. + * Disabled by default, unless a value is set for this option. + * When enabled, your app's DOM will automatically be annotated during build-time with their respective component names. + * This will unlock the capability to search for Replays in Sentry by component name, as well as see component names in breadcrumbs and performance monitoring. + * Please note that this feature is not currently supported by the esbuild bundler plugins, and will only annotate React components + */ + reactComponentAnnotation?: { + /** + * Whether the component name annotate plugin should be enabled or not. + */ + enabled?: boolean; + }; + + /** + * Options to be passed directly to the Sentry Webpack Plugin (`@sentry/webpack-plugin`) that ships with the Sentry Next.js SDK. + * You can use this option to override any options the SDK passes to the webpack plugin. + * + * Please note that this option is unstable and may change in a breaking way in any release. + */ + unstable_sentryWebpackPluginOptions?: SentryWebpackPluginOptions; /** * Use `hidden-source-map` for webpack `devtool` option, which strips the `sourceMappingURL` from the bottom of built @@ -74,13 +337,16 @@ export type SentryBuildtimeOptions = { transpileClientSDK?: boolean; /** - * Instructs the Sentry webpack plugin to upload source files from `/static/chunks` rather than - * `/static/chunks/pages`. Usually files outside of `pages/` only contain third-party code, but in cases - * where they contain user code, restricting the webpack plugin's upload breaks sourcemaps for those - * user-code-containing files, because it keeps them from being uploaded. Defaults to `false`. + * Include Next.js-internal code and code from dependencies when uploading source maps. + * + * Note: Enabling this option can lead to longer build times. + * Disabling this option will leave you without readable stacktraces for dependencies and Next.js-internal code. + * + * Defaults to `false`. */ - // We don't want to widen the scope if we don't have to, because we're guaranteed to end up uploading too many files, - // which is why this defaults to`false`. + // Enabling this option may upload a lot of source maps and since the sourcemap upload endpoint in Sentry is super + // slow we don't enable it by default so that we don't opaquely increase build times for users. + // TODO: Add an alias to this function called "uploadSourceMapsForDependencies" widenClientFileUpload?: boolean; /** @@ -143,7 +409,7 @@ export type NextConfigFunction = ( export type WebpackConfigFunction = (config: WebpackConfigObject, options: BuildContext) => WebpackConfigObject; export type WebpackConfigObject = { devtool?: string; - plugins?: Array; + plugins?: Array; entry: WebpackEntryProperty; output: { filename: string; path: string }; target: string; @@ -176,8 +442,8 @@ export type BuildContext = { DefinePlugin: typeof DefinePlugin; }; // eslint-disable-next-line @typescript-eslint/no-explicit-any - defaultLoaders: any; - totalPages: number; + defaultLoaders: any; // needed for type tests (test:types) + totalPages: number; // needed for type tests (test:types) nextRuntime?: 'nodejs' | 'edge'; // Added in Next.js 12+ }; diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 5a1c9ede21b4..0a8484c9afc8 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -1,13 +1,12 @@ -import * as fs from 'fs'; -import * as path from 'path'; /* eslint-disable complexity */ /* eslint-disable max-lines */ + +import * as fs from 'fs'; +import * as path from 'path'; import { getSentryRelease } from '@sentry/node-experimental'; -import { arrayify, dropUndefinedKeys, escapeStringForRegex, loadModule, logger } from '@sentry/utils'; -import type SentryCliPlugin from '@sentry/webpack-plugin'; +import { arrayify, escapeStringForRegex, loadModule, logger } from '@sentry/utils'; import * as chalk from 'chalk'; import { sync as resolveSync } from 'resolve'; -import type { Compiler } from 'webpack'; import { DEBUG_BUILD } from '../common/debug-build'; import type { VercelCronsConfig } from '../common/types'; @@ -17,15 +16,14 @@ import type { BuildContext, EntryPropertyObject, NextConfigObject, - SentryBuildtimeOptions, - SentryWebpackPluginOptions, + SentryBuildOptions, WebpackConfigFunction, WebpackConfigObject, WebpackConfigObjectWithModuleRules, WebpackEntryProperty, WebpackModuleRule, - WebpackPluginInstance, } from './types'; +import { getWebpackPluginOptions } from './webpackPluginOptions'; const RUNTIME_TO_SDK_ENTRYPOINT_MAP = { client: './client', @@ -35,17 +33,8 @@ const RUNTIME_TO_SDK_ENTRYPOINT_MAP = { // Next.js runs webpack 3 times, once for the client, the server, and for edge. Because we don't want to print certain // warnings 3 times, we keep track of them here. -let showedMissingAuthTokenErrorMsg = false; -let showedMissingOrgSlugErrorMsg = false; -let showedMissingProjectSlugErrorMsg = false; -let showedHiddenSourceMapsWarningMsg = false; -let showedMissingCliBinaryWarningMsg = false; let showedMissingGlobalErrorWarningMsg = false; -// TODO: merge default SentryWebpackPlugin ignore with their SentryWebpackPlugin ignore or ignoreFile -// TODO: merge default SentryWebpackPlugin include with their SentryWebpackPlugin include -// TODO: drop merged keys from override check? `includeDefaults` option? - /** * Construct the function which will be used as the nextjs config's `webpack` value. * @@ -60,8 +49,7 @@ let showedMissingGlobalErrorWarningMsg = false; */ export function constructWebpackConfigFunction( userNextConfig: NextConfigObject = {}, - userSentryWebpackPluginOptions: Partial = {}, - userSentryOptions: SentryBuildtimeOptions = {}, + userSentryOptions: SentryBuildOptions = {}, ): WebpackConfigFunction { // Will be called by nextjs and passed its default webpack configuration and context data about the build (whether // we're building server or client, whether we're in dev, what version of webpack we're using, etc). Note that @@ -86,7 +74,7 @@ export function constructWebpackConfigFunction( const newConfig = setUpModuleRules(rawNewConfig); // Add a loader which will inject code that sets global values - addValueInjectionLoader(newConfig, userNextConfig, userSentryOptions, buildContext, userSentryWebpackPluginOptions); + addValueInjectionLoader(newConfig, userNextConfig, userSentryOptions, buildContext); newConfig.module.rules.push({ test: /node_modules[/\\]@sentry[/\\]nextjs/, @@ -347,6 +335,7 @@ export function constructWebpackConfigFunction( } } + // TODO(v8): Remove this logic since we are deprecating es5. // The SDK uses syntax (ES6 and ES6+ features like object spread) which isn't supported by older browsers. For users // who want to support such browsers, `transpileClientSDK` allows them to force the SDK code to go through the same // transpilation that their code goes through. We don't turn this on by default because it increases bundle size @@ -386,37 +375,28 @@ export function constructWebpackConfigFunction( const origEntryProperty = newConfig.entry; newConfig.entry = async () => addSentryToEntryProperty(origEntryProperty, buildContext, userSentryOptions); - // Enable the Sentry plugin (which uploads source maps to Sentry when not in dev) by default - if (shouldEnableWebpackPlugin(buildContext, userSentryOptions)) { - // TODO Handle possibility that user is using `SourceMapDevToolPlugin` (see - // https://webpack.js.org/plugins/source-map-dev-tool-plugin/) - - // TODO (v9 or v10, maybe): Remove this - handleSourcemapHidingOptionWarning(userSentryOptions, isServer); - - // Next doesn't let you change `devtool` in dev even if you want to, so don't bother trying - see - // https://github.com/vercel/next.js/blob/master/errors/improper-devtool.md - if (!isDev) { - // TODO (v8): Default `hideSourceMaps` to `true` - - // `hidden-source-map` produces the same sourcemaps as `source-map`, but doesn't include the `sourceMappingURL` - // comment at the bottom. For folks who aren't publicly hosting their sourcemaps, this is helpful because then - // the browser won't look for them and throw errors into the console when it can't find them. Because this is a - // front-end-only problem, and because `sentry-cli` handles sourcemaps more reliably with the comment than - // without, the option to use `hidden-source-map` only applies to the client-side build. - newConfig.devtool = userSentryOptions.hideSourceMaps && !isServer ? 'hidden-source-map' : 'source-map'; - - const SentryWebpackPlugin = loadModule('@sentry/webpack-plugin'); - if (SentryWebpackPlugin) { - newConfig.plugins = newConfig.plugins || []; - newConfig.plugins.push(new SentryCliDownloadPlugin()); - newConfig.plugins.push( - // @ts-expect-error - this exists, the dynamic import just doesn't know about it - new SentryWebpackPlugin( - getWebpackPluginOptions(buildContext, userSentryWebpackPluginOptions, userSentryOptions), - ), - ); + // Next doesn't let you change `devtool` in dev even if you want to, so don't bother trying - see + // https://github.com/vercel/next.js/blob/master/errors/improper-devtool.md + if (!isDev) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { sentryWebpackPlugin } = loadModule('@sentry/webpack-plugin') as any; + if (sentryWebpackPlugin) { + if (!userSentryOptions.sourcemaps?.disable) { + // `hidden-source-map` produces the same sourcemaps as `source-map`, but doesn't include the `sourceMappingURL` + // comment at the bottom. For folks who aren't publicly hosting their sourcemaps, this is helpful because then + // the browser won't look for them and throw errors into the console when it can't find them. Because this is a + // front-end-only problem, and because `sentry-cli` handles sourcemaps more reliably with the comment than + // without, the option to use `hidden-source-map` only applies to the client-side build. + newConfig.devtool = !isServer ? 'hidden-source-map' : 'source-map'; } + + newConfig.plugins = newConfig.plugins || []; + const sentryWebpackPluginInstance = sentryWebpackPlugin( + getWebpackPluginOptions(buildContext, userSentryOptions), + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + sentryWebpackPluginInstance._name = 'sentry-webpack-plugin'; // For tests and debugging. Serves no other purpose. + newConfig.plugins.push(sentryWebpackPluginInstance); } } @@ -511,7 +491,7 @@ function findTranspilationRules(rules: WebpackModuleRule[] | undefined, projectD async function addSentryToEntryProperty( currentEntryProperty: WebpackEntryProperty, buildContext: BuildContext, - userSentryOptions: SentryBuildtimeOptions, + userSentryOptions: SentryBuildOptions, ): Promise { // The `entry` entry in a webpack config can be a string, array of strings, object, or function. By default, nextjs // sets it to an async function which returns the promise of an object of string arrays. Because we don't know whether @@ -673,30 +653,6 @@ function addFilesToExistingEntryPoint( entryProperty[entryPointName] = newEntryPoint; } -/** - * Check the SentryWebpackPlugin options provided by the user against the options we set by default, and warn if any of - * our default options are getting overridden. (Note: If any of our default values is undefined, it won't be included in - * the warning.) - * - * @param defaultOptions Default SentryWebpackPlugin options - * @param userOptions The user's SentryWebpackPlugin options - */ -function checkWebpackPluginOverrides( - defaultOptions: SentryWebpackPluginOptions, - userOptions: Partial, -): void { - // warn if any of the default options for the webpack plugin are getting overridden - const sentryWebpackPluginOptionOverrides = Object.keys(defaultOptions).filter(key => key in userOptions); - if (sentryWebpackPluginOptionOverrides.length > 0) { - DEBUG_BUILD && - logger.warn( - '[Sentry] You are overriding the following automatically-set SentryWebpackPlugin config options:\n' + - `\t${sentryWebpackPluginOptionOverrides.toString()},\n` + - "which has the possibility of breaking source map upload and application. This is only a good idea if you know what you're doing.", - ); - } -} - /** * Determine if this is an entry point into which both `Sentry.init()` code and the release value should be injected * @@ -714,238 +670,6 @@ function shouldAddSentryToEntryPoint(entryPointName: string, runtime: 'node' | ' ); } -/** - * Combine default and user-provided SentryWebpackPlugin options, accounting for whether we're building server files or - * client files. - * - * @param buildContext Nexjs-provided data about the current build - * @param userPluginOptions User-provided SentryWebpackPlugin options - * @returns Final set of combined options - */ -export function getWebpackPluginOptions( - buildContext: BuildContext, - userPluginOptions: Partial, - userSentryOptions: SentryBuildtimeOptions, -): SentryWebpackPluginOptions { - const { buildId, isServer, config, dir: projectDir } = buildContext; - const userNextConfig = config as NextConfigObject; - - const distDirAbsPath = path.resolve(projectDir, userNextConfig.distDir || '.next'); // `.next` is the default directory - - const isServerless = userNextConfig.target === 'experimental-serverless-trace'; - const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties')); - const urlPrefix = '~/_next'; - - const serverInclude = isServerless - ? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }] - : [{ paths: [`${distDirAbsPath}/server/`], urlPrefix: `${urlPrefix}/server` }]; - - const serverIgnore: string[] = []; - - const clientInclude = userSentryOptions.widenClientFileUpload - ? [{ paths: [`${distDirAbsPath}/static/chunks`], urlPrefix: `${urlPrefix}/static/chunks` }] - : [ - { paths: [`${distDirAbsPath}/static/chunks/pages`], urlPrefix: `${urlPrefix}/static/chunks/pages` }, - { paths: [`${distDirAbsPath}/static/chunks/app`], urlPrefix: `${urlPrefix}/static/chunks/app` }, - ]; - - // Widening the upload scope is necessarily going to lead to us uploading files we don't need to (ones which - // don't include any user code). In order to lessen that where we can, exclude the internal nextjs files we know - // will be there. - const clientIgnore = userSentryOptions.widenClientFileUpload - ? ['framework-*', 'framework.*', 'main-*', 'polyfills-*', 'webpack-*'] - : []; - - const defaultPluginOptions = dropUndefinedKeys({ - include: isServer ? serverInclude : clientInclude, - ignore: isServer ? serverIgnore : clientIgnore, - url: process.env.SENTRY_URL, - org: process.env.SENTRY_ORG, - project: process.env.SENTRY_PROJECT, - authToken: process.env.SENTRY_AUTH_TOKEN, - configFile: hasSentryProperties ? 'sentry.properties' : undefined, - stripPrefix: ['webpack://_N_E/', 'webpack://'], - urlPrefix, - entries: [], // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. - release: getSentryRelease(buildId), - }); - - checkWebpackPluginOverrides(defaultPluginOptions, userPluginOptions); - - return { - ...defaultPluginOptions, - ...userPluginOptions, - errorHandler(err, invokeErr, compilation) { - if (err) { - const errorMessagePrefix = `${chalk.red('error')} -`; - - if (err.message.includes('ENOENT')) { - if (!showedMissingCliBinaryWarningMsg) { - // eslint-disable-next-line no-console - console.error( - `\n${errorMessagePrefix} ${chalk.bold( - 'The Sentry binary to upload sourcemaps could not be found.', - )} Source maps will not be uploaded. Please check that post-install scripts are enabled in your package manager when installing your dependencies and please run your build once without any caching to avoid caching issues of dependencies.\n`, - ); - showedMissingCliBinaryWarningMsg = true; - } - return; - } - - // Hardcoded way to check for missing auth token until we have a better way of doing this. - if (err.message.includes('Authentication credentials were not provided.')) { - let msg; - - if (process.env.VERCEL) { - msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( - 'SENTRY_AUTH_TOKEN', - )} environment variable: https://vercel.com/integrations/sentry`; - } else { - msg = - 'You can find information on how to generate a Sentry auth token here: https://docs.sentry.io/api/auth/\n' + - `After generating a Sentry auth token, set it via the ${chalk.bold.cyan( - 'SENTRY_AUTH_TOKEN', - )} environment variable during the build.`; - } - - if (!showedMissingAuthTokenErrorMsg) { - // eslint-disable-next-line no-console - console.error( - `${errorMessagePrefix} ${chalk.bold( - 'No Sentry auth token configured.', - )} Source maps will not be uploaded.\n${msg}\n`, - ); - showedMissingAuthTokenErrorMsg = true; - } - - return; - } - - // Hardcoded way to check for missing org slug until we have a better way of doing this. - if (err.message.includes('An organization slug is required')) { - let msg; - if (process.env.VERCEL) { - msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( - 'SENTRY_ORG', - )} environment variable: https://vercel.com/integrations/sentry`; - } else { - msg = `To fix this, set the ${chalk.bold.cyan( - 'SENTRY_ORG', - )} environment variable to the to your organization slug during the build.`; - } - - if (!showedMissingOrgSlugErrorMsg) { - // eslint-disable-next-line no-console - console.error( - `${errorMessagePrefix} ${chalk.bold( - 'No Sentry organization slug configured.', - )} Source maps will not be uploaded.\n${msg}\n`, - ); - showedMissingOrgSlugErrorMsg = true; - } - - return; - } - - // Hardcoded way to check for missing project slug until we have a better way of doing this. - if (err.message.includes('A project slug is required')) { - let msg; - if (process.env.VERCEL) { - msg = `To fix this, use Sentry's Vercel integration to automatically set the ${chalk.bold.cyan( - 'SENTRY_PROJECT', - )} environment variable: https://vercel.com/integrations/sentry`; - } else { - msg = `To fix this, set the ${chalk.bold.cyan( - 'SENTRY_PROJECT', - )} environment variable to the name of your Sentry project during the build.`; - } - - if (!showedMissingProjectSlugErrorMsg) { - // eslint-disable-next-line no-console - console.error( - `${errorMessagePrefix} ${chalk.bold( - 'No Sentry project slug configured.', - )} Source maps will not be uploaded.\n${msg}\n`, - ); - showedMissingProjectSlugErrorMsg = true; - } - - return; - } - } - - if (userPluginOptions.errorHandler) { - return userPluginOptions.errorHandler(err, invokeErr, compilation); - } - - return invokeErr(); - }, - }; -} - -/** Check various conditions to decide if we should run the plugin */ -function shouldEnableWebpackPlugin(buildContext: BuildContext, userSentryOptions: SentryBuildtimeOptions): boolean { - const { isServer } = buildContext; - const { disableServerWebpackPlugin, disableClientWebpackPlugin } = userSentryOptions; - - if (isServer && disableServerWebpackPlugin !== undefined) { - return !disableServerWebpackPlugin; - } else if (!isServer && disableClientWebpackPlugin !== undefined) { - return !disableClientWebpackPlugin; - } - - return true; -} - -/** Handle warning messages about `hideSourceMaps` option. Can be removed in v9 or v10 (or whenever we consider that - * enough people will have upgraded the SDK that the warning about the default in v8 - currently commented out - is - * overkill). */ -function handleSourcemapHidingOptionWarning(userSentryOptions: SentryBuildtimeOptions, isServer: boolean): void { - // This is nextjs's own logging formatting, vendored since it's not exported. See - // https://github.com/vercel/next.js/blob/c3ceeb03abb1b262032bd96457e224497d3bbcef/packages/next/build/output/log.ts#L3-L11 - // and - // https://github.com/vercel/next.js/blob/de7aa2d6e486c40b8be95a1327639cbed75a8782/packages/next/lib/eslint/runLintCheck.ts#L321-L323. - const codeFormat = (str: string): string => chalk.bold.cyan(str); - - const _warningPrefix_ = `${chalk.yellow('warn')} -`; - const _sentryNextjs_ = codeFormat('@sentry/nextjs'); - const _hideSourceMaps_ = codeFormat('hideSourceMaps'); - const _true_ = codeFormat('true'); - const _false_ = codeFormat('false'); - const _sentry_ = codeFormat('sentry'); - const _nextConfigJS_ = codeFormat('next.config.js'); - - if (isServer && userSentryOptions.hideSourceMaps === undefined && !showedHiddenSourceMapsWarningMsg) { - // eslint-disable-next-line no-console - console.warn( - `\n${_warningPrefix_} In order to be able to deminify errors, ${_sentryNextjs_} creates sourcemaps and uploads ` + - 'them to the Sentry server. Depending on your deployment setup, this means your original code may be visible ' + - `in browser devtools in production. To prevent this, set ${_hideSourceMaps_} to ${_true_} in the ${_sentry_} ` + - `options in your ${_nextConfigJS_}. To disable this warning without changing sourcemap behavior, set ` + - `${_hideSourceMaps_} to ${_false_}. (In ${_sentryNextjs_} version 8.0.0 and beyond, this option will default ` + - `to ${_true_}.) See https://webpack.js.org/configuration/devtool/ and ` + - 'https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map for more ' + - 'information.\n', - ); - showedHiddenSourceMapsWarningMsg = true; - } - - // TODO (v8): Remove the check above in favor of the one below - - // const infoPrefix = `${chalk.cyan('info')} -`; - // - // if (isServer && userSentryOptions.hideSourceMaps === true) { - // // eslint-disable-next-line no-console - // console.log( - // `\n${infoPrefix} Starting in ${_sentryNextjs_} version 8.0.0, ${_hideSourceMaps_} defaults to ${_true_}, and ` + - // `thus can be removed from the ${_sentry_} options in ${_nextConfigJS_}. See ` + - // 'https://webpack.js.org/configuration/devtool/ and ' + - // 'https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#use-hidden-source-map for more ' + - // 'information.\n', - // ); - // } -} - /** * Ensure that `newConfig.module.rules` exists. Modifies the given config in place but also returns it in order to * change its type. @@ -969,9 +693,8 @@ function setUpModuleRules(newConfig: WebpackConfigObject): WebpackConfigObjectWi function addValueInjectionLoader( newConfig: WebpackConfigObjectWithModuleRules, userNextConfig: NextConfigObject, - userSentryOptions: SentryBuildtimeOptions, + userSentryOptions: SentryBuildOptions, buildContext: BuildContext, - sentryWebpackPluginOptions: Partial, ): void { const assetPrefix = userNextConfig.assetPrefix || userNextConfig.basePath || ''; @@ -986,7 +709,7 @@ function addValueInjectionLoader( // Having a release defined in dev-mode spams releases in Sentry so we only set one in non-dev mode SENTRY_RELEASE: buildContext.dev ? undefined - : { id: sentryWebpackPluginOptions.release ?? getSentryRelease(buildContext.buildId) }, + : { id: userSentryOptions.release?.name ?? getSentryRelease(buildContext.buildId) }, __sentryBasePath: buildContext.dev ? userNextConfig.basePath : undefined, }; @@ -1076,54 +799,3 @@ function getRequestAsyncStorageModuleLocation( return undefined; } - -let downloadingCliAttempted = false; - -class SentryCliDownloadPlugin implements WebpackPluginInstance { - public apply(compiler: Compiler): void { - compiler.hooks.beforeRun.tapAsync('SentryCliDownloadPlugin', (compiler, callback) => { - const SentryWebpackPlugin = loadModule('@sentry/webpack-plugin'); - if (!SentryWebpackPlugin) { - // Pretty much an invariant. - return callback(); - } - - // @ts-expect-error - this exists, the dynamic import just doesn't know it - if (SentryWebpackPlugin.cliBinaryExists()) { - return callback(); - } - - if (!downloadingCliAttempted) { - downloadingCliAttempted = true; - // eslint-disable-next-line no-console - logger.info( - `\n${chalk.cyan('info')} - ${chalk.bold( - 'Sentry binary to upload source maps not found.', - )} Package manager post-install scripts are likely disabled or there is a caching issue. Manually downloading instead...`, - ); - - // @ts-expect-error - this exists, the dynamic import just doesn't know it - const cliDownloadPromise: Promise = SentryWebpackPlugin.downloadCliBinary({ - log: () => { - // No logs from directly from CLI - }, - }); - - cliDownloadPromise.then( - () => { - // eslint-disable-next-line no-console - logger.info(`${chalk.cyan('info')} - Sentry binary was successfully downloaded.\n`); - return callback(); - }, - e => { - // eslint-disable-next-line no-console - logger.error(`${chalk.red('error')} - Sentry binary download failed:`, e); - return callback(); - }, - ); - } else { - return callback(); - } - }); - } -} diff --git a/packages/nextjs/src/config/webpackPluginOptions.ts b/packages/nextjs/src/config/webpackPluginOptions.ts new file mode 100644 index 000000000000..8f3c7aab28de --- /dev/null +++ b/packages/nextjs/src/config/webpackPluginOptions.ts @@ -0,0 +1,94 @@ +import * as path from 'path'; +import { getSentryRelease } from '@sentry/node-experimental'; +import type { SentryWebpackPluginOptions } from '@sentry/webpack-plugin'; +import type { BuildContext, NextConfigObject, SentryBuildOptions } from './types'; + +/** + * Combine default and user-provided SentryWebpackPlugin options, accounting for whether we're building server files or + * client files. + */ +export function getWebpackPluginOptions( + buildContext: BuildContext, + sentryBuildOptions: SentryBuildOptions, +): SentryWebpackPluginOptions { + const { buildId, isServer, config: userNextConfig, dir: projectDir } = buildContext; + + const distDirAbsPath = path.join(projectDir, (userNextConfig as NextConfigObject).distDir || '.next'); // `.next` is the default directory + + let sourcemapUploadAssets: string[] = []; + const sourcemapUploadIgnore: string[] = []; + + if (isServer) { + sourcemapUploadAssets.push( + path.join(distDirAbsPath, 'server', '**'), // This is normally where Next.js outputs things + path.join(distDirAbsPath, 'serverless', '**'), // This was the output location for serverless Next.js + ); + } else { + if (sentryBuildOptions.widenClientFileUpload) { + sourcemapUploadAssets.push(path.join(distDirAbsPath, 'static', 'chunks', '**')); + } else { + sourcemapUploadAssets.push( + path.join(distDirAbsPath, 'static', 'chunks', 'pages', '**'), + path.join(distDirAbsPath, 'static', 'chunks', 'app', '**'), + ); + } + + // TODO: We should think about uploading these when `widenClientFileUpload` is `true`. They may be useful in some situations. + sourcemapUploadIgnore.push( + path.join(distDirAbsPath, 'static', 'chunks', 'framework-*'), + path.join(distDirAbsPath, 'static', 'chunks', 'framework.*'), + path.join(distDirAbsPath, 'static', 'chunks', 'main-*'), + path.join(distDirAbsPath, 'static', 'chunks', 'polyfills-*'), + path.join(distDirAbsPath, 'static', 'chunks', 'webpack-*'), + ); + } + + if (sentryBuildOptions.sourcemaps?.disable) { + sourcemapUploadAssets = []; + } + + return { + authToken: sentryBuildOptions.authToken, + headers: sentryBuildOptions.headers, + org: sentryBuildOptions.org, + project: sentryBuildOptions.project, + telemetry: sentryBuildOptions.telemetry, + debug: sentryBuildOptions.debug, + reactComponentAnnotation: { + ...sentryBuildOptions.reactComponentAnnotation, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.reactComponentAnnotation, + }, + silent: sentryBuildOptions.silent, + url: sentryBuildOptions.sentryUrl, + sourcemaps: { + rewriteSources(source) { + if (source.startsWith('webpack://_N_E/')) { + return source.replace('webpack://_N_E/', ''); + } else if (source.startsWith('webpack://')) { + return source.replace('webpack://', ''); + } else { + return source; + } + }, + assets: sentryBuildOptions.sourcemaps?.assets ?? sourcemapUploadAssets, + ignore: sentryBuildOptions.sourcemaps?.ignore ?? sourcemapUploadIgnore, + // TODO: Add this functionality + // filesToDeleteAfterUpload: sentryBuildOptions.sourcemaps?.deleteSourcemapsAfterUpload + // ? path.join(distDirAbsPath, '**', '*.js.map') + // : undefined, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.sourcemaps, + }, + release: { + inject: false, // The webpack plugin's release injection breaks the `app` directory - we inject the release manually with the value injection loader instead. + name: sentryBuildOptions.release?.name ?? getSentryRelease(buildId), + create: sentryBuildOptions.release?.create, + finalize: sentryBuildOptions.release?.finalize, + dist: sentryBuildOptions.release?.dist, + vcsRemote: sentryBuildOptions.release?.vcsRemote, + setCommits: sentryBuildOptions.release?.setCommits, + deploy: sentryBuildOptions.release?.deploy, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions?.release, + }, + ...sentryBuildOptions.unstable_sentryWebpackPluginOptions, + }; +} diff --git a/packages/nextjs/src/config/withSentryConfig.ts b/packages/nextjs/src/config/withSentryConfig.ts index b4cc92eea75d..f9f03b0af2fd 100644 --- a/packages/nextjs/src/config/withSentryConfig.ts +++ b/packages/nextjs/src/config/withSentryConfig.ts @@ -4,8 +4,7 @@ import type { ExportedNextConfig as NextConfig, NextConfigFunction, NextConfigObject, - SentryBuildtimeOptions, - SentryWebpackPluginOptions, + SentryBuildOptions, } from './types'; import { constructWebpackConfigFunction } from './webpack'; @@ -16,13 +15,12 @@ let showedExportModeTunnelWarning = false; * * @param nextConfig A Next.js configuration object, as usually exported in `next.config.js` or `next.config.mjs`. * @param sentryWebpackPluginOptions Options to configure the automatically included Sentry Webpack Plugin for source maps and release management in Sentry. - * @param sentryBuildtimeOptions Additional options to configure instrumentation and + * @param sentryBuildOptions Additional options to configure instrumentation and * @returns The modified config to be exported */ export function withSentryConfig( nextConfig: NextConfig = {}, - sentryWebpackPluginOptions: Partial = {}, - sentryBuildtimeOptions: SentryBuildtimeOptions = {}, + sentryBuildOptions: SentryBuildOptions = {}, ): NextConfigFunction | NextConfigObject { if (typeof nextConfig === 'function') { return function (this: unknown, ...webpackConfigFunctionArgs: unknown[]): ReturnType { @@ -30,14 +28,14 @@ export function withSentryConfig( if (isThenable(maybePromiseNextConfig)) { return maybePromiseNextConfig.then(promiseResultNextConfig => { - return getFinalConfigObject(promiseResultNextConfig, sentryBuildtimeOptions, sentryWebpackPluginOptions); + return getFinalConfigObject(promiseResultNextConfig, sentryBuildOptions); }); } - return getFinalConfigObject(maybePromiseNextConfig, sentryBuildtimeOptions, sentryWebpackPluginOptions); + return getFinalConfigObject(maybePromiseNextConfig, sentryBuildOptions); }; } else { - return getFinalConfigObject(nextConfig, sentryBuildtimeOptions, sentryWebpackPluginOptions); + return getFinalConfigObject(nextConfig, sentryBuildOptions); } } @@ -45,8 +43,7 @@ export function withSentryConfig( // `webpack` property function getFinalConfigObject( incomingUserNextConfigObject: NextConfigObject, - userSentryOptions: SentryBuildtimeOptions, - userSentryWebpackPluginOptions: Partial, + userSentryOptions: SentryBuildOptions, ): NextConfigObject { if ('sentry' in incomingUserNextConfigObject) { // eslint-disable-next-line no-console @@ -74,11 +71,7 @@ function getFinalConfigObject( return { ...incomingUserNextConfigObject, - webpack: constructWebpackConfigFunction( - incomingUserNextConfigObject, - userSentryWebpackPluginOptions, - userSentryOptions, - ), + webpack: constructWebpackConfigFunction(incomingUserNextConfigObject, userSentryOptions), }; } diff --git a/packages/nextjs/test/config/testUtils.ts b/packages/nextjs/test/config/testUtils.ts index 3b4062083f46..1e93e3740152 100644 --- a/packages/nextjs/test/config/testUtils.ts +++ b/packages/nextjs/test/config/testUtils.ts @@ -1,13 +1,9 @@ -import type { default as SentryWebpackPlugin } from '@sentry/webpack-plugin'; -import type { WebpackPluginInstance } from 'webpack'; - import type { BuildContext, EntryPropertyFunction, ExportedNextConfig, NextConfigObject, - SentryBuildtimeOptions, - SentryWebpackPluginOptions, + SentryBuildOptions, WebpackConfigObject, WebpackConfigObjectWithModuleRules, } from '../../src/config/types'; @@ -27,11 +23,10 @@ import { defaultRuntimePhase, defaultsObject } from './fixtures'; */ export function materializeFinalNextConfig( exportedNextConfig: ExportedNextConfig, - userSentryWebpackPluginConfig?: Partial, runtimePhase?: string, - sentryBuildTimeOptions?: SentryBuildtimeOptions, + sentryBuildOptions?: SentryBuildOptions, ): NextConfigObject { - const sentrifiedConfig = withSentryConfig(exportedNextConfig, userSentryWebpackPluginConfig, sentryBuildTimeOptions); + const sentrifiedConfig = withSentryConfig(exportedNextConfig, sentryBuildOptions); let finalConfigValues = sentrifiedConfig; if (typeof sentrifiedConfig === 'function') { @@ -58,13 +53,11 @@ export function materializeFinalNextConfig( */ export async function materializeFinalWebpackConfig(options: { exportedNextConfig: ExportedNextConfig; - userSentryWebpackPluginConfig?: Partial; incomingWebpackConfig: WebpackConfigObject; incomingWebpackBuildContext: BuildContext; - sentryBuildTimeOptions?: SentryBuildtimeOptions; + sentryBuildTimeOptions?: SentryBuildOptions; }): Promise { - const { exportedNextConfig, userSentryWebpackPluginConfig, incomingWebpackConfig, incomingWebpackBuildContext } = - options; + const { exportedNextConfig, incomingWebpackConfig, incomingWebpackBuildContext } = options; // if the user's next config is a function, run it so we have access to the values const materializedUserNextConfig = @@ -75,8 +68,7 @@ export async function materializeFinalWebpackConfig(options: { // get the webpack config function we'd normally pass back to next const webpackConfigFunction = constructWebpackConfigFunction( materializedUserNextConfig, - userSentryWebpackPluginConfig, - options.sentryBuildTimeOptions ?? {}, + options.sentryBuildTimeOptions, ); // call it to get concrete values for comparison @@ -86,37 +78,3 @@ export async function materializeFinalWebpackConfig(options: { return finalWebpackConfigValue as WebpackConfigObjectWithModuleRules; } - -// helper function to make sure we're checking the correct plugin's data - -/** - * Given a webpack config, find a plugin (or the plugins) with the given name. - * - * Note that this function will error if more than one instance is found, unless the `allowMultiple` flag is passed. - * - * @param webpackConfig The webpack config object - * @param pluginName The name of the plugin's constructor - * @returns The plugin instance(s), or undefined if it's not found. - */ -export function findWebpackPlugin( - webpackConfig: WebpackConfigObject, - pluginName: string, - multipleAllowed: boolean = false, -): WebpackPluginInstance | SentryWebpackPlugin | WebpackPluginInstance[] | SentryWebpackPlugin[] | undefined { - const plugins = webpackConfig.plugins || []; - const matchingPlugins = plugins.filter(plugin => plugin.constructor.name === pluginName); - - if (matchingPlugins.length > 1 && !multipleAllowed) { - throw new Error( - `More than one ${pluginName} instance found. Please use the \`multipleAllowed\` flag if this is intentional.\nExisting plugins: ${plugins.map( - plugin => plugin.constructor.name, - )}`, - ); - } - - if (matchingPlugins.length > 0) { - return multipleAllowed ? matchingPlugins : matchingPlugins[0]; - } - - return undefined; -} diff --git a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts index ed101058a57e..a9d8b812f8a9 100644 --- a/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts +++ b/packages/nextjs/test/config/webpack/constructWebpackConfig.test.ts @@ -1,8 +1,6 @@ // mock helper functions not tested directly in this file import '../mocks'; -import { default as SentryWebpackPlugin } from '@sentry/webpack-plugin'; - import { CLIENT_SDK_CONFIG_FILE, clientBuildContext, @@ -26,7 +24,7 @@ describe('constructWebpackConfigFunction()', () => { expect.objectContaining({ devtool: 'source-map', entry: expect.any(Object), // `entry` is tested specifically elsewhere - plugins: expect.arrayContaining([expect.any(SentryWebpackPlugin)]), + plugins: expect.arrayContaining([expect.objectContaining({ _name: 'sentry-webpack-plugin' })]), }), ); }); @@ -58,8 +56,11 @@ describe('constructWebpackConfigFunction()', () => { }) as any, }, undefined, - undefined, - { disableServerWebpackPlugin: true }, + { + sourcemaps: { + disable: true, + }, + }, ); const finalWebpackConfig = finalNextConfig.webpack?.(serverWebpackConfig, serverBuildContext); diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts deleted file mode 100644 index f75aae23046b..000000000000 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ /dev/null @@ -1,367 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import { default as SentryWebpackPlugin } from '@sentry/webpack-plugin'; - -import type { BuildContext, ExportedNextConfig, SentryBuildtimeOptions } from '../../../src/config/types'; -import { getUserConfigFile, getWebpackPluginOptions } from '../../../src/config/webpack'; -import { - clientBuildContext, - clientWebpackConfig, - exportedNextConfig, - getBuildContext, - serverBuildContext, - serverWebpackConfig, - userSentryWebpackPluginConfig, -} from '../fixtures'; -import { exitsSync, mkdtempSyncSpy, mockExistsSync, realExistsSync } from '../mocks'; -import { findWebpackPlugin, materializeFinalWebpackConfig } from '../testUtils'; - -describe('Sentry webpack plugin config', () => { - it('includes expected properties', async () => { - // also, can pull from either env or user config (see notes on specific properties below) - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig, - userSentryWebpackPluginConfig, - incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: serverBuildContext, - }); - const sentryWebpackPluginInstance = findWebpackPlugin(finalWebpackConfig, 'SentryCliPlugin') as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options).toEqual( - expect.objectContaining({ - include: expect.any(Array), // default, tested separately elsewhere - ignore: [], // default - org: 'squirrelChasers', // from user webpack plugin config - project: 'simulator', // from user webpack plugin config - authToken: 'dogsarebadatkeepingsecrets', // picked up from env - stripPrefix: ['webpack://_N_E/', 'webpack://'], // default - urlPrefix: '~/_next', // default - entries: [], - release: 'doGsaREgReaT', // picked up from env - }), - ); - }); - - it('preserves unrelated plugin config options', async () => { - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig, - userSentryWebpackPluginConfig: { ...userSentryWebpackPluginConfig, debug: true }, - incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: serverBuildContext, - }); - const sentryWebpackPluginInstance = findWebpackPlugin(finalWebpackConfig, 'SentryCliPlugin') as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.debug).toEqual(true); - }); - - it('warns when overriding certain default values', () => { - // TODO - }); - - it("merges default include and ignore/ignoreFile options with user's values", () => { - // do we even want to do this? - }); - - describe('Sentry webpack plugin `include` option', () => { - it('has the correct value when building client bundles', async () => { - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig, - incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: clientBuildContext, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: [`${clientBuildContext.dir}/.next/static/chunks/pages`], urlPrefix: '~/_next/static/chunks/pages' }, - { paths: [`${clientBuildContext.dir}/.next/static/chunks/app`], urlPrefix: '~/_next/static/chunks/app' }, - ]); - }); - - it('has the correct value when building client bundles using `widenClientFileUpload` option', async () => { - const exportedNextConfigWithWidening = { ...exportedNextConfig }; - const buildContext = getBuildContext('client', exportedNextConfigWithWidening); - - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig: exportedNextConfigWithWidening, - incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: buildContext, - sentryBuildTimeOptions: { widenClientFileUpload: true }, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: [`${buildContext.dir}/.next/static/chunks`], urlPrefix: '~/_next/static/chunks' }, - ]); - }); - - it('has the correct value when building serverless server bundles', async () => { - const exportedNextConfigServerless = { - ...exportedNextConfig, - target: 'experimental-serverless-trace' as const, - }; - const buildContext = getBuildContext('server', exportedNextConfigServerless); - - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig: exportedNextConfigServerless, - incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: buildContext, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: [`${buildContext.dir}/.next/serverless/`], urlPrefix: '~/_next/serverless' }, - ]); - }); - - it('has the correct value when building serverful server bundles using webpack 4', async () => { - const serverBuildContextWebpack4 = getBuildContext('server', exportedNextConfig); - serverBuildContextWebpack4.webpack.version = '4.15.13'; - - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig, - incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: serverBuildContextWebpack4, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: [`${serverBuildContextWebpack4.dir}/.next/server/`], urlPrefix: '~/_next/server' }, - ]); - }); - - it('has the correct value when building serverful server bundles using webpack 5', async () => { - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig, - incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: serverBuildContext, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: [`${serverBuildContext.dir}/.next/server/`], urlPrefix: '~/_next/server' }, - ]); - }); - }); - - describe('Sentry webpack plugin `ignore` option', () => { - it('has the correct value when building client bundles', async () => { - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig, - incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: clientBuildContext, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.ignore).toEqual([]); - }); - - it('has the correct value when building client bundles using `widenClientFileUpload` option', async () => { - const exportedNextConfigWithWidening = { ...exportedNextConfig }; - const finalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig: exportedNextConfigWithWidening, - incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: getBuildContext('client', exportedNextConfigWithWidening), - sentryBuildTimeOptions: { widenClientFileUpload: true }, - }); - - const sentryWebpackPluginInstance = findWebpackPlugin( - finalWebpackConfig, - 'SentryCliPlugin', - ) as SentryWebpackPlugin; - - expect(sentryWebpackPluginInstance.options.ignore).toEqual([ - 'framework-*', - 'framework.*', - 'main-*', - 'polyfills-*', - 'webpack-*', - ]); - }); - }); - - describe('SentryWebpackPlugin enablement', () => { - let processEnvBackup: typeof process.env; - - beforeEach(() => { - processEnvBackup = { ...process.env }; - }); - - afterEach(() => { - process.env = processEnvBackup; - }); - - it.each([ - // [testName, exportedNextConfig, extraEnvValues, shouldFindServerPlugin, shouldFindClientPlugin] - [ - 'obeys `disableClientWebpackPlugin = true`', - { - ...exportedNextConfig, - }, - { disableClientWebpackPlugin: true }, - {}, - true, - false, - ], - - [ - 'obeys `disableServerWebpackPlugin = true`', - { - ...exportedNextConfig, - }, - { disableServerWebpackPlugin: true }, - {}, - false, - true, - ], - ])( - '%s', - async ( - _testName: string, - exportedNextConfig: ExportedNextConfig, - buildTimeOptions: SentryBuildtimeOptions, - extraEnvValues: Record, - shouldFindServerPlugin: boolean, - shouldFindClientPlugin: boolean, - ) => { - process.env = { ...process.env, ...extraEnvValues }; - - // We create a copy of the next config for each `materializeFinalWebpackConfig` call because the `sentry` - // property gets deleted along the way, and its value matters for some of our test cases - const serverFinalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig: { ...exportedNextConfig }, - userSentryWebpackPluginConfig, - incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: serverBuildContext, - sentryBuildTimeOptions: buildTimeOptions, - }); - - const clientFinalWebpackConfig = await materializeFinalWebpackConfig({ - exportedNextConfig: { ...exportedNextConfig }, - userSentryWebpackPluginConfig, - incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: clientBuildContext, - sentryBuildTimeOptions: buildTimeOptions, - }); - - const genericSentryWebpackPluginInstance = expect.any(SentryWebpackPlugin); - - expect(findWebpackPlugin(serverFinalWebpackConfig, 'SentryCliPlugin')).toEqual( - shouldFindServerPlugin ? genericSentryWebpackPluginInstance : undefined, - ); - expect(findWebpackPlugin(clientFinalWebpackConfig, 'SentryCliPlugin')).toEqual( - shouldFindClientPlugin ? genericSentryWebpackPluginInstance : undefined, - ); - }, - ); - }); - - describe('getUserConfigFile', () => { - let tempDir: string; - - beforeAll(() => { - exitsSync.mockImplementation(realExistsSync); - }); - - beforeEach(() => { - // these will get cleaned up by the file's overall `afterAll` function, and the `mkdtempSync` mock above ensures - // that the location of the created folder is stored in `tempDir` - const tempDirPathPrefix = path.join(os.tmpdir(), 'sentry-nextjs-test-'); - fs.mkdtempSync(tempDirPathPrefix); - tempDir = mkdtempSyncSpy.mock.results[0].value; - }); - - afterAll(() => { - exitsSync.mockImplementation(mockExistsSync); - }); - - it('successfully finds js files', () => { - fs.writeFileSync(path.resolve(tempDir, 'sentry.server.config.js'), 'Dogs are great!'); - fs.writeFileSync(path.resolve(tempDir, 'sentry.client.config.js'), 'Squirrel!'); - - expect(getUserConfigFile(tempDir, 'server')).toEqual('sentry.server.config.js'); - expect(getUserConfigFile(tempDir, 'client')).toEqual('sentry.client.config.js'); - }); - - it('successfully finds ts files', () => { - fs.writeFileSync(path.resolve(tempDir, 'sentry.server.config.ts'), 'Sit. Stay. Lie Down.'); - fs.writeFileSync(path.resolve(tempDir, 'sentry.client.config.ts'), 'Good dog!'); - - expect(getUserConfigFile(tempDir, 'server')).toEqual('sentry.server.config.ts'); - expect(getUserConfigFile(tempDir, 'client')).toEqual('sentry.client.config.ts'); - }); - - it('errors when files are missing', () => { - expect(() => getUserConfigFile(tempDir, 'server')).toThrowError( - `Cannot find 'sentry.server.config.ts' or 'sentry.server.config.js' in '${tempDir}'`, - ); - expect(() => getUserConfigFile(tempDir, 'client')).toThrowError( - `Cannot find 'sentry.client.config.ts' or 'sentry.client.config.js' in '${tempDir}'`, - ); - }); - }); - - describe('correct paths from `distDir` in WebpackPluginOptions', () => { - it.each([ - [getBuildContext('client', {}), '.next'], - [getBuildContext('server', { target: 'experimental-serverless-trace' }), '.next'], // serverless - [getBuildContext('server', {}, '4'), '.next'], - [getBuildContext('server', {}, '5'), '.next'], - ])('`distDir` is not defined', (buildContext: BuildContext, expectedDistDir) => { - const includePaths = getWebpackPluginOptions( - buildContext, - {}, // userPluginOptions - {}, // userSentryOptions - ).include as { paths: [] }[]; - - for (const pathDescriptor of includePaths) { - for (const path of pathDescriptor.paths) { - expect(path).toMatch(new RegExp(`${buildContext.dir}/${expectedDistDir}.*`)); - } - } - }); - - it.each([ - [getBuildContext('client', { distDir: 'tmpDir' }), 'tmpDir'], - [getBuildContext('server', { distDir: 'tmpDir', target: 'experimental-serverless-trace' }), 'tmpDir'], // serverless - [getBuildContext('server', { distDir: 'tmpDir' }, '4'), 'tmpDir'], - [getBuildContext('server', { distDir: 'tmpDir' }, '5'), 'tmpDir'], - ])('`distDir` is defined', (buildContext: BuildContext, expectedDistDir) => { - const includePaths = getWebpackPluginOptions( - buildContext, - {}, // userPluginOptions - {}, // userSentryOptions - ).include as { paths: [] }[]; - - for (const pathDescriptor of includePaths) { - for (const path of pathDescriptor.paths) { - expect(path).toMatch(new RegExp(`${buildContext.dir}/${expectedDistDir}.*`)); - } - } - }); - }); -}); diff --git a/packages/nextjs/test/config/webpack/webpack.test.ts b/packages/nextjs/test/config/webpack/webpack.test.ts new file mode 100644 index 000000000000..d0f8606e3b4c --- /dev/null +++ b/packages/nextjs/test/config/webpack/webpack.test.ts @@ -0,0 +1,51 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { getUserConfigFile } from '../../../src/config/webpack'; +import { exitsSync, mkdtempSyncSpy, mockExistsSync, realExistsSync } from '../mocks'; + +describe('getUserConfigFile', () => { + let tempDir: string; + + beforeAll(() => { + exitsSync.mockImplementation(realExistsSync); + }); + + beforeEach(() => { + // these will get cleaned up by the file's overall `afterAll` function, and the `mkdtempSync` mock above ensures + // that the location of the created folder is stored in `tempDir` + const tempDirPathPrefix = path.join(os.tmpdir(), 'sentry-nextjs-test-'); + fs.mkdtempSync(tempDirPathPrefix); + tempDir = mkdtempSyncSpy.mock.results[0].value; + }); + + afterAll(() => { + exitsSync.mockImplementation(mockExistsSync); + }); + + it('successfully finds js files', () => { + fs.writeFileSync(path.resolve(tempDir, 'sentry.server.config.js'), 'Dogs are great!'); + fs.writeFileSync(path.resolve(tempDir, 'sentry.client.config.js'), 'Squirrel!'); + + expect(getUserConfigFile(tempDir, 'server')).toEqual('sentry.server.config.js'); + expect(getUserConfigFile(tempDir, 'client')).toEqual('sentry.client.config.js'); + }); + + it('successfully finds ts files', () => { + fs.writeFileSync(path.resolve(tempDir, 'sentry.server.config.ts'), 'Sit. Stay. Lie Down.'); + fs.writeFileSync(path.resolve(tempDir, 'sentry.client.config.ts'), 'Good dog!'); + + expect(getUserConfigFile(tempDir, 'server')).toEqual('sentry.server.config.ts'); + expect(getUserConfigFile(tempDir, 'client')).toEqual('sentry.client.config.ts'); + }); + + it('errors when files are missing', () => { + expect(() => getUserConfigFile(tempDir, 'server')).toThrowError( + `Cannot find 'sentry.server.config.ts' or 'sentry.server.config.js' in '${tempDir}'`, + ); + expect(() => getUserConfigFile(tempDir, 'client')).toThrowError( + `Cannot find 'sentry.client.config.ts' or 'sentry.client.config.js' in '${tempDir}'`, + ); + }); +}); diff --git a/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts new file mode 100644 index 000000000000..54d55a179e28 --- /dev/null +++ b/packages/nextjs/test/config/webpack/webpackPluginOptions.test.ts @@ -0,0 +1,157 @@ +import type { BuildContext, NextConfigObject } from '../../../src/config/types'; +import { getWebpackPluginOptions } from '../../../src/config/webpackPluginOptions'; + +function generateBuildContext(overrides: { + isServer: boolean; + nextjsConfig?: NextConfigObject; +}): BuildContext { + return { + dev: false, // The plugin is not included in dev mode + isServer: overrides.isServer, + buildId: 'test-build-id', + dir: '/my/project/dir', + config: overrides.nextjsConfig ?? {}, + totalPages: 2, + defaultLoaders: true, + webpack: { + version: '4.0.0', + DefinePlugin: {} as any, + }, + }; +} + +describe('getWebpackPluginOptions()', () => { + it('forwards relevant options', () => { + const buildContext = generateBuildContext({ isServer: false }); + const generatedPluginOptions = getWebpackPluginOptions(buildContext, { + authToken: 'my-auth-token', + headers: { 'my-test-header': 'test' }, + org: 'my-org', + project: 'my-project', + telemetry: false, + reactComponentAnnotation: { + enabled: true, + }, + silent: false, + debug: true, + sentryUrl: 'my-url', + sourcemaps: { + assets: ['my-asset'], + ignore: ['my-ignore'], + }, + release: { + name: 'my-release', + create: false, + finalize: false, + dist: 'my-dist', + vcsRemote: 'my-origin', + setCommits: { + auto: true, + }, + deploy: { + env: 'my-env', + }, + }, + }); + + expect(generatedPluginOptions.authToken).toBe('my-auth-token'); + expect(generatedPluginOptions.debug).toBe(true); + expect(generatedPluginOptions.headers).toStrictEqual({ 'my-test-header': 'test' }); + expect(generatedPluginOptions.org).toBe('my-org'); + expect(generatedPluginOptions.project).toBe('my-project'); + expect(generatedPluginOptions.reactComponentAnnotation?.enabled).toBe(true); + expect(generatedPluginOptions.release?.create).toBe(false); + expect(generatedPluginOptions.release?.deploy?.env).toBe('my-env'); + expect(generatedPluginOptions.release?.dist).toBe('my-dist'); + expect(generatedPluginOptions.release?.finalize).toBe(false); + expect(generatedPluginOptions.release?.name).toBe('my-release'); + expect(generatedPluginOptions.release?.setCommits?.auto).toBe(true); + expect(generatedPluginOptions.release?.vcsRemote).toBe('my-origin'); + expect(generatedPluginOptions.silent).toBe(false); + expect(generatedPluginOptions.sourcemaps?.assets).toStrictEqual(['my-asset']); + expect(generatedPluginOptions.sourcemaps?.ignore).toStrictEqual(['my-ignore']); + expect(generatedPluginOptions.telemetry).toBe(false); + expect(generatedPluginOptions.url).toBe('my-url'); + + expect(generatedPluginOptions).toMatchObject({ + authToken: 'my-auth-token', + debug: true, + headers: { + 'my-test-header': 'test', + }, + org: 'my-org', + project: 'my-project', + reactComponentAnnotation: { + enabled: true, + }, + release: { + create: false, + deploy: { + env: 'my-env', + }, + dist: 'my-dist', + finalize: false, + inject: false, + name: 'my-release', + setCommits: { + auto: true, + }, + vcsRemote: 'my-origin', + }, + silent: false, + sourcemaps: { + assets: ['my-asset'], + ignore: ['my-ignore'], + }, + telemetry: false, + url: 'my-url', + }); + }); + + it('returns the right `assets` and `ignore` values during the server build', () => { + const buildContext = generateBuildContext({ isServer: true }); + const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}); + expect(generatedPluginOptions.sourcemaps).toMatchObject({ + assets: ['/my/project/dir/.next/server/**', '/my/project/dir/.next/serverless/**'], + ignore: [], + }); + }); + + it('returns the right `assets` and `ignore` values during the client build', () => { + const buildContext = generateBuildContext({ isServer: false }); + const generatedPluginOptions = getWebpackPluginOptions(buildContext, {}); + expect(generatedPluginOptions.sourcemaps).toMatchObject({ + assets: ['/my/project/dir/.next/static/chunks/pages/**', '/my/project/dir/.next/static/chunks/app/**'], + ignore: [ + '/my/project/dir/.next/static/chunks/framework-*', + '/my/project/dir/.next/static/chunks/framework.*', + '/my/project/dir/.next/static/chunks/main-*', + '/my/project/dir/.next/static/chunks/polyfills-*', + '/my/project/dir/.next/static/chunks/webpack-*', + ], + }); + }); + + it('returns the right `assets` and `ignore` values during the client build with `widenClientFileUpload`', () => { + const buildContext = generateBuildContext({ isServer: false }); + const generatedPluginOptions = getWebpackPluginOptions(buildContext, { widenClientFileUpload: true }); + expect(generatedPluginOptions.sourcemaps).toMatchObject({ + assets: ['/my/project/dir/.next/static/chunks/**'], + ignore: [ + '/my/project/dir/.next/static/chunks/framework-*', + '/my/project/dir/.next/static/chunks/framework.*', + '/my/project/dir/.next/static/chunks/main-*', + '/my/project/dir/.next/static/chunks/polyfills-*', + '/my/project/dir/.next/static/chunks/webpack-*', + ], + }); + }); + + it('sets `sourcemaps.assets` to an empty array when `sourcemaps.disable` is true', () => { + const buildContext = generateBuildContext({ isServer: false }); + const generatedPluginOptions = getWebpackPluginOptions(buildContext, { sourcemaps: { disable: true } }); + expect(generatedPluginOptions.sourcemaps).toMatchObject({ + assets: [], + }); + }); +}); diff --git a/packages/nextjs/test/integration/next.config.js b/packages/nextjs/test/integration/next.config.js index 3f209311c332..815eba98e889 100644 --- a/packages/nextjs/test/integration/next.config.js +++ b/packages/nextjs/test/integration/next.config.js @@ -5,16 +5,9 @@ const moduleExports = { ignoreDuringBuilds: true, }, pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], - sentry: { - // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` - // TODO (v8): This can come out in v8, because this option will get a default value - hideSourceMaps: false, - excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], - }, -}; -const SentryWebpackPluginOptions = { - dryRun: true, - silent: true, }; -module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); +module.exports = withSentryConfig(moduleExports, { + debug: true, + excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], +}); diff --git a/packages/nextjs/test/integration/next10.config.template b/packages/nextjs/test/integration/next10.config.template index 6e55fa099a22..e01358c9e4d4 100644 --- a/packages/nextjs/test/integration/next10.config.template +++ b/packages/nextjs/test/integration/next10.config.template @@ -6,20 +6,9 @@ const moduleExports = { webpack5: %RUN_WEBPACK_5%, }, pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], - sentry: { - // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` - // TODO (v8): This can come out in v8, because this option will get a default value - hideSourceMaps: false, - excludeServerRoutes: [ - '/api/excludedEndpoints/excludedWithString', - /\/api\/excludedEndpoints\/excludedWithRegExp/, - ], - }, -}; - -const SentryWebpackPluginOptions = { - dryRun: true, - silent: true, }; -module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); +module.exports = withSentryConfig(moduleExports, { + debug: true, + excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], +}); diff --git a/packages/nextjs/test/integration/next11.config.template b/packages/nextjs/test/integration/next11.config.template index 28aaba18639a..5215e38a9e5b 100644 --- a/packages/nextjs/test/integration/next11.config.template +++ b/packages/nextjs/test/integration/next11.config.template @@ -7,20 +7,9 @@ const moduleExports = { ignoreDuringBuilds: true, }, pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], - sentry: { - // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` - // TODO (v8): This can come out in v8, because this option will get a default value - hideSourceMaps: false, - excludeServerRoutes: [ - '/api/excludedEndpoints/excludedWithString', - /\/api\/excludedEndpoints\/excludedWithRegExp/, - ], - }, -}; - -const SentryWebpackPluginOptions = { - dryRun: true, - silent: true, }; -module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); +module.exports = withSentryConfig(moduleExports, { + debug: true, + excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], +}); diff --git a/packages/nextjs/test/integration/next12.config.template b/packages/nextjs/test/integration/next12.config.template index 80b928629534..82fe205e279d 100644 --- a/packages/nextjs/test/integration/next12.config.template +++ b/packages/nextjs/test/integration/next12.config.template @@ -5,17 +5,9 @@ const moduleExports = async () => ({ ignoreDuringBuilds: true, }, pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], - sentry: { - // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` - // TODO (v8): This can come out in v8, because this option will get a default value - hideSourceMaps: false, - excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], - }, }); -const SentryWebpackPluginOptions = { - dryRun: true, - silent: true, -}; - -module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); +module.exports = withSentryConfig(moduleExports, { + debug: true, + excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], +}); diff --git a/packages/nextjs/test/integration/next13.appdir.config.template b/packages/nextjs/test/integration/next13.appdir.config.template index 4b17134ee1d7..e9e4e4e04b2e 100644 --- a/packages/nextjs/test/integration/next13.appdir.config.template +++ b/packages/nextjs/test/integration/next13.appdir.config.template @@ -8,18 +8,9 @@ const moduleExports = { appDir: Number(process.env.NODE_MAJOR) >= 16, // experimental.appDir requires Node v16.8.0 or later. }, pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], - sentry: { - // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` - // TODO (v8): This can come out in v8, because this option will get a default value - hideSourceMaps: false, - excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], - }, -}; - -const SentryWebpackPluginOptions = { - dryRun: true, - silent: true, }; -module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); - +module.exports = withSentryConfig(moduleExports, { + debug: true, + excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], +}); diff --git a/packages/nextjs/test/integration/next13.config.template b/packages/nextjs/test/integration/next13.config.template index e32ecec4b0f4..815eba98e889 100644 --- a/packages/nextjs/test/integration/next13.config.template +++ b/packages/nextjs/test/integration/next13.config.template @@ -5,18 +5,9 @@ const moduleExports = { ignoreDuringBuilds: true, }, pageExtensions: ['jsx', 'js', 'tsx', 'ts', 'page.tsx'], - sentry: { - // Suppress the warning message from `handleSourcemapHidingOptionWarning` in `src/config/webpack.ts` - // TODO (v8): This can come out in v8, because this option will get a default value - hideSourceMaps: false, - excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], - }, -}; - -const SentryWebpackPluginOptions = { - dryRun: true, - silent: true, }; -module.exports = withSentryConfig(moduleExports, SentryWebpackPluginOptions); - +module.exports = withSentryConfig(moduleExports, { + debug: true, + excludeServerRoutes: ['/api/excludedEndpoints/excludedWithString', /\/api\/excludedEndpoints\/excludedWithRegExp/], +}); diff --git a/packages/nextjs/test/integration/tsconfig.json b/packages/nextjs/test/integration/tsconfig.json index a804126c855b..49a874193c18 100644 --- a/packages/nextjs/test/integration/tsconfig.json +++ b/packages/nextjs/test/integration/tsconfig.json @@ -24,7 +24,6 @@ "name": "next" } ], - "incremental": true }, "exclude": ["node_modules"], "include": ["**/*.ts", "**/*.tsx", "../../playwright.config.ts", ".next/types/**/*.ts"] diff --git a/packages/nextjs/test/run-integration-tests.sh b/packages/nextjs/test/run-integration-tests.sh index 79adf0016075..2522b05f6acb 100755 --- a/packages/nextjs/test/run-integration-tests.sh +++ b/packages/nextjs/test/run-integration-tests.sh @@ -68,9 +68,15 @@ for NEXTJS_VERSION in 10 11 12 13; do if [ "$NEXTJS_VERSION" -eq "13" ]; then npm i --save react@18.2.0 react-dom@18.2.0 fi - # We have to use `--ignore-engines` because sucrase claims to need Node 12, even though tests pass just fine on Node - # 10 - yarn --no-lockfile --ignore-engines --silent >/dev/null 2>&1 + + # Yarn install randomly started failing because it couldn't find some cache so for now we need to run these two commands which seem to fix it. + # It was pretty much this issue: https://github.com/yarnpkg/yarn/issues/5275 + rm -rf node_modules + yarn cache clean + + # We have to use `--ignore-engines` because sucrase claims to need Node 12, even though tests pass just fine on Node 10 + yarn --no-lockfile --ignore-engines + # if applicable, use local versions of `@sentry/cli` and/or `@sentry/webpack-plugin` (these commands no-op unless # LINKED_CLI_REPO and/or LINKED_PLUGIN_REPO are set) linkcli && linkplugin @@ -125,13 +131,6 @@ for NEXTJS_VERSION in 10 11 12 13; do echo "[nextjs@$NEXTJS_VERSION | webpack@$WEBPACK_VERSION] Building..." yarn build - # if the user hasn't passed any args, use the default one, which restricts each test to only outputting success and - # failure messages - args=$* - if [[ ! $args ]]; then - args="--silent" - fi - # we keep this updated as we run the tests, so that if it's ever non-zero, we can bail EXIT_CODE=0 diff --git a/packages/nextjs/test/types/next.config.ts b/packages/nextjs/test/types/next.config.ts index 36e50a276125..74ea16a946db 100644 --- a/packages/nextjs/test/types/next.config.ts +++ b/packages/nextjs/test/types/next.config.ts @@ -13,6 +13,4 @@ const config: NextConfig = { }), }; -module.exports = withSentryConfig(config, { - validate: true, -}); +module.exports = withSentryConfig(config); diff --git a/yarn.lock b/yarn.lock index 74ae63b93184..447204cfed33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -733,6 +733,27 @@ json5 "^2.2.1" semver "^6.3.0" +"@babel/core@^7.18.5": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.0.tgz#56cbda6b185ae9d9bed369816a8f4423c5f2ff1b" + integrity sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.0" + "@babel/parser" "^7.24.0" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.22.10": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83" @@ -881,7 +902,7 @@ lru-cache "^5.1.1" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.18.2": +"@babel/helper-compilation-targets@^7.18.2", "@babel/helper-compilation-targets@^7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== @@ -1089,7 +1110,7 @@ "@babel/traverse" "^7.20.10" "@babel/types" "^7.20.7" -"@babel/helper-module-transforms@^7.18.0": +"@babel/helper-module-transforms@^7.18.0", "@babel/helper-module-transforms@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== @@ -1299,6 +1320,15 @@ "@babel/traverse" "^7.23.0" "@babel/types" "^7.23.0" +"@babel/helpers@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.0.tgz#a3dd462b41769c95db8091e49cfe019389a9409b" + integrity sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -1351,6 +1381,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== +"@babel/parser@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" + integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -2519,6 +2554,15 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" +"@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/traverse@^7.11.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7", "@babel/traverse@^7.22.10", "@babel/traverse@^7.23.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" @@ -2551,6 +2595,22 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.0.tgz#4a408fbf364ff73135c714a2ab46a5eab2831b1e" + integrity sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" @@ -2605,6 +2665,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -5757,6 +5826,11 @@ resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.14.2.tgz#d756bed93495e97a5a2aad56e2a6dc5020305adc" integrity sha512-mFBVnIZmdMrpxo61rG5yf0WFt5VrRpy8cpIpJtT3mYkX9vDmcUZaZaD1ctv73iZF3QwaieVdn05Na5mWzZ8h/A== +"@sentry/babel-plugin-component-annotate@2.14.3": + version "2.14.3" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.14.3.tgz#7064d656e620e73c671f23815727475369ecc070" + integrity sha512-h0ONVTe8j3Ma2g5SMsl9ynmLZdCf+CupLF7PQ7n06K0L8dDtrHqo8yjsWaZSJf4InGrJ9HC4MaOSItbFjiTFLw== + "@sentry/bundler-plugin-core@2.14.2": version "2.14.2" resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.14.2.tgz#6750c46fa4836b46ea48556b19f5e6789a428a47" @@ -5771,6 +5845,20 @@ magic-string "0.27.0" unplugin "1.0.1" +"@sentry/bundler-plugin-core@2.14.3": + version "2.14.3" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.14.3.tgz#b431361afb86dfb7330e4e1c593c6edc921139ee" + integrity sha512-iEtMdAPFUAYngvYhkfbgY8m3zB439u+5tvovj9rBXHGMr3nEB5fzZLOcVuiL47GEuUvMjDdrubl9MDGZ0c1IuQ== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "2.14.3" + "@sentry/cli" "^2.22.3" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^9.3.2" + magic-string "0.27.0" + unplugin "1.0.1" + "@sentry/cli-darwin@2.30.0": version "2.30.0" resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.30.0.tgz#492ecade496a54523ae5400562e3b43e719a3d0f" @@ -5806,7 +5894,7 @@ resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.30.0.tgz#1b6670222eedd4ae3c50f40f7180c07df38a304e" integrity sha512-CiXuxnpJI0zho0XE5PVqIS9CwjDEYoMnHQRIr4Jj9OzlGTJ2iSSU6Zp3Ykd7lgo2iK4P6MfxIBm30gFQPnSvVA== -"@sentry/cli@^1.74.4", "@sentry/cli@^1.77.1": +"@sentry/cli@^1.74.4": version "1.77.1" resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.77.1.tgz#ebcf884712ef6c3c75443f491ec16f6a22148aec" integrity sha512-OtJ7U9LeuPUAY/xow9wwcjM9w42IJIpDtClTKI/RliE685vd/OJUIpiAvebHNthDYpQynvwb/0iuF4fonh+CKw== @@ -5852,13 +5940,14 @@ dependencies: "@sentry/cli" "^1.74.4" -"@sentry/webpack-plugin@1.21.0": - version "1.21.0" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.21.0.tgz#bbe7cb293751f80246a4a56f9a7dd6de00f14b58" - integrity sha512-x0PYIMWcsTauqxgl7vWUY6sANl+XGKtx7DCVnnY7aOIIlIna0jChTAPANTfA2QrK+VK+4I/4JxatCEZBnXh3Og== +"@sentry/webpack-plugin@2.14.3": + version "2.14.3" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.14.3.tgz#b392d53381115528537355d0eb1a8cf8189662ce" + integrity sha512-3nsaBqdC2MJ+06BsCa5IwJIFaODT4UJis9+MSyuPLF+Ics+K3le4dlOCl8mc2Xm0WjqJUTlUmwvTj+jasnGMUw== dependencies: - "@sentry/cli" "^1.77.1" - webpack-sources "^2.0.0 || ^3.0.0" + "@sentry/bundler-plugin-core" "2.14.3" + unplugin "1.0.1" + uuid "^9.0.0" "@sideway/address@^4.1.3": version "4.1.4" @@ -17155,7 +17244,7 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, gl once "^1.3.0" path-is-absolute "^1.0.0" -glob@^9.2.0: +glob@^9.2.0, glob@^9.3.2: version "9.3.5" resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== @@ -32598,7 +32687,7 @@ webpack-sources@1.4.3, webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-s source-list-map "^2.0.0" source-map "~0.6.1" -"webpack-sources@^2.0.0 || ^3.0.0", webpack-sources@^3.2.0, webpack-sources@^3.2.3: +webpack-sources@^3.2.0, webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==