From fae068221ddd0f72d980ad016e29557636d83079 Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Wed, 16 Nov 2022 13:20:23 -0800 Subject: [PATCH] fix(nextjs): Use absolute path for `distDir` in webpack plugin options (#6214) In nextjs, the output directory (`distDir`) is specified by the user and stored in memory as a relative path. It's then used in two places: - At build time, it's resolved against the project directory, in order to know where to output built files. - At runtime, it's resolved against the working directory, in order to know where to find those built files. In the nextjs SDK, we also use the value in two places: - At build time, we pass it to `@sentry/cli` via Sentry webpack plugin options. `sentry/cli` then resolves it against its working directory, so that it knows where to find built files to upload. - At runtime, we grab it from `global.__rewriteFramesDistDir__` (where we've stashed it at build time via our prefix loader), and then resolve it against the working directory, so the `RewriteFrames` integration knows what value to strip from stackframe paths. At runtime, this resolution always works, because it matches what nextjs itself does. At build time, it also works... most of the time. But in the case where the project directory and `@sentry/cli`'s working directory don't match, it leads to `@sentry/cli` not being able to find the files it needs to upload. (This can happen if, for example, the app is a package in a monorepo - located at `packages/nextjsApp`, say - and `@sentry/cli` is running from the monorepo's root level `node_modules`.) This fixes that by resolving the `distDir` value against the project directory (thereby turning it into an absolute path) before passing it to `@sentry/cli`. That way, no resolution on `@sentry/cli`'s part is necessary, preventing the mismatch. Fixes the problem outlined in https://github.com/getsentry/sentry-javascript/pull/6194. --- packages/nextjs/src/config/webpack.ts | 12 ++--- .../webpack/sentryWebpackPlugin.test.ts | 48 ++++++++++++------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index 45d64e9db857..81e38cee6007 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -452,7 +452,7 @@ export function getWebpackPluginOptions( const { buildId, isServer, webpack, config, dev: isDev, dir: projectDir } = buildContext; const userNextConfig = config as NextConfigObject; - const distDir = userNextConfig.distDir ?? '.next'; // `.next` is the default directory + const distDirAbsPath = path.resolve(projectDir, userNextConfig.distDir || '.next'); // `.next` is the default directory const isWebpack5 = webpack.version.startsWith('5'); const isServerless = userNextConfig.target === 'experimental-serverless-trace'; @@ -460,14 +460,14 @@ export function getWebpackPluginOptions( const urlPrefix = userNextConfig.basePath ? `~${userNextConfig.basePath}/_next` : '~/_next'; const serverInclude = isServerless - ? [{ paths: [`${distDir}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }] - : [{ paths: [`${distDir}/server/pages/`], urlPrefix: `${urlPrefix}/server/pages` }].concat( - isWebpack5 ? [{ paths: [`${distDir}/server/chunks/`], urlPrefix: `${urlPrefix}/server/chunks` }] : [], + ? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }] + : [{ paths: [`${distDirAbsPath}/server/pages/`], urlPrefix: `${urlPrefix}/server/pages` }].concat( + isWebpack5 ? [{ paths: [`${distDirAbsPath}/server/chunks/`], urlPrefix: `${urlPrefix}/server/chunks` }] : [], ); const clientInclude = userSentryOptions.widenClientFileUpload - ? [{ paths: [`${distDir}/static/chunks`], urlPrefix: `${urlPrefix}/static/chunks` }] - : [{ paths: [`${distDir}/static/chunks/pages`], urlPrefix: `${urlPrefix}/static/chunks/pages` }]; + ? [{ paths: [`${distDirAbsPath}/static/chunks`], urlPrefix: `${urlPrefix}/static/chunks` }] + : [{ paths: [`${distDirAbsPath}/static/chunks/pages`], urlPrefix: `${urlPrefix}/static/chunks/pages` }]; const defaultPluginOptions = dropUndefinedKeys({ include: isServer ? serverInclude : clientInclude, diff --git a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts index c08736a7b2d9..e1e16a0e7ed1 100644 --- a/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts +++ b/packages/nextjs/test/config/webpack/sentryWebpackPlugin.test.ts @@ -77,16 +77,18 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/static/chunks/pages'], urlPrefix: '~/_next/static/chunks/pages' }, + { paths: [`${clientBuildContext.dir}/.next/static/chunks/pages`], urlPrefix: '~/_next/static/chunks/pages' }, ]); }); it('has the correct value when building client bundles using `widenClientFileUpload` option', async () => { const exportedNextConfigWithWidening = { ...exportedNextConfig, sentry: { widenClientFileUpload: true } }; + const buildContext = getBuildContext('client', exportedNextConfigWithWidening); + const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig: exportedNextConfigWithWidening, incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: getBuildContext('client', exportedNextConfigWithWidening), + incomingWebpackBuildContext: buildContext, }); const sentryWebpackPluginInstance = findWebpackPlugin( @@ -95,7 +97,7 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/static/chunks'], urlPrefix: '~/_next/static/chunks' }, + { paths: [`${buildContext.dir}/.next/static/chunks`], urlPrefix: '~/_next/static/chunks' }, ]); }); @@ -104,11 +106,12 @@ describe('Sentry webpack plugin config', () => { ...exportedNextConfig, target: 'experimental-serverless-trace' as const, }; + const buildContext = getBuildContext('server', exportedNextConfigServerless); const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig: exportedNextConfigServerless, incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: getBuildContext('server', exportedNextConfigServerless), + incomingWebpackBuildContext: buildContext, }); const sentryWebpackPluginInstance = findWebpackPlugin( @@ -117,7 +120,7 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/serverless/'], urlPrefix: '~/_next/serverless' }, + { paths: [`${buildContext.dir}/.next/serverless/`], urlPrefix: '~/_next/serverless' }, ]); }); @@ -137,7 +140,7 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/server/pages/'], urlPrefix: '~/_next/server/pages' }, + { paths: [`${serverBuildContextWebpack4.dir}/.next/server/pages/`], urlPrefix: '~/_next/server/pages' }, ]); }); @@ -154,8 +157,8 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/server/pages/'], urlPrefix: '~/_next/server/pages' }, - { paths: ['.next/server/chunks/'], urlPrefix: '~/_next/server/chunks' }, + { paths: [`${serverBuildContext.dir}/.next/server/pages/`], urlPrefix: '~/_next/server/pages' }, + { paths: [`${serverBuildContext.dir}/.next/server/chunks/`], urlPrefix: '~/_next/server/chunks' }, ]); }); }); @@ -206,10 +209,11 @@ describe('Sentry webpack plugin config', () => { }; it('has the correct value when building client bundles', async () => { + const buildContext = getBuildContext('client', exportedNextConfigWithBasePath); const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig: exportedNextConfigWithBasePath, incomingWebpackConfig: clientWebpackConfig, - incomingWebpackBuildContext: getBuildContext('client', exportedNextConfigWithBasePath), + incomingWebpackBuildContext: buildContext, }); const sentryWebpackPluginInstance = findWebpackPlugin( @@ -218,7 +222,10 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/static/chunks/pages'], urlPrefix: '~/city-park/_next/static/chunks/pages' }, + { + paths: [`${buildContext.dir}/.next/static/chunks/pages`], + urlPrefix: '~/city-park/_next/static/chunks/pages', + }, ]); }); @@ -227,11 +234,12 @@ describe('Sentry webpack plugin config', () => { ...exportedNextConfigWithBasePath, target: 'experimental-serverless-trace' as const, }; + const buildContext = getBuildContext('server', exportedNextConfigServerless); const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig: exportedNextConfigServerless, incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: getBuildContext('server', exportedNextConfigServerless), + incomingWebpackBuildContext: buildContext, }); const sentryWebpackPluginInstance = findWebpackPlugin( @@ -240,7 +248,7 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/serverless/'], urlPrefix: '~/city-park/_next/serverless' }, + { paths: [`${buildContext.dir}/.next/serverless/`], urlPrefix: '~/city-park/_next/serverless' }, ]); }); @@ -260,15 +268,19 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/server/pages/'], urlPrefix: '~/city-park/_next/server/pages' }, + { + paths: [`${serverBuildContextWebpack4.dir}/.next/server/pages/`], + urlPrefix: '~/city-park/_next/server/pages', + }, ]); }); it('has the correct value when building serverful server bundles using webpack 5', async () => { + const buildContext = getBuildContext('server', exportedNextConfigWithBasePath); const finalWebpackConfig = await materializeFinalWebpackConfig({ exportedNextConfig: exportedNextConfigWithBasePath, incomingWebpackConfig: serverWebpackConfig, - incomingWebpackBuildContext: getBuildContext('server', exportedNextConfigWithBasePath), + incomingWebpackBuildContext: buildContext, }); const sentryWebpackPluginInstance = findWebpackPlugin( @@ -277,8 +289,8 @@ describe('Sentry webpack plugin config', () => { ) as SentryWebpackPlugin; expect(sentryWebpackPluginInstance.options.include).toEqual([ - { paths: ['.next/server/pages/'], urlPrefix: '~/city-park/_next/server/pages' }, - { paths: ['.next/server/chunks/'], urlPrefix: '~/city-park/_next/server/chunks' }, + { paths: [`${buildContext.dir}/.next/server/pages/`], urlPrefix: '~/city-park/_next/server/pages' }, + { paths: [`${buildContext.dir}/.next/server/chunks/`], urlPrefix: '~/city-park/_next/server/chunks' }, ]); }); }); @@ -450,7 +462,7 @@ describe('Sentry webpack plugin config', () => { for (const pathDescriptor of includePaths) { for (const path of pathDescriptor.paths) { - expect(path).toMatch(new RegExp(`^${expectedDistDir}.*`)); + expect(path).toMatch(new RegExp(`${buildContext.dir}/${expectedDistDir}.*`)); } } }); @@ -469,7 +481,7 @@ describe('Sentry webpack plugin config', () => { for (const pathDescriptor of includePaths) { for (const path of pathDescriptor.paths) { - expect(path).toMatch(new RegExp(`^${expectedDistDir}.*`)); + expect(path).toMatch(new RegExp(`${buildContext.dir}/${expectedDistDir}.*`)); } } });