diff --git a/src/lib/functions/registry.mjs b/src/lib/functions/registry.mjs index c48aa6794fd..85a563b3f7a 100644 --- a/src/lib/functions/registry.mjs +++ b/src/lib/functions/registry.mjs @@ -1,5 +1,5 @@ // @ts-check -import { mkdir } from 'fs/promises' +import { mkdir, stat } from 'fs/promises' import { createRequire } from 'module' import { basename, extname, isAbsolute, join, resolve } from 'path' import { env } from 'process' @@ -39,6 +39,7 @@ export class FunctionsRegistry { debug = false, isConnected = false, logLambdaCompat, + manifest, projectRoot, settings, timeouts, @@ -96,6 +97,14 @@ export class FunctionsRegistry { * @type {boolean} */ this.logLambdaCompat = Boolean(logLambdaCompat) + + /** + * Contents of a `manifest.json` file that can be looked up when dealing + * with built functions. + * + * @type {object} + */ + this.manifest = manifest } checkTypesPackage() { @@ -390,12 +399,30 @@ export class FunctionsRegistry { FunctionsRegistry.logEvent('extracted', { func }) } - func.mainFile = join(unzippedDirectory, `${func.name}.js`) + // If there's a manifest file, look up the function in order to extract + // the build data. + const manifestEntry = (this.manifest?.functions || []).find((manifestFunc) => manifestFunc.name === func.name) + + func.buildData = manifestEntry?.buildData || {} + + // When we look at an unzipped function, we don't know whether it uses + // the legacy entry file format (i.e. `[function name].js`) or the new + // one (i.e. `___netlify-entry-point.mjs`). Let's look for the new one + // and use it if it exists, otherwise use the old one. + try { + const v2EntryPointPath = join(unzippedDirectory, '___netlify-entry-point.mjs') + + await stat(v2EntryPointPath) + + func.mainFile = v2EntryPointPath + } catch { + func.mainFile = join(unzippedDirectory, `${func.name}.js`) + } + } else { + this.buildFunctionAndWatchFiles(func, !isReload) } this.functions.set(name, func) - - this.buildFunctionAndWatchFiles(func, !isReload) } /** diff --git a/src/lib/functions/server.mjs b/src/lib/functions/server.mjs index d39bee53397..d0eadf4762b 100644 --- a/src/lib/functions/server.mjs +++ b/src/lib/functions/server.mjs @@ -1,6 +1,7 @@ // @ts-check import { Buffer } from 'buffer' import { promises as fs } from 'fs' +import path from 'path' import express from 'express' import expressLogging from 'express-logging' @@ -261,6 +262,7 @@ export const startFunctionsServer = async (options) => { options const internalFunctionsDir = await getInternalFunctionsDir({ base: site.root }) const functionsDirectories = [] + let manifest // If the `loadDistFunctions` parameter is sent, the functions server will // use the built functions created by zip-it-and-ship-it rather than building @@ -270,6 +272,18 @@ export const startFunctionsServer = async (options) => { if (distPath) { functionsDirectories.push(distPath) + + // When using built functions, read the manifest file so that we can + // extract metadata such as routes and API version. + try { + const manifestPath = path.join(distPath, 'manifest.json') + // eslint-disable-next-line unicorn/prefer-json-parse-buffer + const data = await fs.readFile(manifestPath, 'utf8') + + manifest = JSON.parse(data) + } catch { + // no-op + } } } else { // The order of the function directories matters. Rightmost directories take @@ -297,6 +311,7 @@ export const startFunctionsServer = async (options) => { debug, isConnected: Boolean(siteUrl), logLambdaCompat: isFeatureFlagEnabled('cli_log_lambda_compat', siteInfo), + manifest, // functions always need to be inside the packagePath if set inside a monorepo projectRoot: command.workingDir, settings,