Skip to content

Commit 7c7238f

Browse files
feat: load generated functions from Netlify Build (#7408)
#### Summary Loads functions generated by the new `functions.generate` util added in netlify/build#6487 and netlify/build#6525. It depends on netlify/build#6539, so tests will fail until that lands.
1 parent d717fbc commit 7c7238f

File tree

9 files changed

+81
-46
lines changed

9 files changed

+81
-46
lines changed

src/commands/dev/dev.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
191191

192192
log(`${NETLIFYDEVLOG} Setting up local dev server`)
193193

194-
const { configMutations, configPath: configPathOverride } = await runDevTimeline({
194+
const { configMutations, generatedFunctions } = await runDevTimeline({
195195
command,
196196
options,
197197
settings,
@@ -208,7 +208,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
208208
blobsContext,
209209
command,
210210
config: mutatedConfig,
211-
211+
generatedFunctions,
212212
debug: options.debug,
213213
settings,
214214
site,
@@ -247,7 +247,6 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
247247
blobsContext,
248248
command,
249249
config: mutatedConfig,
250-
configPath: configPathOverride,
251250
debug: options.debug,
252251
disableEdgeFunctions: options.internalDisableEdgeFunctions,
253252
projectDir: command.workingDir,

src/commands/functions/functions-serve.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const functionsServe = async (options: OptionValues, command: BaseCommand
6161
siteUrl,
6262
capabilities,
6363
timeouts,
64+
generatedFunctions: [],
6465
geolocationMode: options.geo,
6566
geoCountry: options.country,
6667
offline: options.offline,

src/commands/serve/serve.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export const serve = async (options: OptionValues, command: BaseCommand) => {
110110
// which is what build plugins use.
111111
process.env[BLOBS_CONTEXT_VARIABLE] = encodeBlobsContext(await getBlobsContextWithAPIAccess(blobsOptions))
112112

113-
const { configPath: configPathOverride } = await runBuildTimeline({
113+
const { configPath: configPathOverride, generatedFunctions } = await runBuildTimeline({
114114
command,
115115
settings,
116116
options,
@@ -130,6 +130,7 @@ export const serve = async (options: OptionValues, command: BaseCommand) => {
130130
command,
131131
config: mergedConfig,
132132
debug: options.debug,
133+
generatedFunctions,
133134
loadDistFunctions: true,
134135
settings,
135136
site,

src/lib/functions/netlify-function.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ export default class NetlifyFunction<BuildResult extends BaseBuildResult> {
5656
public readonly runtime: Runtime<BuildResult>
5757
public schedule?: string
5858

59+
// The path to the function boundary: if the function is in a sub-directory,
60+
// this will hold the path to the sub-directory; if not, it's the path to the
61+
// main file.
62+
public readonly srcPath: string
63+
5964
// Determines whether this is a background function based on the function
6065
// name.
6166
public readonly isBackground: boolean
@@ -78,6 +83,7 @@ export default class NetlifyFunction<BuildResult extends BaseBuildResult> {
7883
projectRoot,
7984
runtime,
8085
settings,
86+
srcPath,
8187
timeoutBackground,
8288
timeoutSynchronous,
8389
}: {
@@ -91,6 +97,7 @@ export default class NetlifyFunction<BuildResult extends BaseBuildResult> {
9197
runtime: Runtime<BuildResult>
9298
// TODO(serhalp): This is confusing. Refactor to accept entire settings or rename or something?
9399
settings: Pick<ServerSettings, 'functions' | 'functionsPort'>
100+
srcPath: string
94101
timeoutBackground?: number
95102
timeoutSynchronous?: number
96103
}) {
@@ -105,6 +112,7 @@ export default class NetlifyFunction<BuildResult extends BaseBuildResult> {
105112
this.timeoutBackground = timeoutBackground
106113
this.timeoutSynchronous = timeoutSynchronous
107114
this.settings = settings
115+
this.srcPath = srcPath
108116

109117
this.isBackground = name.endsWith(BACKGROUND)
110118

src/lib/functions/registry.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createRequire } from 'module'
33
import { basename, extname, isAbsolute, join, resolve } from 'path'
44
import { env } from 'process'
55

6+
import type { GeneratedFunction } from '@netlify/build'
67
import { type ListedFunction, listFunctions, type Manifest } from '@netlify/zip-it-and-ship-it'
78
import { type MemoizeCache, watchDebounced } from '@netlify/dev-utils'
89
import extractZip from 'extract-zip'
@@ -73,6 +74,7 @@ export class FunctionsRegistry {
7374
private config: NormalizedCachedConfigConfig
7475
private debug: boolean
7576
private frameworksAPIPaths: ReturnType<typeof getFrameworksAPIPaths>
77+
private generatedFunctions: GeneratedFunction[]
7678
private isConnected: boolean
7779
private logLambdaCompat: boolean
7880
private manifest?: Manifest
@@ -87,6 +89,7 @@ export class FunctionsRegistry {
8789
config,
8890
debug = false,
8991
frameworksAPIPaths,
92+
generatedFunctions,
9093
isConnected = false,
9194
logLambdaCompat,
9295
manifest,
@@ -102,6 +105,7 @@ export class FunctionsRegistry {
102105
config: NormalizedCachedConfigConfig
103106
debug?: boolean
104107
frameworksAPIPaths: ReturnType<typeof getFrameworksAPIPaths>
108+
generatedFunctions: GeneratedFunction[]
105109
isConnected?: boolean
106110
logLambdaCompat: boolean
107111
manifest?: Manifest
@@ -114,6 +118,7 @@ export class FunctionsRegistry {
114118
this.config = config
115119
this.debug = debug
116120
this.frameworksAPIPaths = frameworksAPIPaths
121+
this.generatedFunctions = generatedFunctions ?? []
117122
this.isConnected = isConnected
118123
this.projectRoot = projectRoot
119124
this.timeouts = timeouts
@@ -464,14 +469,32 @@ export class FunctionsRegistry {
464469

465470
await Promise.all(directories.map((path) => FunctionsRegistry.prepareDirectory(path)))
466471

467-
const functions = await this.listFunctions(directories, {
468-
featureFlags: {
469-
buildRustSource: env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true',
472+
const functions = await this.listFunctions(
473+
{
474+
generated: {
475+
functions: this.generatedFunctions.map((func) => func.path),
476+
},
477+
user: {
478+
// In reality, `directories` contains both directories with user and
479+
// generated functions. The registry currently lacks knowledge about
480+
// the contents of each directory, so we put them in the same bag and
481+
// rely on the order of the directories to get the priority right.
482+
// But now that zip-it-and-ship-it accepts an object with mixed paths
483+
// that lets us specify exactly which paths contain user functions or
484+
// generated functions, we should refactor this call so it encodes
485+
// that distinction.
486+
directories,
487+
},
470488
},
471-
configFileDirectories: [getPathInProject([INTERNAL_FUNCTIONS_FOLDER])],
472-
// @ts-expect-error -- TODO(serhalp): Function config types do not match. Investigate and fix.
473-
config: this.config.functions,
474-
})
489+
{
490+
featureFlags: {
491+
buildRustSource: env.NETLIFY_EXPERIMENTAL_BUILD_RUST_SOURCE === 'true',
492+
},
493+
configFileDirectories: [getPathInProject([INTERNAL_FUNCTIONS_FOLDER])],
494+
// @ts-expect-error -- TODO(serhalp): Function config types do not match. Investigate and fix.
495+
config: this.config.functions,
496+
},
497+
)
475498

476499
// user-defined functions take precedence over internal functions,
477500
// so we want to ignore any internal functions where there's a user-defined one with the same name
@@ -505,7 +528,7 @@ export class FunctionsRegistry {
505528
// zip-it-and-ship-it returns an array sorted based on which extension should have precedence,
506529
// where the last ones precede the previous ones. This is why
507530
// we reverse the array so we get the right functions precedence in the CLI.
508-
functions.reverse().map(async ({ displayName, mainFile, name, runtime: runtimeName }) => {
531+
functions.reverse().map(async ({ displayName, mainFile, name, runtime: runtimeName, srcPath }) => {
509532
if (ignoredFunctions.has(name)) {
510533
return
511534
}
@@ -526,7 +549,6 @@ export class FunctionsRegistry {
526549
const func = new NetlifyFunction({
527550
blobsContext: this.blobsContext,
528551
config: this.config,
529-
directory: directories.find((directory) => mainFile.startsWith(directory)),
530552
mainFile,
531553
name,
532554
displayName,
@@ -538,6 +560,7 @@ export class FunctionsRegistry {
538560
timeoutBackground: this.timeouts.backgroundFunctions,
539561
timeoutSynchronous: this.timeouts.syncFunctions,
540562
settings: this.settings,
563+
srcPath,
541564
})
542565

543566
// If a function we're registering was also unregistered in this run,

src/lib/functions/runtimes/js/builders/zisi.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ const addFunctionsConfigDefaults = (config: NormalizedFunctionsConfig) => ({
3939
const buildFunction = async ({
4040
cache,
4141
config,
42-
directory,
4342
featureFlags,
4443
func,
4544
hasTypeModule,
@@ -48,7 +47,6 @@ const buildFunction = async ({
4847
}: {
4948
cache: MemoizeCache<FunctionResult>
5049
config: NormalizedFunctionsConfig
51-
directory?: string | undefined
5250
featureFlags: FeatureFlags
5351
// This seems like it should be `ZisiBuildResult` but it's technically referenced from `detectZisiBuilder` so TS
5452
// can't know at that point that we'll only end up calling it with a `ZisiBuildResult`... Consider refactoring?
@@ -63,16 +61,6 @@ const buildFunction = async ({
6361
config,
6462
featureFlags: { ...featureFlags, zisi_functions_api_v2: true },
6563
}
66-
const functionDirectory = path.dirname(func.mainFile)
67-
68-
// If we have a function at `functions/my-func/index.js` and we pass
69-
// that path to `zipFunction`, it will lack the context of the whole
70-
// functions directory and will infer the name of the function to be
71-
// `index`, not `my-func`. Instead, we need to pass the directory of
72-
// the function. The exception is when the function is a file at the
73-
// root of the functions directory (e.g. `functions/my-func.js`). In
74-
// this case, we use `mainFile` as the function path of `zipFunction`.
75-
const entryPath = functionDirectory === directory ? func.mainFile : functionDirectory
7664
const {
7765
entryFilename,
7866
excludedRoutes,
@@ -86,9 +74,9 @@ const buildFunction = async ({
8674
schedule,
8775
} = await memoize({
8876
cache,
89-
cacheKey: `zisi-${entryPath}`,
77+
cacheKey: `zisi-${func.srcPath}`,
9078
command: async () => {
91-
const result = await zipFunction(entryPath, targetDirectory, zipOptions)
79+
const result = await zipFunction(func.srcPath, targetDirectory, zipOptions)
9280
if (result == null) {
9381
throw new Error('Failed to build function')
9482
}
@@ -179,7 +167,6 @@ const netlifyConfigToZisiConfig = ({
179167

180168
export default async function detectZisiBuilder({
181169
config,
182-
directory,
183170
errorExit,
184171
func,
185172
metadata,
@@ -233,7 +220,6 @@ export default async function detectZisiBuilder({
233220
buildFunction({
234221
cache,
235222
config: functionsConfig,
236-
directory,
237223
func,
238224
projectRoot,
239225
targetDirectory,

src/lib/functions/runtimes/js/index.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { createConnection } from 'net'
2-
import { dirname } from 'path'
32
import { pathToFileURL } from 'url'
43
import { Worker } from 'worker_threads'
54

@@ -30,25 +29,19 @@ lambdaLocal.getLogger().level = 'alert'
3029

3130
export async function getBuildFunction({
3231
config,
33-
directory,
3432
errorExit,
3533
func,
3634
projectRoot,
3735
}: Parameters<GetBuildFunction<JsBuildResult>>[0]) {
3836
const metadata = await getFunctionMetadata({ mainFile: func.mainFile, config, projectRoot })
39-
const zisiBuilder = await detectZisiBuilder({ config, directory, errorExit, func, metadata, projectRoot })
37+
const zisiBuilder = await detectZisiBuilder({ config, errorExit, func, metadata, projectRoot })
4038

4139
if (zisiBuilder) {
4240
return zisiBuilder.build
4341
}
4442

45-
// If there's no function builder, we create a simple one on-the-fly which
46-
// returns as `srcFiles` the function directory, if there is one, or its
47-
// main file otherwise.
48-
const functionDirectory = dirname(func.mainFile)
49-
const srcFiles = functionDirectory === directory ? [func.mainFile] : [functionDirectory]
50-
51-
const build: BuildFunction<JsBuildResult> = () => Promise.resolve({ schedule: metadata?.schedule, srcFiles })
43+
const build: BuildFunction<JsBuildResult> = () =>
44+
Promise.resolve({ schedule: metadata?.schedule, srcFiles: [func.srcPath] })
5245
return build
5346
}
5447

src/lib/functions/server.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { promises as fs } from 'fs'
33
import type { IncomingHttpHeaders } from 'http'
44
import path from 'path'
55

6+
import type { GeneratedFunction } from '@netlify/build'
67
import { shouldBase64Encode } from '@netlify/dev-utils'
78
import express, { type Request, type RequestHandler } from 'express'
89
import expressLogging from 'express-logging'
@@ -305,6 +306,7 @@ export const startFunctionsServer = async (
305306
backgroundFunctions?: boolean
306307
}
307308
debug: boolean
309+
generatedFunctions: GeneratedFunction[]
308310
loadDistFunctions?: boolean
309311
// TODO(serhalp): This is confusing. Refactor to accept entire settings or rename or something?
310312
settings: Pick<ServerSettings, 'functions' | 'functionsPort'>
@@ -319,6 +321,7 @@ export const startFunctionsServer = async (
319321
command,
320322
config,
321323
debug,
324+
generatedFunctions,
322325
loadDistFunctions,
323326
settings,
324327
site,
@@ -380,6 +383,7 @@ export const startFunctionsServer = async (
380383
config,
381384
debug,
382385
frameworksAPIPaths: command.netlify.frameworksAPIPaths,
386+
generatedFunctions,
383387
isConnected: Boolean(siteUrl),
384388
logLambdaCompat: isFeatureFlagEnabled('cli_log_lambda_compat', siteInfo),
385389
manifest,

0 commit comments

Comments
 (0)