| 
 | 1 | +import { WebpackPluginInstance } from 'webpack';  | 
 | 2 | + | 
 | 3 | +import { withSentryConfig } from '../../src/config';  | 
 | 4 | +import {  | 
 | 5 | +  BuildContext,  | 
 | 6 | +  EntryPropertyFunction,  | 
 | 7 | +  ExportedNextConfig,  | 
 | 8 | +  NextConfigObject,  | 
 | 9 | +  SentryWebpackPluginOptions,  | 
 | 10 | +  WebpackConfigObject,  | 
 | 11 | +} from '../../src/config/types';  | 
 | 12 | +import { constructWebpackConfigFunction, SentryWebpackPlugin } from '../../src/config/webpack';  | 
 | 13 | +import { defaultsObject, runtimePhase } from './fixtures';  | 
 | 14 | + | 
 | 15 | +/**  | 
 | 16 | + * Derive the final values of all next config options, by first applying `withSentryConfig` and then, if it returns a  | 
 | 17 | + *  function, running that function.  | 
 | 18 | + *  | 
 | 19 | + * @param exportedNextConfig Next config options provided by the user  | 
 | 20 | + * @param userSentryWebpackPluginConfig SentryWebpackPlugin options provided by the user  | 
 | 21 | + *  | 
 | 22 | + * @returns The config values next will receive directly from `withSentryConfig` or when it calls the function returned  | 
 | 23 | + * by `withSentryConfig`  | 
 | 24 | + */  | 
 | 25 | +export function materializeFinalNextConfig(  | 
 | 26 | +  exportedNextConfig: ExportedNextConfig,  | 
 | 27 | +  userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>,  | 
 | 28 | +): NextConfigObject {  | 
 | 29 | +  const sentrifiedConfig = withSentryConfig(exportedNextConfig, userSentryWebpackPluginConfig);  | 
 | 30 | +  let finalConfigValues = sentrifiedConfig;  | 
 | 31 | + | 
 | 32 | +  if (typeof sentrifiedConfig === 'function') {  | 
 | 33 | +    // for some reason TS won't recognize that `finalConfigValues` is now a NextConfigObject, which is why the cast  | 
 | 34 | +    // below is necessary  | 
 | 35 | +    finalConfigValues = sentrifiedConfig(runtimePhase, defaultsObject);  | 
 | 36 | +  }  | 
 | 37 | + | 
 | 38 | +  return finalConfigValues as NextConfigObject;  | 
 | 39 | +}  | 
 | 40 | + | 
 | 41 | +/**  | 
 | 42 | + * Derive the final values of all webpack config options, by first applying `constructWebpackConfigFunction` and then  | 
 | 43 | + * running the resulting function. Since the `entry` property of the resulting object is itself a function, also call  | 
 | 44 | + * that.  | 
 | 45 | + *  | 
 | 46 | + * @param options An object including the following:  | 
 | 47 | + *   - `exportedNextConfig` Next config options provided by the user  | 
 | 48 | + *   - `userSentryWebpackPluginConfig` SentryWebpackPlugin options provided by the user  | 
 | 49 | + *   - `incomingWebpackConfig` The existing webpack config, passed to the function as `config`  | 
 | 50 | + *   - `incomingWebpackBuildContext` The existing webpack build context, passed to the function as `options`  | 
 | 51 | + *  | 
 | 52 | + * @returns The webpack config values next will use when it calls the function that `createFinalWebpackConfig` returns  | 
 | 53 | + */  | 
 | 54 | +export async function materializeFinalWebpackConfig(options: {  | 
 | 55 | +  exportedNextConfig: ExportedNextConfig;  | 
 | 56 | +  userSentryWebpackPluginConfig?: Partial<SentryWebpackPluginOptions>;  | 
 | 57 | +  incomingWebpackConfig: WebpackConfigObject;  | 
 | 58 | +  incomingWebpackBuildContext: BuildContext;  | 
 | 59 | +}): Promise<WebpackConfigObject> {  | 
 | 60 | +  const { exportedNextConfig, userSentryWebpackPluginConfig, incomingWebpackConfig, incomingWebpackBuildContext } =  | 
 | 61 | +    options;  | 
 | 62 | + | 
 | 63 | +  // if the user's next config is a function, run it so we have access to the values  | 
 | 64 | +  const materializedUserNextConfig =  | 
 | 65 | +    typeof exportedNextConfig === 'function'  | 
 | 66 | +      ? exportedNextConfig('phase-production-build', defaultsObject)  | 
 | 67 | +      : exportedNextConfig;  | 
 | 68 | + | 
 | 69 | +  // extract the `sentry` property as we do in `withSentryConfig`  | 
 | 70 | +  const { sentry: sentryConfig } = materializedUserNextConfig;  | 
 | 71 | +  delete materializedUserNextConfig.sentry;  | 
 | 72 | + | 
 | 73 | +  // get the webpack config function we'd normally pass back to next  | 
 | 74 | +  const webpackConfigFunction = constructWebpackConfigFunction(  | 
 | 75 | +    materializedUserNextConfig,  | 
 | 76 | +    userSentryWebpackPluginConfig,  | 
 | 77 | +    sentryConfig,  | 
 | 78 | +  );  | 
 | 79 | + | 
 | 80 | +  // call it to get concrete values for comparison  | 
 | 81 | +  const finalWebpackConfigValue = webpackConfigFunction(incomingWebpackConfig, incomingWebpackBuildContext);  | 
 | 82 | +  const webpackEntryProperty = finalWebpackConfigValue.entry as EntryPropertyFunction;  | 
 | 83 | +  finalWebpackConfigValue.entry = await webpackEntryProperty();  | 
 | 84 | + | 
 | 85 | +  return finalWebpackConfigValue;  | 
 | 86 | +}  | 
 | 87 | + | 
 | 88 | +// helper function to make sure we're checking the correct plugin's data  | 
 | 89 | + | 
 | 90 | +/**  | 
 | 91 | + * Given a webpack config, find a plugin (or the plugins) with the given name.  | 
 | 92 | + *  | 
 | 93 | + * Note that this function will error if more than one instance is found, unless the `allowMultiple` flag is passed.  | 
 | 94 | + *  | 
 | 95 | + * @param webpackConfig The webpack config object  | 
 | 96 | + * @param pluginName The name of the plugin's constructor  | 
 | 97 | + * @returns The plugin instance(s), or undefined if it's not found.  | 
 | 98 | + */  | 
 | 99 | +export function findWebpackPlugin(  | 
 | 100 | +  webpackConfig: WebpackConfigObject,  | 
 | 101 | +  pluginName: string,  | 
 | 102 | +  multipleAllowed: boolean = false,  | 
 | 103 | +): WebpackPluginInstance | SentryWebpackPlugin | WebpackPluginInstance[] | SentryWebpackPlugin[] | undefined {  | 
 | 104 | +  const plugins = webpackConfig.plugins || [];  | 
 | 105 | +  const matchingPlugins = plugins.filter(plugin => plugin.constructor.name === pluginName);  | 
 | 106 | + | 
 | 107 | +  if (matchingPlugins.length > 1 && !multipleAllowed) {  | 
 | 108 | +    throw new Error(  | 
 | 109 | +      `More than one ${pluginName} instance found. Please use the \`multipleAllowed\` flag if this is intentional.\nExisting plugins: ${plugins.map(  | 
 | 110 | +        plugin => plugin.constructor.name,  | 
 | 111 | +      )}`,  | 
 | 112 | +    );  | 
 | 113 | +  }  | 
 | 114 | + | 
 | 115 | +  if (matchingPlugins.length > 0) {  | 
 | 116 | +    return multipleAllowed ? matchingPlugins : matchingPlugins[0];  | 
 | 117 | +  }  | 
 | 118 | + | 
 | 119 | +  return undefined;  | 
 | 120 | +}  | 
0 commit comments