mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-10 21:07:48 +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,
|
validateEntrypoint,
|
||||||
getOperationType,
|
getOperationType,
|
||||||
isApiPage,
|
isApiPage,
|
||||||
|
getFunctionsConfigManifest,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
export const version = 2;
|
export const version = 2;
|
||||||
@@ -272,7 +273,7 @@ export const build: BuildV2 = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let hasLegacyRoutes = false;
|
let hasLegacyRoutes = false;
|
||||||
const hasFunctionsConfig = !!config.functions;
|
const hasFunctionsConfig = Boolean(config.functions);
|
||||||
|
|
||||||
if (await pathExists(dotNextStatic)) {
|
if (await pathExists(dotNextStatic)) {
|
||||||
console.warn('WARNING: You should not upload the `.next` directory.');
|
console.warn('WARNING: You should not upload the `.next` directory.');
|
||||||
@@ -487,7 +488,12 @@ export const build: BuildV2 = async ({
|
|||||||
? await getRequiredServerFilesManifest(entryPath, outputDirectory)
|
? await getRequiredServerFilesManifest(entryPath, outputDirectory)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
isServerMode = !!requiredServerFilesManifest;
|
isServerMode = Boolean(requiredServerFilesManifest);
|
||||||
|
|
||||||
|
const functionsConfigManifest = await getFunctionsConfigManifest(
|
||||||
|
entryPath,
|
||||||
|
outputDirectory
|
||||||
|
);
|
||||||
|
|
||||||
const routesManifest = await getRoutesManifest(
|
const routesManifest = await getRoutesManifest(
|
||||||
entryPath,
|
entryPath,
|
||||||
@@ -1312,6 +1318,7 @@ export const build: BuildV2 = async ({
|
|||||||
|
|
||||||
return serverBuild({
|
return serverBuild({
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
nextVersion,
|
nextVersion,
|
||||||
trailingSlash,
|
trailingSlash,
|
||||||
appPathRoutesManifest,
|
appPathRoutesManifest,
|
||||||
@@ -1570,6 +1577,7 @@ export const build: BuildV2 = async ({
|
|||||||
const initialPageLambdaGroups = await getPageLambdaGroups({
|
const initialPageLambdaGroups = await getPageLambdaGroups({
|
||||||
entryPath,
|
entryPath,
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
pages: nonApiPages,
|
pages: nonApiPages,
|
||||||
prerenderRoutes: new Set(),
|
prerenderRoutes: new Set(),
|
||||||
pageTraces,
|
pageTraces,
|
||||||
@@ -1586,6 +1594,7 @@ export const build: BuildV2 = async ({
|
|||||||
const initialApiLambdaGroups = await getPageLambdaGroups({
|
const initialApiLambdaGroups = await getPageLambdaGroups({
|
||||||
entryPath,
|
entryPath,
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
pages: apiPages,
|
pages: apiPages,
|
||||||
prerenderRoutes: new Set(),
|
prerenderRoutes: new Set(),
|
||||||
pageTraces,
|
pageTraces,
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
getFilesMapFromReasons,
|
getFilesMapFromReasons,
|
||||||
UnwrapPromise,
|
UnwrapPromise,
|
||||||
getOperationType,
|
getOperationType,
|
||||||
|
FunctionsConfigManifestV1,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import {
|
import {
|
||||||
nodeFileTrace,
|
nodeFileTrace,
|
||||||
@@ -66,6 +67,7 @@ export async function serverBuild({
|
|||||||
dynamicPages,
|
dynamicPages,
|
||||||
pagesDir,
|
pagesDir,
|
||||||
config = {},
|
config = {},
|
||||||
|
functionsConfigManifest,
|
||||||
privateOutputs,
|
privateOutputs,
|
||||||
baseDir,
|
baseDir,
|
||||||
workPath,
|
workPath,
|
||||||
@@ -105,6 +107,7 @@ export async function serverBuild({
|
|||||||
dynamicPages: string[];
|
dynamicPages: string[];
|
||||||
trailingSlash: boolean;
|
trailingSlash: boolean;
|
||||||
config: Config;
|
config: Config;
|
||||||
|
functionsConfigManifest?: FunctionsConfigManifestV1;
|
||||||
pagesDir: string;
|
pagesDir: string;
|
||||||
baseDir: string;
|
baseDir: string;
|
||||||
canUsePreviewMode: boolean;
|
canUsePreviewMode: boolean;
|
||||||
@@ -752,6 +755,7 @@ export async function serverBuild({
|
|||||||
const pageLambdaGroups = await getPageLambdaGroups({
|
const pageLambdaGroups = await getPageLambdaGroups({
|
||||||
entryPath: projectDir,
|
entryPath: projectDir,
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
pages: nonApiPages,
|
pages: nonApiPages,
|
||||||
prerenderRoutes,
|
prerenderRoutes,
|
||||||
pageTraces,
|
pageTraces,
|
||||||
@@ -767,6 +771,7 @@ export async function serverBuild({
|
|||||||
const appRouterLambdaGroups = await getPageLambdaGroups({
|
const appRouterLambdaGroups = await getPageLambdaGroups({
|
||||||
entryPath: projectDir,
|
entryPath: projectDir,
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
pages: appRouterPages,
|
pages: appRouterPages,
|
||||||
prerenderRoutes,
|
prerenderRoutes,
|
||||||
pageTraces,
|
pageTraces,
|
||||||
@@ -789,6 +794,7 @@ export async function serverBuild({
|
|||||||
const apiLambdaGroups = await getPageLambdaGroups({
|
const apiLambdaGroups = await getPageLambdaGroups({
|
||||||
entryPath: projectDir,
|
entryPath: projectDir,
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
pages: apiPages,
|
pages: apiPages,
|
||||||
prerenderRoutes,
|
prerenderRoutes,
|
||||||
pageTraces,
|
pageTraces,
|
||||||
|
|||||||
@@ -1397,6 +1397,7 @@ const LAMBDA_RESERVED_COMPRESSED_SIZE = 250 * KIB;
|
|||||||
export async function getPageLambdaGroups({
|
export async function getPageLambdaGroups({
|
||||||
entryPath,
|
entryPath,
|
||||||
config,
|
config,
|
||||||
|
functionsConfigManifest,
|
||||||
pages,
|
pages,
|
||||||
prerenderRoutes,
|
prerenderRoutes,
|
||||||
pageTraces,
|
pageTraces,
|
||||||
@@ -1410,6 +1411,7 @@ export async function getPageLambdaGroups({
|
|||||||
}: {
|
}: {
|
||||||
entryPath: string;
|
entryPath: string;
|
||||||
config: Config;
|
config: Config;
|
||||||
|
functionsConfigManifest?: FunctionsConfigManifestV1;
|
||||||
pages: string[];
|
pages: string[];
|
||||||
prerenderRoutes: Set<string>;
|
prerenderRoutes: Set<string>;
|
||||||
pageTraces: {
|
pageTraces: {
|
||||||
@@ -1436,16 +1438,26 @@ export async function getPageLambdaGroups({
|
|||||||
|
|
||||||
let opts: { memory?: number; maxDuration?: number } = {};
|
let opts: { memory?: number; maxDuration?: number } = {};
|
||||||
|
|
||||||
|
if (
|
||||||
|
functionsConfigManifest &&
|
||||||
|
functionsConfigManifest.functions[routeName]
|
||||||
|
) {
|
||||||
|
opts = functionsConfigManifest.functions[routeName];
|
||||||
|
}
|
||||||
|
|
||||||
if (config && config.functions) {
|
if (config && config.functions) {
|
||||||
const sourceFile = await getSourceFilePathFromPage({
|
const sourceFile = await getSourceFilePathFromPage({
|
||||||
workPath: entryPath,
|
workPath: entryPath,
|
||||||
page,
|
page,
|
||||||
pageExtensions,
|
pageExtensions,
|
||||||
});
|
});
|
||||||
opts = await getLambdaOptionsFromFunction({
|
|
||||||
|
const vercelConfigOpts = await getLambdaOptionsFromFunction({
|
||||||
sourceFile,
|
sourceFile,
|
||||||
config,
|
config,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
opts = { ...vercelConfigOpts, ...opts };
|
||||||
}
|
}
|
||||||
|
|
||||||
let matchingGroup = groups.find(group => {
|
let matchingGroup = groups.find(group => {
|
||||||
@@ -2388,6 +2400,16 @@ export {
|
|||||||
getSourceFilePathFromPage,
|
getSourceFilePathFromPage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FunctionsConfigManifestV1 = {
|
||||||
|
version: 1;
|
||||||
|
functions: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
maxDuration?: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2;
|
type MiddlewareManifest = MiddlewareManifestV1 | MiddlewareManifestV2;
|
||||||
|
|
||||||
interface MiddlewareManifestV1 {
|
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
|
* Attempts to read the middleware manifest from the pre-defined
|
||||||
* location. If the manifest can't be found it will resolve to
|
* 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 () => {
|
it('should build with app-dir in edge runtime correctly', async () => {
|
||||||
const { buildResult } = await runBuildLambda(
|
const { buildResult } = await runBuildLambda(
|
||||||
path.join(__dirname, '../fixtures/00-app-dir-edge')
|
path.join(__dirname, '../fixtures/00-app-dir-edge')
|
||||||
|
|||||||
Reference in New Issue
Block a user