diff --git a/.changeset/good-radios-sleep.md b/.changeset/good-radios-sleep.md new file mode 100644 index 00000000..33f3939e --- /dev/null +++ b/.changeset/good-radios-sleep.md @@ -0,0 +1,5 @@ +--- +"open-next": patch +--- + +Improve config validation diff --git a/packages/open-next/src/build/copyTracedFiles.ts b/packages/open-next/src/build/copyTracedFiles.ts index 22246e5f..9723424c 100644 --- a/packages/open-next/src/build/copyTracedFiles.ts +++ b/packages/open-next/src/build/copyTracedFiles.ts @@ -89,16 +89,24 @@ export async function copyTracedFiles( ), ); } catch (e) { - //TODO: add a link to the docs - throw new Error( - ` + if (existsSync(path.join(standaloneNextDir, fullFilePath))) { + //TODO: add a link to the docs + throw new Error( + ` -------------------------------------------------------------------------------- ${pagePath} cannot use the edge runtime. OpenNext requires edge runtime function to be defined in a separate function. See the docs for more information on how to bundle edge runtime functions. -------------------------------------------------------------------------------- `, - ); + ); + } else { + throw new Error(` +-------------------------------------------------------------------------------- +We cannot find the route for ${pagePath}. +File ${fullFilePath} does not exist +--------------------------------------------------------------------------------`); + } } const dir = path.dirname(fullFilePath); extractFiles( diff --git a/packages/open-next/src/build/validateConfig.ts b/packages/open-next/src/build/validateConfig.ts index 7fc2f147..b3a5dae0 100644 --- a/packages/open-next/src/build/validateConfig.ts +++ b/packages/open-next/src/build/validateConfig.ts @@ -1,26 +1,68 @@ import { FunctionOptions, + IncludedConverter, + IncludedWrapper, OpenNextConfig, SplittedFunctionOptions, } from "types/open-next"; import logger from "../logger.js"; +const compatibilityMatrix: Record = { + "aws-lambda": [ + "aws-apigw-v1", + "aws-apigw-v2", + "aws-cloudfront", + "sqs-revalidate", + ], + "aws-lambda-streaming": ["aws-apigw-v2"], + cloudflare: ["edge"], + node: ["node"], +}; + function validateFunctionOptions(fnOptions: FunctionOptions) { if (fnOptions.runtime === "edge" && fnOptions.experimentalBundledNextServer) { logger.warn( "experimentalBundledNextServer has no effect for edge functions", ); } + const wrapper = + typeof fnOptions.override?.wrapper === "string" + ? fnOptions.override.wrapper + : "aws-lambda"; + const converter = + typeof fnOptions.override?.converter === "string" + ? fnOptions.override.converter + : "aws-apigw-v2"; if ( fnOptions.override?.generateDockerfile && - fnOptions.override.converter !== "node" && - fnOptions.override.wrapper !== "node" + converter !== "node" && + wrapper !== "node" ) { logger.warn( "You've specified generateDockerfile without node converter and wrapper. Without custom converter and wrapper the dockerfile will not work", ); } + if (converter === "aws-cloudfront" && fnOptions.placement !== "global") { + logger.warn( + "You've specified aws-cloudfront converter without global placement. This may not generate the correct output", + ); + } + const isCustomWrapper = typeof fnOptions.override?.wrapper === "function"; + const isCustomConverter = typeof fnOptions.override?.converter === "function"; + // Check if the wrapper and converter are compatible + // Only check if using one of the included converters or wrapper + if ( + !compatibilityMatrix[wrapper].includes(converter) && + !isCustomWrapper && + !isCustomConverter + ) { + logger.error( + `Wrapper ${wrapper} and converter ${converter} are not compatible. For the wrapper ${wrapper} you should only use the following converters: ${compatibilityMatrix[ + wrapper + ].join(", ")}`, + ); + } } function validateSplittedFunctionOptions( @@ -31,6 +73,14 @@ function validateSplittedFunctionOptions( if (fnOptions.routes.length === 0) { throw new Error(`Splitted function ${name} must have at least one route`); } + // Check if the routes are properly formated + fnOptions.routes.forEach((route) => { + if (!route.startsWith("app/") && !route.startsWith("pages/")) { + throw new Error( + `Route ${route} in function ${name} is not a valid route. It should starts with app/ or pages/ depending on if you use page or app router`, + ); + } + }); if (fnOptions.runtime === "edge" && fnOptions.routes.length > 1) { throw new Error(`Edge function ${name} can only have one route`); } @@ -53,4 +103,11 @@ export function validateConfig(config: OpenNextConfig) { It is safe to disable if you only use page router`, ); } + validateFunctionOptions(config.imageOptimization ?? {}); + validateFunctionOptions(config.middleware ?? {}); + //@ts-expect-error - Revalidate custom wrapper type is different + validateFunctionOptions(config.revalidate ?? {}); + //@ts-expect-error - Warmer custom wrapper type is different + validateFunctionOptions(config.warmer ?? {}); + validateFunctionOptions(config.initializationFunction ?? {}); }