Skip to content

Commit 2e264e3

Browse files
feat: accept mixed paths in listFunctions (#6537)
* refactor: move paths logic to separate file * feat: update signature of `listFunctions` * chore: fix test helper * chore: add test * feat: export type
1 parent a84d525 commit 2e264e3

File tree

6 files changed

+139
-93
lines changed

6 files changed

+139
-93
lines changed

packages/build/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { buildSite } from './core/main.js'
22
export { NetlifyPluginConstants } from './core/constants.js'
33

4+
export type { GeneratedFunction } from './steps/return_values.js'
45
// export the legacy types
56
export type { NetlifyPlugin } from './types/netlify_plugin.js'
67
export type { NetlifyPluginOptions } from './types/netlify_plugin_options.js'

packages/zip-it-and-ship-it/src/main.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { extname } from 'path'
33
import { Config } from './config.js'
44
import { FeatureFlags, getFlags } from './feature_flags.js'
55
import { FunctionSource } from './function.js'
6+
import { type MixedPaths, getFunctionsBag } from './paths.js'
67
import { getFunctionFromPath, getFunctionsFromPaths } from './runtimes/index.js'
78
import { parseFile, StaticAnalysisResult } from './runtimes/node/in_source_config/index.js'
89
import { ModuleFormat } from './runtimes/node/utils/module_format.js'
@@ -12,7 +13,8 @@ import { listFunctionsDirectories, resolveFunctionsDirectories } from './utils/f
1213
import type { ExtendedRoute, Route } from './utils/routes.js'
1314

1415
export { Config, FunctionConfig } from './config.js'
15-
export { type FunctionsBag, zipFunction, zipFunctions, ZipFunctionOptions, ZipFunctionsOptions } from './zip.js'
16+
export { zipFunction, zipFunctions, ZipFunctionOptions, ZipFunctionsOptions } from './zip.js'
17+
export type { FunctionsBag } from './paths.js'
1618

1719
export { ArchiveFormat, ARCHIVE_FORMAT } from './archive.js'
1820
export type { TrafficRules } from './rate_limit.js'
@@ -68,14 +70,20 @@ interface ListFunctionsOptions {
6870

6971
// List all Netlify Functions main entry files for a specific directory.
7072
export const listFunctions = async function (
71-
relativeSrcFolders: string | string[],
73+
input: MixedPaths,
7274
{ featureFlags: inputFeatureFlags, config, configFileDirectories, parseISC = false }: ListFunctionsOptions = {},
7375
) {
7476
const featureFlags = getFlags(inputFeatureFlags)
75-
const srcFolders = resolveFunctionsDirectories(relativeSrcFolders)
77+
const bag = getFunctionsBag(input)
78+
const srcFolders = resolveFunctionsDirectories([...bag.generated.directories, ...bag.user.directories])
7679
const paths = await listFunctionsDirectories(srcFolders)
7780
const cache = new RuntimeCache()
78-
const functionsMap = await getFunctionsFromPaths(paths, { cache, config, configFileDirectories, featureFlags })
81+
const functionsMap = await getFunctionsFromPaths([...paths, ...bag.generated.functions, ...bag.user.functions], {
82+
cache,
83+
config,
84+
configFileDirectories,
85+
featureFlags,
86+
})
7987
const functions = [...functionsMap.values()]
8088
const augmentedFunctions = parseISC
8189
? await Promise.all(functions.map((func) => augmentWithStaticAnalysis(func)))
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
export interface FunctionsBag {
2+
generated: FunctionsCategory
3+
user: FunctionsCategory
4+
}
5+
6+
export interface FunctionsCategory {
7+
/**
8+
* List of paths for directories containing one or more functions. Entries in
9+
* these directories are considered functions when they are files that match
10+
* one of the supported extensions or when they are sub-directories that
11+
* contain a function following the sub-directory naming patterns.
12+
* Paths can be relative.
13+
*/
14+
directories: string[]
15+
16+
/**
17+
* List of paths for specific functions. Paths can be files that match one
18+
* of the supported extensions or sub-directories that contain a function
19+
* following the sub-directory naming patterns. Paths can be relative.
20+
*/
21+
functions: string[]
22+
}
23+
24+
export type MixedPaths =
25+
| string
26+
| string[]
27+
| {
28+
/**
29+
* Functions generated on behalf of the user by a build plugin, extension
30+
* or a framework.
31+
*/
32+
generated?: Partial<FunctionsCategory>
33+
34+
/**
35+
* Functions authored by the user.
36+
*/
37+
user?: Partial<FunctionsCategory>
38+
}
39+
40+
/**
41+
* Normalizes the `zipFunctions` input into a `FunctionsBag` object.
42+
*/
43+
export const getFunctionsBag = (input: MixedPaths): FunctionsBag => {
44+
if (typeof input === 'string') {
45+
return {
46+
generated: {
47+
directories: [],
48+
functions: [],
49+
},
50+
user: {
51+
directories: [input],
52+
functions: [],
53+
},
54+
}
55+
}
56+
57+
if (Array.isArray(input)) {
58+
return {
59+
generated: {
60+
directories: [],
61+
functions: [],
62+
},
63+
user: {
64+
directories: input,
65+
functions: [],
66+
},
67+
}
68+
}
69+
70+
return {
71+
generated: {
72+
directories: input.generated?.directories ?? [],
73+
functions: input.generated?.functions ?? [],
74+
},
75+
user: {
76+
directories: input.user?.directories ?? [],
77+
functions: input.user?.functions ?? [],
78+
},
79+
}
80+
}

packages/zip-it-and-ship-it/src/zip.ts

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { FunctionSource } from './function.js'
1111
import { createManifest } from './manifest.js'
1212
import { getFunctionsFromPaths } from './runtimes/index.js'
1313
import { MODULE_FORMAT } from './runtimes/node/utils/module_format.js'
14+
import { type MixedPaths, getFunctionsBag } from './paths.js'
1415
import { addArchiveSize } from './utils/archive_size.js'
1516
import { RuntimeCache } from './utils/cache.js'
1617
import { formatZipResult, FunctionResult } from './utils/format_result.js'
@@ -45,91 +46,10 @@ const validateArchiveFormat = (archiveFormat: ArchiveFormat) => {
4546
}
4647
}
4748

48-
export interface FunctionsBag {
49-
generated: FunctionsCategory
50-
user: FunctionsCategory
51-
}
52-
53-
export interface FunctionsCategory {
54-
/**
55-
* List of paths for directories containing one or more functions. Entries in
56-
* these directories are considered functions when they are files that match
57-
* one of the supported extensions or when they are sub-directories that
58-
* contain a function following the sub-directory naming patterns.
59-
* Paths can be relative.
60-
*/
61-
directories: string[]
62-
63-
/**
64-
* List of paths for specific functions. Paths can be files that match one
65-
* of the supported extensions or sub-directories that contain a function
66-
* following the sub-directory naming patterns. Paths can be relative.
67-
*/
68-
functions: string[]
69-
}
70-
71-
/**
72-
* Normalizes the `zipFunctions` input into a `FunctionsBag` object.
73-
*/
74-
export const getFunctionsBag = (input: ZipFunctionsPaths): FunctionsBag => {
75-
if (typeof input === 'string') {
76-
return {
77-
generated: {
78-
directories: [],
79-
functions: [],
80-
},
81-
user: {
82-
directories: [input],
83-
functions: [],
84-
},
85-
}
86-
}
87-
88-
if (Array.isArray(input)) {
89-
return {
90-
generated: {
91-
directories: [],
92-
functions: [],
93-
},
94-
user: {
95-
directories: input,
96-
functions: [],
97-
},
98-
}
99-
}
100-
101-
return {
102-
generated: {
103-
directories: input.generated?.directories ?? [],
104-
functions: input.generated?.functions ?? [],
105-
},
106-
user: {
107-
directories: input.user?.directories ?? [],
108-
functions: input.user?.functions ?? [],
109-
},
110-
}
111-
}
112-
113-
export type ZipFunctionsPaths =
114-
| string
115-
| string[]
116-
| {
117-
/**
118-
* Functions generated on behalf of the user by a build plugin, extension
119-
* or a framework.
120-
*/
121-
generated?: Partial<FunctionsCategory>
122-
123-
/**
124-
* Functions authored by the user.
125-
*/
126-
user?: Partial<FunctionsCategory>
127-
}
128-
12949
// Zip `srcFolder/*` (Node.js or Go files) to `destFolder/*.zip` so it can be
13050
// used by AWS Lambda
13151
export const zipFunctions = async function (
132-
input: ZipFunctionsPaths,
52+
input: MixedPaths,
13353
destFolder: string,
13454
{
13555
archiveFormat = ARCHIVE_FORMAT.ZIP,

packages/zip-it-and-ship-it/tests/helpers/main.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import { afterAll, expect } from 'vitest'
1111
import type { Config } from '../../src/config.js'
1212
import { ListedFunction, zipFunctions } from '../../src/main.js'
1313
import { listImports } from '../../src/runtimes/node/bundlers/zisi/list_imports.js'
14+
import { getFunctionsBag, type MixedPaths } from '../../src/paths.js'
1415
import type { FunctionResult } from '../../src/utils/format_result.js'
15-
import { getFunctionsBag, ZipFunctionsOptions, type ZipFunctionsPaths } from '../../src/zip.js'
16+
import { ZipFunctionsOptions } from '../../src/zip.js'
1617

1718
export const FIXTURES_DIR = fileURLToPath(new URL('../fixtures', import.meta.url))
1819
export const FIXTURES_ESM_DIR = fileURLToPath(new URL('../fixtures-esm', import.meta.url))
@@ -49,10 +50,7 @@ afterAll(async () => {
4950
cleanupDirectories = []
5051
})
5152

52-
export const zipNode = async function (
53-
fixture: ZipFunctionsPaths,
54-
zipOptions: ZipOptions = {},
55-
): Promise<ZipNodeReturn> {
53+
export const zipNode = async function (fixture: MixedPaths, zipOptions: ZipOptions = {}): Promise<ZipNodeReturn> {
5654
const { files, tmpDir } = await zipFixture(fixture, zipOptions)
5755
const { archiveFormat } = zipOptions.opts || {}
5856

@@ -67,7 +65,7 @@ export const getBundlerNameFromOptions = ({ config = {} }: { config?: Config })
6765
config['*'] && config['*'].nodeBundler
6866

6967
export const zipFixture = async function (
70-
fixture: ZipFunctionsPaths,
68+
fixture: MixedPaths,
7169
{ length, fixtureDir, opts = {} }: ZipOptions = {},
7270
): Promise<ZipReturn> {
7371
const bundlerString = getBundlerNameFromOptions(opts) || 'default'
@@ -100,7 +98,7 @@ export const getFunctionResultsByName = (files: FunctionResult[]): Record<string
10098
}
10199

102100
export const zipCheckFunctions = async function (
103-
fixture: ZipFunctionsPaths,
101+
fixture: MixedPaths,
104102
{ length = 1, fixtureDir = FIXTURES_DIR, tmpDir, opts = {} }: ZipOptions & { tmpDir: string },
105103
): Promise<ZipReturn> {
106104
const bag = getFunctionsBag(fixture)

packages/zip-it-and-ship-it/tests/list_functions.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,44 @@ describe('listFunctions', () => {
105105

106106
expect(normalizeFiles(fixtureDir, func)).toMatchSnapshot()
107107
})
108+
109+
test('Accepts an object with mixed paths', async () => {
110+
const basePath = join(FIXTURES_ESM_DIR, 'v2-api-isolated')
111+
const functions = await listFunctions(
112+
{
113+
generated: {
114+
functions: [
115+
join(basePath, '.netlify/plugins/node_modules/extension-buildhooks/functions/extension-func1.mjs'),
116+
join(basePath, '.netlify/plugins/node_modules/extension-buildhooks/functions/extension-func2'),
117+
],
118+
},
119+
user: {
120+
directories: [join(basePath, 'netlify/functions')],
121+
},
122+
},
123+
{
124+
basePath,
125+
parseISC: true,
126+
},
127+
)
128+
129+
expect(functions.length).toBe(3)
130+
131+
const userFunc1 = normalizeFiles(basePath, functions[0])
132+
expect(userFunc1.name).toBe('user-func1')
133+
expect(userFunc1.mainFile).toBe('netlify/functions/user-func1.mjs')
134+
135+
const extensionFunc1 = normalizeFiles(basePath, functions[1])
136+
expect(extensionFunc1.name).toBe('extension-func1')
137+
expect(extensionFunc1.mainFile).toBe(
138+
'.netlify/plugins/node_modules/extension-buildhooks/functions/extension-func1.mjs',
139+
)
140+
141+
const extensionFunc2 = normalizeFiles(basePath, functions[2])
142+
expect(extensionFunc2.name).toBe('extension-func2')
143+
expect(extensionFunc2.mainFile).toBe(
144+
'.netlify/plugins/node_modules/extension-buildhooks/functions/extension-func2/index.mjs',
145+
)
146+
})
108147
})
109148
})

0 commit comments

Comments
 (0)