From cbcdc0c25af3b5f5805622b2dab6cfa3c691789c Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Tue, 6 Jun 2023 14:10:35 +0200 Subject: [PATCH 1/2] feat: provide display name for split api routes --- packages/runtime/src/constants.ts | 2 + packages/runtime/src/helpers/functions.ts | 54 +++++++++++++++++++---- test/__snapshots__/index.spec.ts.snap | 12 ++--- test/helpers/functions.spec.ts | 2 +- test/index.spec.ts | 17 +++++++ 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/packages/runtime/src/constants.ts b/packages/runtime/src/constants.ts index 998f25ea34..2a9fd1d9c6 100644 --- a/packages/runtime/src/constants.ts +++ b/packages/runtime/src/constants.ts @@ -2,11 +2,13 @@ import destr from 'destr' export const HANDLER_FUNCTION_NAME = '___netlify-handler' export const ODB_FUNCTION_NAME = '___netlify-odb-handler' +export const API_FUNCTION_NAME = '___netlify-api-handler' export const IMAGE_FUNCTION_NAME = '_ipx' export const NEXT_PLUGIN_NAME = '@netlify/next-runtime' export const NEXT_PLUGIN = '@netlify/plugin-nextjs' export const HANDLER_FUNCTION_TITLE = 'Next.js SSR handler' export const ODB_FUNCTION_TITLE = 'Next.js ISR handler' +export const API_FUNCTION_TITLE = 'Next.js API handler' export const IMAGE_FUNCTION_TITLE = 'next/image handler' // These are paths in .next that shouldn't be publicly accessible export const HIDDEN_PATHS = destr(process.env.NEXT_KEEP_METADATA_FILES) diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index 283b86a9fc..92f964150f 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -16,6 +16,8 @@ import { HANDLER_FUNCTION_TITLE, ODB_FUNCTION_TITLE, IMAGE_FUNCTION_TITLE, + API_FUNCTION_TITLE, + API_FUNCTION_NAME, } from '../constants' import { getApiHandler } from '../templates/getApiHandler' import { getHandler } from '../templates/getHandler' @@ -31,6 +33,7 @@ import { getFunctionNameForPage } from './utils' export interface ApiRouteConfig { functionName: string + functionTitle?: string route: string config: ApiConfig compiled: string @@ -39,6 +42,7 @@ export interface ApiRouteConfig { export interface APILambda { functionName: string + functionTitle: string routes: ApiRouteConfig[] includedFiles: string[] type?: ApiRouteType @@ -60,7 +64,7 @@ export const generateFunctions = async ( : undefined for (const apiLambda of apiLambdas) { - const { functionName, routes, type, includedFiles } = apiLambda + const { functionName, functionTitle, routes, type, includedFiles } = apiLambda const apiHandlerSource = getApiHandler({ // most api lambdas serve multiple routes, but scheduled functions need to be in separate lambdas. @@ -102,6 +106,8 @@ export const generateFunctions = async ( }) await writeFile(join(functionsDir, functionName, 'pages.js'), resolverSource) + await writeFunctionConfiguration({ functionName, functionTitle, functionsDir }) + const nfInternalFiles = await glob(join(functionsDir, functionName, '**')) includedFiles.push(...nfInternalFiles) } @@ -128,7 +134,7 @@ export const generateFunctions = async ( join(__dirname, '..', '..', 'lib', 'templates', 'handlerUtils.js'), join(functionsDir, functionName, 'handlerUtils.js'), ) - writeFunctionConfiguration({ functionName, functionTitle, functionsDir }) + await writeFunctionConfiguration({ functionName, functionTitle, functionsDir }) } await writeHandler(HANDLER_FUNCTION_NAME, HANDLER_FUNCTION_TITLE, false) @@ -334,12 +340,41 @@ export const getAPILambdas = async ( const bins = pack(weighedRoutes, threshold) - return bins.map((bin, index) => ({ - functionName: bin.length === 1 ? bin[0].functionName : `api-${index}`, - routes: bin, - includedFiles: [...commonDependencies, ...routes.flatMap((route) => route.includedFiles)], - type, - })) + return bins.map((bin) => { + if (bin.length === 1) { + const [func] = bin + const { functionName, functionTitle, config, includedFiles } = func + return { + functionName, + functionTitle, + routes: [func], + includedFiles: [...commonDependencies, ...includedFiles], + type: config.type, + } + } + + const includedFiles = [...commonDependencies, ...bin.flatMap((route) => route.includedFiles)] + const nonSingletonBins = bins.filter((b) => b.length > 1) + if (nonSingletonBins.length === 1) { + return { + functionName: API_FUNCTION_NAME, + functionTitle: API_FUNCTION_TITLE, + includedFiles, + routes: bin, + type, + } + } + + const indexInNonSingletonBins = nonSingletonBins.indexOf(bin) + + return { + functionName: `${API_FUNCTION_NAME}-${indexInNonSingletonBins + 1}`, + functionTitle: `${API_FUNCTION_TITLE} ${indexInNonSingletonBins + 1}/${nonSingletonBins.length}`, + includedFiles, + routes: bin, + type, + } + }) } const standardFunctions = apiRoutes.filter( @@ -381,6 +416,7 @@ export const getApiRouteConfigs = async ( const config = await extractConfigFromFile(filePath, appDir) const functionName = getFunctionNameForPage(apiRoute, config.type === ApiRouteType.BACKGROUND) + const functionTitle = `${API_FUNCTION_TITLE} ${apiRoute}` const compiled = pages[apiRoute] const compiledPath = join(publish, 'server', compiled) @@ -390,6 +426,7 @@ export const getApiRouteConfigs = async ( return { functionName, + functionTitle, route: apiRoute, config, compiled, @@ -415,6 +452,7 @@ export const getExtendedApiRouteConfigs = async ( export const packSingleFunction = (func: ApiRouteConfig): APILambda => ({ functionName: func.functionName, + functionTitle: func.functionTitle, includedFiles: func.includedFiles, routes: [func], type: func.config.type, diff --git a/test/__snapshots__/index.spec.ts.snap b/test/__snapshots__/index.spec.ts.snap index a322148a33..2252736986 100644 --- a/test/__snapshots__/index.spec.ts.snap +++ b/test/__snapshots__/index.spec.ts.snap @@ -2109,17 +2109,17 @@ Array [ Object { "from": "/api/enterPreview", "status": 200, - "to": "/.netlify/functions/api-0", + "to": "/.netlify/functions/___netlify-api-handler", }, Object { "from": "/api/exitPreview", "status": 200, - "to": "/.netlify/functions/api-0", + "to": "/.netlify/functions/___netlify-api-handler", }, Object { "from": "/api/hello", "status": 200, - "to": "/.netlify/functions/api-0", + "to": "/.netlify/functions/___netlify-api-handler", }, Object { "from": "/api/hello-background", @@ -2134,17 +2134,17 @@ Array [ Object { "from": "/api/revalidate", "status": 200, - "to": "/.netlify/functions/api-0", + "to": "/.netlify/functions/___netlify-api-handler", }, Object { "from": "/api/shows/:id", "status": 200, - "to": "/.netlify/functions/api-0", + "to": "/.netlify/functions/___netlify-api-handler", }, Object { "from": "/api/shows/:params/*", "status": 200, - "to": "/.netlify/functions/api-0", + "to": "/.netlify/functions/___netlify-api-handler", }, Object { "force": false, diff --git a/test/helpers/functions.spec.ts b/test/helpers/functions.spec.ts index c2083602fb..3b68669194 100644 --- a/test/helpers/functions.spec.ts +++ b/test/helpers/functions.spec.ts @@ -70,7 +70,7 @@ describeCwdTmpDir('api route file analysis', () => { it('only shows scheduled/background functions as extended funcs', async () => { await moveNextDist() - const configs = await getExtendedApiRouteConfigs('.next', process.cwd()) + const configs = await getExtendedApiRouteConfigs('.next', process.cwd(), []) // Using a Set means the order doesn't matter expect(new Set(configs.map(({ includedFiles, ...rest }) => rest))).toEqual( new Set([ diff --git a/test/index.spec.ts b/test/index.spec.ts index 565e8428d4..038dfbde64 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -738,6 +738,23 @@ describe('onBuild()', () => { expect(netlifyConfig.functions['_api_*'].node_bundler).toEqual('nft') }) + it('provides displayname for split api routes', async () => { + await moveNextDist() + await nextRuntime.onBuild(defaultArgs) + + const functionsManifest = await readJson( + path.join('.netlify', 'functions-internal', '___netlify-api-handler', '___netlify-api-handler.json'), + ) + + expect(functionsManifest).toEqual({ + config: { + generator: '@netlify/next-runtime@unknown', + name: 'Next.js API handler', + }, + version: 1, + }) + }) + // eslint-disable-next-line jest/expect-expect it('works when `relativeAppDir` is undefined', async () => { await moveNextDist() From 37a6e3abcf83c5bd2f8b7cb8baa1e5b6b853e605 Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Wed, 7 Jun 2023 09:20:05 +0200 Subject: [PATCH 2/2] fix: functions test --- packages/runtime/src/helpers/functions.ts | 4 ++-- test/helpers/functions.spec.ts | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/runtime/src/helpers/functions.ts b/packages/runtime/src/helpers/functions.ts index 92f964150f..0bd3acd99e 100644 --- a/packages/runtime/src/helpers/functions.ts +++ b/packages/runtime/src/helpers/functions.ts @@ -401,7 +401,7 @@ export const getAPILambdas = async ( export const getApiRouteConfigs = async ( publish: string, appDir: string, - pageExtensions: string[], + pageExtensions?: string[], ): Promise> => { const pages = await readJSON(join(publish, 'server', 'pages-manifest.json')) const apiRoutes = Object.keys(pages).filter((page) => page.startsWith('/api/')) @@ -442,7 +442,7 @@ export const getApiRouteConfigs = async ( export const getExtendedApiRouteConfigs = async ( publish: string, appDir: string, - pageExtensions: string[], + pageExtensions?: string[], ): Promise> => { const settledApiRoutes = await getApiRouteConfigs(publish, appDir, pageExtensions) diff --git a/test/helpers/functions.spec.ts b/test/helpers/functions.spec.ts index 3b68669194..5dc08f66ca 100644 --- a/test/helpers/functions.spec.ts +++ b/test/helpers/functions.spec.ts @@ -4,12 +4,13 @@ import { describeCwdTmpDir, moveNextDist } from '../test-utils' describeCwdTmpDir('api route file analysis', () => { it('extracts correct route configs from source files', async () => { await moveNextDist() - const configs = await getApiRouteConfigs('.next', process.cwd(), ['js', 'jsx', 'ts', 'tsx']) + const configs = await getApiRouteConfigs('.next', process.cwd()) // Using a Set means the order doesn't matter expect(new Set(configs.map(({ includedFiles, ...rest }) => rest))).toEqual( new Set([ { functionName: '_api_og-handler', + functionTitle: 'Next.js API handler /api/og', compiled: 'pages/api/og.js', config: { runtime: 'edge', @@ -18,48 +19,56 @@ describeCwdTmpDir('api route file analysis', () => { }, { functionName: '_api_enterPreview-handler', + functionTitle: 'Next.js API handler /api/enterPreview', compiled: 'pages/api/enterPreview.js', config: {}, route: '/api/enterPreview', }, { functionName: '_api_exitPreview-handler', + functionTitle: 'Next.js API handler /api/exitPreview', compiled: 'pages/api/exitPreview.js', config: {}, route: '/api/exitPreview', }, { functionName: '_api_hello-handler', + functionTitle: 'Next.js API handler /api/hello', compiled: 'pages/api/hello.js', config: {}, route: '/api/hello', }, { functionName: '_api_shows_params-SPLAT-handler', + functionTitle: 'Next.js API handler /api/shows/[...params]', compiled: 'pages/api/shows/[...params].js', config: {}, route: '/api/shows/[...params]', }, { functionName: '_api_shows_id-PARAM-handler', + functionTitle: 'Next.js API handler /api/shows/[id]', compiled: 'pages/api/shows/[id].js', config: {}, route: '/api/shows/[id]', }, { functionName: '_api_hello-background-background', + functionTitle: 'Next.js API handler /api/hello-background', compiled: 'pages/api/hello-background.js', config: { type: 'experimental-background' }, route: '/api/hello-background', }, { functionName: '_api_hello-scheduled-handler', + functionTitle: 'Next.js API handler /api/hello-scheduled', compiled: 'pages/api/hello-scheduled.js', config: { schedule: '@hourly', type: 'experimental-scheduled' }, route: '/api/hello-scheduled', }, { functionName: '_api_revalidate-handler', + functionTitle: 'Next.js API handler /api/revalidate', compiled: 'pages/api/revalidate.js', config: {}, route: '/api/revalidate', @@ -70,18 +79,20 @@ describeCwdTmpDir('api route file analysis', () => { it('only shows scheduled/background functions as extended funcs', async () => { await moveNextDist() - const configs = await getExtendedApiRouteConfigs('.next', process.cwd(), []) + const configs = await getExtendedApiRouteConfigs('.next', process.cwd()) // Using a Set means the order doesn't matter expect(new Set(configs.map(({ includedFiles, ...rest }) => rest))).toEqual( new Set([ { functionName: '_api_hello-background-background', + functionTitle: 'Next.js API handler /api/hello-background', compiled: 'pages/api/hello-background.js', config: { type: 'experimental-background' }, route: '/api/hello-background', }, { functionName: '_api_hello-scheduled-handler', + functionTitle: 'Next.js API handler /api/hello-scheduled', compiled: 'pages/api/hello-scheduled.js', config: { schedule: '@hourly', type: 'experimental-scheduled' }, route: '/api/hello-scheduled',