mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 12:57:47 +00:00
[next] support maxDuration in Next.js deployments (#10069)
Follow up PR to
8703c55f9f
which reads the newly created function config manifest and patches in
the options for resource creation.
---------
Co-authored-by: Steven <steven@ceriously.com>
This commit is contained in:
5
.changeset/khaki-ties-taste.md
Normal file
5
.changeset/khaki-ties-taste.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@vercel/next': minor
|
||||
---
|
||||
|
||||
Support maxDuration in Next.js deployments
|
||||
@@ -90,6 +90,7 @@ import {
|
||||
validateEntrypoint,
|
||||
getOperationType,
|
||||
isApiPage,
|
||||
getFunctionsConfigManifest,
|
||||
} from './utils';
|
||||
|
||||
export const version = 2;
|
||||
@@ -272,7 +273,7 @@ export const build: BuildV2 = async ({
|
||||
});
|
||||
|
||||
let hasLegacyRoutes = false;
|
||||
const hasFunctionsConfig = !!config.functions;
|
||||
const hasFunctionsConfig = Boolean(config.functions);
|
||||
|
||||
if (await pathExists(dotNextStatic)) {
|
||||
console.warn('WARNING: You should not upload the `.next` directory.');
|
||||
@@ -487,7 +488,12 @@ export const build: BuildV2 = async ({
|
||||
? await getRequiredServerFilesManifest(entryPath, outputDirectory)
|
||||
: false;
|
||||
|
||||
isServerMode = !!requiredServerFilesManifest;
|
||||
isServerMode = Boolean(requiredServerFilesManifest);
|
||||
|
||||
const functionsConfigManifest = await getFunctionsConfigManifest(
|
||||
entryPath,
|
||||
outputDirectory
|
||||
);
|
||||
|
||||
const routesManifest = await getRoutesManifest(
|
||||
entryPath,
|
||||
@@ -1312,6 +1318,7 @@ export const build: BuildV2 = async ({
|
||||
|
||||
return serverBuild({
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
nextVersion,
|
||||
trailingSlash,
|
||||
appPathRoutesManifest,
|
||||
@@ -1570,6 +1577,7 @@ export const build: BuildV2 = async ({
|
||||
const initialPageLambdaGroups = await getPageLambdaGroups({
|
||||
entryPath,
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
pages: nonApiPages,
|
||||
prerenderRoutes: new Set(),
|
||||
pageTraces,
|
||||
@@ -1586,6 +1594,7 @@ export const build: BuildV2 = async ({
|
||||
const initialApiLambdaGroups = await getPageLambdaGroups({
|
||||
entryPath,
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
pages: apiPages,
|
||||
prerenderRoutes: new Set(),
|
||||
pageTraces,
|
||||
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
getFilesMapFromReasons,
|
||||
UnwrapPromise,
|
||||
getOperationType,
|
||||
FunctionsConfigManifestV1,
|
||||
} from './utils';
|
||||
import {
|
||||
nodeFileTrace,
|
||||
@@ -66,6 +67,7 @@ export async function serverBuild({
|
||||
dynamicPages,
|
||||
pagesDir,
|
||||
config = {},
|
||||
functionsConfigManifest,
|
||||
privateOutputs,
|
||||
baseDir,
|
||||
workPath,
|
||||
@@ -105,6 +107,7 @@ export async function serverBuild({
|
||||
dynamicPages: string[];
|
||||
trailingSlash: boolean;
|
||||
config: Config;
|
||||
functionsConfigManifest?: FunctionsConfigManifestV1;
|
||||
pagesDir: string;
|
||||
baseDir: string;
|
||||
canUsePreviewMode: boolean;
|
||||
@@ -752,6 +755,7 @@ export async function serverBuild({
|
||||
const pageLambdaGroups = await getPageLambdaGroups({
|
||||
entryPath: projectDir,
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
pages: nonApiPages,
|
||||
prerenderRoutes,
|
||||
pageTraces,
|
||||
@@ -767,6 +771,7 @@ export async function serverBuild({
|
||||
const appRouterLambdaGroups = await getPageLambdaGroups({
|
||||
entryPath: projectDir,
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
pages: appRouterPages,
|
||||
prerenderRoutes,
|
||||
pageTraces,
|
||||
@@ -789,6 +794,7 @@ export async function serverBuild({
|
||||
const apiLambdaGroups = await getPageLambdaGroups({
|
||||
entryPath: projectDir,
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
pages: apiPages,
|
||||
prerenderRoutes,
|
||||
pageTraces,
|
||||
|
||||
@@ -1397,6 +1397,7 @@ const LAMBDA_RESERVED_COMPRESSED_SIZE = 250 * KIB;
|
||||
export async function getPageLambdaGroups({
|
||||
entryPath,
|
||||
config,
|
||||
functionsConfigManifest,
|
||||
pages,
|
||||
prerenderRoutes,
|
||||
pageTraces,
|
||||
@@ -1410,6 +1411,7 @@ export async function getPageLambdaGroups({
|
||||
}: {
|
||||
entryPath: string;
|
||||
config: Config;
|
||||
functionsConfigManifest?: FunctionsConfigManifestV1;
|
||||
pages: string[];
|
||||
prerenderRoutes: Set<string>;
|
||||
pageTraces: {
|
||||
@@ -1436,16 +1438,26 @@ export async function getPageLambdaGroups({
|
||||
|
||||
let opts: { memory?: number; maxDuration?: number } = {};
|
||||
|
||||
if (
|
||||
functionsConfigManifest &&
|
||||
functionsConfigManifest.functions[routeName]
|
||||
) {
|
||||
opts = functionsConfigManifest.functions[routeName];
|
||||
}
|
||||
|
||||
if (config && config.functions) {
|
||||
const sourceFile = await getSourceFilePathFromPage({
|
||||
workPath: entryPath,
|
||||
page,
|
||||
pageExtensions,
|
||||
});
|
||||
opts = await getLambdaOptionsFromFunction({
|
||||
|
||||
const vercelConfigOpts = await getLambdaOptionsFromFunction({
|
||||
sourceFile,
|
||||
config,
|
||||
});
|
||||
|
||||
opts = { ...vercelConfigOpts, ...opts };
|
||||
}
|
||||
|
||||
let matchingGroup = groups.find(group => {
|
||||
@@ -2388,6 +2400,16 @@ export {
|
||||
getSourceFilePathFromPage,
|
||||
};
|
||||
|
||||
export type FunctionsConfigManifestV1 = {
|
||||
version: 1;
|
||||
functions: Record<
|
||||
string,
|
||||
{
|
||||
maxDuration?: number;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2;
|
||||
|
||||
interface MiddlewareManifestV1 {
|
||||
@@ -2731,6 +2753,37 @@ export async function getMiddlewareBundle({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read the functions config manifest from the pre-defined
|
||||
* location. If the manifest can't be found it will resolve to
|
||||
* undefined.
|
||||
*/
|
||||
export async function getFunctionsConfigManifest(
|
||||
entryPath: string,
|
||||
outputDirectory: string
|
||||
): Promise<FunctionsConfigManifestV1 | undefined> {
|
||||
const functionConfigManifestPath = path.join(
|
||||
entryPath,
|
||||
outputDirectory,
|
||||
'./server/functions-config-manifest.json'
|
||||
);
|
||||
|
||||
const hasManifest = await fs
|
||||
.access(functionConfigManifestPath)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!hasManifest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const manifest: FunctionsConfigManifestV1 = await fs.readJSON(
|
||||
functionConfigManifestPath
|
||||
);
|
||||
|
||||
return manifest.version === 1 ? manifest : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read the middleware manifest from the pre-defined
|
||||
* location. If the manifest can't be found it will resolve to
|
||||
|
||||
6
packages/next/test/fixtures/00-app-dir-segment-options/app/api/hello-again/route.js
vendored
Normal file
6
packages/next/test/fixtures/00-app-dir-segment-options/app/api/hello-again/route.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export const GET = req => {
|
||||
console.log(req.url);
|
||||
return new Response('hello world');
|
||||
};
|
||||
|
||||
export const maxDuration = 7;
|
||||
12
packages/next/test/fixtures/00-app-dir-segment-options/index.test.js
vendored
Normal file
12
packages/next/test/fixtures/00-app-dir-segment-options/index.test.js
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/* eslint-env jest */
|
||||
const path = require('path');
|
||||
const { deployAndTest } = require('../../utils');
|
||||
|
||||
const ctx = {};
|
||||
|
||||
describe(`${__dirname.split(path.sep).pop()}`, () => {
|
||||
it('should deploy and pass probe checks', async () => {
|
||||
const info = await deployAndTest(__dirname);
|
||||
Object.assign(ctx, info);
|
||||
});
|
||||
});
|
||||
1
packages/next/test/fixtures/00-app-dir-segment-options/next.config.js
vendored
Normal file
1
packages/next/test/fixtures/00-app-dir-segment-options/next.config.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
8
packages/next/test/fixtures/00-app-dir-segment-options/package.json
vendored
Normal file
8
packages/next/test/fixtures/00-app-dir-segment-options/package.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "experimental",
|
||||
"react-dom": "experimental"
|
||||
},
|
||||
"ignoreNextjsUpdates": true
|
||||
}
|
||||
5
packages/next/test/fixtures/00-app-dir-segment-options/pages/api/hello.js
vendored
Normal file
5
packages/next/test/fixtures/00-app-dir-segment-options/pages/api/hello.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function handler(req, res) {
|
||||
return res.json({ hello: 'world' });
|
||||
}
|
||||
|
||||
export const maxDuration = 7;
|
||||
8
packages/next/test/fixtures/00-app-dir-segment-options/vercel.json
vendored
Normal file
8
packages/next/test/fixtures/00-app-dir-segment-options/vercel.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"builds": [
|
||||
{
|
||||
"src": "package.json",
|
||||
"use": "@vercel/next"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -122,6 +122,40 @@ if (parseInt(process.versions.node.split('.')[0], 10) >= 16) {
|
||||
// );
|
||||
});
|
||||
|
||||
it('should build with app-dir with segment options correctly', async () => {
|
||||
const { buildResult } = await runBuildLambda(
|
||||
path.join(__dirname, '../fixtures/00-app-dir-segment-options')
|
||||
);
|
||||
|
||||
const lambdas = new Set();
|
||||
|
||||
for (const key of Object.keys(buildResult.output)) {
|
||||
if (buildResult.output[key].type === 'Lambda') {
|
||||
lambdas.add(buildResult.output[key]);
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
buildResult.routes.some(
|
||||
route =>
|
||||
route.src?.includes('_next/data') && route.src?.includes('.rsc')
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
expect(lambdas.size).toBe(2);
|
||||
|
||||
expect(buildResult.output['api/hello']).toBeDefined();
|
||||
expect(buildResult.output['api/hello'].type).toBe('Lambda');
|
||||
expect(buildResult.output['api/hello'].maxDuration).toBe(7);
|
||||
|
||||
expect(buildResult.output['api/hello-again']).toBeDefined();
|
||||
expect(buildResult.output['api/hello-again'].type).toBe('Lambda');
|
||||
expect(buildResult.output['api/hello-again'].maxDuration).toBe(7);
|
||||
expect(
|
||||
buildResult.output['api/hello-again'].supportsResponseStreaming
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should build with app-dir in edge runtime correctly', async () => {
|
||||
const { buildResult } = await runBuildLambda(
|
||||
path.join(__dirname, '../fixtures/00-app-dir-edge')
|
||||
|
||||
Reference in New Issue
Block a user