From a84f73aed0e44331d1b27feb4be4e0c9cdf5efcb Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:07:53 +0100 Subject: [PATCH 1/9] feat: first work on moving ef configs that are not route-related to function_config --- node/bundler.test.ts | 23 ++++--- node/bundler.ts | 60 ++++++++++++------- node/config.test.ts | 19 ++++++ node/config.ts | 4 +- node/manifest.ts | 18 ++++-- .../edge-functions/framework-func1.ts | 3 +- .../.netlify/edge-functions/func3.ts | 4 +- 7 files changed, 93 insertions(+), 38 deletions(-) diff --git a/node/bundler.test.ts b/node/bundler.test.ts index 749679b4..06fe8193 100644 --- a/node/bundler.test.ts +++ b/node/bundler.test.ts @@ -321,7 +321,7 @@ test('Processes a function that imports a custom layer', async () => { await cleanup() }) -test('Loads declarations and import maps from the deploy configuration', async () => { +test('Loads declarations and import maps from the deploy configuration and in-source-config', async () => { const { basePath, cleanup, distPath } = await useFixture('with_deploy_config') const declarations: Declaration[] = [ { @@ -342,18 +342,25 @@ test('Loads declarations and import maps from the deploy configuration', async ( const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8') const manifest = JSON.parse(manifestFile) - const { routes, bundles, function_config: functionConfig } = manifest - + const { bundles, function_config: functionConfig } = manifest expect(bundles.length).toBe(1) expect(bundles[0].format).toBe('eszip2') expect(generatedFiles.includes(bundles[0].asset)).toBe(true) - expect(routes[0].generator).toBeUndefined() - expect(routes[1].name).toBe('Function two') - expect(routes[1].generator).toBe('@netlify/fake-plugin@1.0.0') - expect(routes[2].generator).toBe('internalFunc') // respects excludedPath from deploy config - expect(functionConfig.func2).toEqual({ excluded_patterns: ['^/func2/skip/?$'] }) + expect(functionConfig.func2).toEqual({ + excluded_patterns: ['^/func2/skip/?$'], + name: 'Function two', + generator: '@netlify/fake-plugin@1.0.0', + }) + + // respects in-source-config + expect(functionConfig.func3).toEqual({ + excluded_patterns: [], + name: 'in-config-function', + on_error: 'bypass', + generator: 'internalFunc', + }) await cleanup() }) diff --git a/node/bundler.ts b/node/bundler.ts index 9cdd49e8..752a3859 100644 --- a/node/bundler.ts +++ b/node/bundler.ts @@ -2,17 +2,15 @@ import { promises as fs } from 'fs' import { join } from 'path' import commonPathPrefix from 'common-path-prefix' -import isPathInside from 'is-path-inside' import { v4 as uuidv4 } from 'uuid' import { importMapSpecifier } from '../shared/consts.js' import { DenoBridge, DenoOptions, OnAfterDownloadHook, OnBeforeDownloadHook } from './bridge.js' import type { Bundle } from './bundle.js' -import { getFunctionConfig } from './config.js' +import { FunctionConfig, getFunctionConfig } from './config.js' import { Declaration, mergeDeclarations } from './declaration.js' import { load as loadDeployConfig } from './deploy_config.js' -import { EdgeFunction } from './edge_function.js' import { FeatureFlags, getFlags } from './feature_flags.js' import { findFunctions } from './finder.js' import { bundle as bundleESZIP } from './formats/eszip.js' @@ -128,18 +126,17 @@ const bundle = async ( // Creating a final declarations array by combining the TOML file with the // deploy configuration API and the in-source configuration. - const declarationsFromConfig = mergeDeclarations( + const declarations = mergeDeclarations( tomlDeclarations, userFunctionsWithConfig, internalFunctionsWithConfig, deployConfig.declarations, ) - // If any declarations are autogenerated and are missing the generator field - // add a default string. - const declarations = internalSrcFolder - ? declarationsFromConfig.map((declaration) => addGeneratorFieldIfMissing(declaration, functions, internalSrcFolder)) - : declarationsFromConfig + const internalFunctionConfig = createFunctionConfig({ + internalFunctionsWithConfig, + declarations, + }) const manifest = await writeManifest({ bundles: [functionBundle], @@ -148,7 +145,7 @@ const bundle = async ( featureFlags, functions, userFunctionConfig: userFunctionsWithConfig, - internalFunctionConfig: internalFunctionsWithConfig, + internalFunctionConfig, importMap: importMapSpecifier, layers: deployConfig.layers, }) @@ -186,19 +183,42 @@ const getBasePath = (sourceDirectories: string[], inputBasePath?: string) => { return commonPathPrefix(sourceDirectories) } -export const addGeneratorFieldIfMissing = ( - declaration: Declaration, - functions: EdgeFunction[], - internalFunctionsPath?: string, -) => { - const fullFuncPath = functions?.find((func) => func.name === declaration.function)?.path +interface MergeWithDeclarationConfigOptions { + name: string + config: FunctionConfig + declarations: Declaration[] +} - // If function path is in the internalFunctionsPath, we assume it is autogenerated. - const isInternal = Boolean(internalFunctionsPath && fullFuncPath && isPathInside(fullFuncPath, internalFunctionsPath)) +// Merged any old-style non-in-source configs from declaration. +const mergeWithDeclarationConfig = ({ name, config, declarations }: MergeWithDeclarationConfigOptions) => { + const declaration = declarations?.find((decl) => decl.function === name) - const generatorFallback = isInternal ? 'internalFunc' : undefined - return { ...declaration, generator: declaration.generator || generatorFallback } + return { + ...config, + name: declaration?.name || config.name, + generator: declaration?.generator || config.generator, + } } +const addGeneratorFallback = (config: FunctionConfig) => ({ + ...config, + generator: config.generator || 'internalFunc', +}) + +interface CreateFunctionConfigOptions { + internalFunctionsWithConfig: Record + declarations: Declaration[] +} + +const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }: CreateFunctionConfigOptions) => + Object.entries(internalFunctionsWithConfig).reduce((acc, [name, config]) => { + const mergedConfigFields = mergeWithDeclarationConfig({ name, config, declarations }) + + return { + ...acc, + [name]: addGeneratorFallback(mergedConfigFields), + } + }, {} as Record) + export { bundle } export type { BundleOptions } diff --git a/node/config.test.ts b/node/config.test.ts index bbe2945b..3ed45782 100644 --- a/node/config.test.ts +++ b/node/config.test.ts @@ -127,6 +127,25 @@ const functions = [ export const config = { path: "/home" } `, }, + { + testName: 'config with path, generator, name and onError`', + expectedConfig: { + path: '/home', + generator: '@netlify/fake-plugin@1.0.0', + name: 'a displayName', + onError: 'bypass', + }, + name: 'func6', + source: ` + export default async () => new Response("Hello from function three") + + export const config = { path: "/home", + generator: '@netlify/fake-plugin@1.0.0', + name: 'a displayName', + onError: 'bypass', + } + `, + }, ] describe('`getFunctionConfig` extracts configuration properties from function file', () => { test.each(functions)('$testName', async (func) => { diff --git a/node/config.ts b/node/config.ts index eebdc1e6..fe101663 100644 --- a/node/config.ts +++ b/node/config.ts @@ -29,7 +29,7 @@ export const enum Cache { export type Path = `/${string}` -export type OnError = 'fail' | 'bypass' | `/${string}` +export type OnError = 'fail' | 'bypass' | Path export const isValidOnError = (value: unknown): value is OnError => { if (typeof value === 'undefined') return true @@ -42,6 +42,8 @@ export interface FunctionConfig { path?: Path | Path[] excludedPath?: Path | Path[] onError?: OnError + name?: string + generator?: string } const getConfigExtractor = () => { diff --git a/node/manifest.ts b/node/manifest.ts index 16b929fb..86e6a5e7 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -22,6 +22,8 @@ interface Route { interface EdgeFunctionConfig { excluded_patterns: string[] on_error?: string + generator?: string + name?: string } interface Manifest { bundler_version: string @@ -54,7 +56,12 @@ const sanitizeEdgeFunctionConfig = (config: Record): const newConfig: Record = {} for (const [name, functionConfig] of Object.entries(config)) { - if (functionConfig.excluded_patterns.length !== 0 || functionConfig.on_error) { + if ( + functionConfig.excluded_patterns.length !== 0 || + functionConfig.on_error || + functionConfig.generator || + functionConfig.name + ) { newConfig[name] = functionConfig } } @@ -78,7 +85,7 @@ const generateManifest = ({ functions.map(({ name }) => [name, { excluded_patterns: [] }]), ) - for (const [name, { excludedPath, onError }] of Object.entries({ + for (const [name, { excludedPath, onError, name: displayName, generator }] of Object.entries({ ...internalFunctionConfig, ...userFunctionConfig, })) { @@ -86,7 +93,6 @@ const generateManifest = ({ if (manifestFunctionConfig[name] === undefined) { continue } - if (excludedPath) { const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath] const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern) @@ -94,9 +100,9 @@ const generateManifest = ({ manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns) } - if (onError) { - manifestFunctionConfig[name].on_error = onError - } + manifestFunctionConfig[name].on_error = onError + manifestFunctionConfig[name].name = displayName + manifestFunctionConfig[name].generator = generator } declarations.forEach((declaration) => { diff --git a/test/fixtures/with_config/.netlify/edge-functions/framework-func1.ts b/test/fixtures/with_config/.netlify/edge-functions/framework-func1.ts index 3e465202..b05d30b1 100644 --- a/test/fixtures/with_config/.netlify/edge-functions/framework-func1.ts +++ b/test/fixtures/with_config/.netlify/edge-functions/framework-func1.ts @@ -1,3 +1,4 @@ +import { IntegrationsConfig } from 'https://edge.netlify.com' import { greet } from 'alias:helper' export default async () => { @@ -6,6 +7,6 @@ export default async () => { return new Response(greeting) } -export const config = { +export const config: IntegrationsConfig = { path: '/framework-func1', } diff --git a/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts b/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts index e3e0deca..29353650 100644 --- a/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts +++ b/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts @@ -1,9 +1,9 @@ -import { Config } from 'https://edge.netlify.com' +import { IntegrationsConfig } from 'https://edge.netlify.com' export default async () => { return new Response('Hello world') } -export const config: Config = { +export const config: IntegrationsConfig = { path: '/func-3', } From 8d7a4b0f66b989958e76e29c96ff08a9adc4fdbd Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:23:28 +0100 Subject: [PATCH 2/9] feat: add comments for backwards-compatibility --- node/declaration.ts | 1 + node/manifest.ts | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/node/declaration.ts b/node/declaration.ts index 1ec1843f..0e9c4fb8 100644 --- a/node/declaration.ts +++ b/node/declaration.ts @@ -5,6 +5,7 @@ import { FunctionConfig, Path } from './config.js' interface BaseDeclaration { cache?: string function: string + // todo: remove these two after a while and only support in-source-config for non-route related configs name?: string generator?: string } diff --git a/node/manifest.ts b/node/manifest.ts index 86e6a5e7..68fea720 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -14,9 +14,7 @@ import { nonNullable } from './utils/non_nullable.js' interface Route { function: string - name?: string pattern: string - generator?: string } interface EdgeFunctionConfig { @@ -115,9 +113,7 @@ const generateManifest = ({ const pattern = getRegularExpression(declaration, featureFlags?.edge_functions_fail_unsupported_regex) const route: Route = { function: func.name, - name: declaration.name, pattern: serializePattern(pattern), - generator: declaration.generator, } const excludedPattern = getExcludedRegularExpression( declaration, From dfcbd87a991529ec28e147fa4b81d0a7cf483818 Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:50:23 +0100 Subject: [PATCH 3/9] test: fix test and refactor a few things --- node/bundler.test.ts | 1 - node/manifest.test.ts | 64 ++++++++----------- node/manifest.ts | 28 ++++---- .../.netlify/edge-functions/func3.ts | 2 + 4 files changed, 47 insertions(+), 48 deletions(-) diff --git a/node/bundler.test.ts b/node/bundler.test.ts index 06fe8193..19ebe409 100644 --- a/node/bundler.test.ts +++ b/node/bundler.test.ts @@ -356,7 +356,6 @@ test('Loads declarations and import maps from the deploy configuration and in-so // respects in-source-config expect(functionConfig.func3).toEqual({ - excluded_patterns: [], name: 'in-config-function', on_error: 'bypass', generator: 'internalFunc', diff --git a/node/manifest.test.ts b/node/manifest.test.ts index cbf2da94..1bfa4dd4 100644 --- a/node/manifest.test.ts +++ b/node/manifest.test.ts @@ -34,48 +34,39 @@ test('Generates a manifest with different bundles', () => { }) test('Generates a manifest with display names', () => { - const functions = [ - { name: 'func-1', path: '/path/to/func-1.ts' }, - { name: 'func-2', path: '/path/to/func-2.ts' }, - ] - const declarations: Declaration[] = [ - { function: 'func-1', name: 'Display Name', path: '/f1/*' }, - { function: 'func-2', path: '/f2/*' }, - ] - const manifest = generateManifest({ bundles: [], declarations, functions }) + const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }] + const declarations: Declaration[] = [{ function: 'func-1', path: '/f1/*' }] - const expectedRoutes = [ - { function: 'func-1', name: 'Display Name', pattern: '^/f1/.*/?$' }, - { function: 'func-2', pattern: '^/f2/.*/?$' }, - ] + const internalFunctionConfig: Record = { + 'func-1': { + name: 'Display Name', + }, + } + const manifest = generateManifest({ bundles: [], declarations, functions, internalFunctionConfig }) + const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/.*/?$' }] + expect(manifest.function_config).toEqual({ + 'func-1': { name: 'Display Name' }, + }) expect(manifest.routes).toEqual(expectedRoutes) expect(manifest.bundler_version).toBe(env.npm_package_version as string) }) test('Generates a manifest with a generator field', () => { - const functions = [ - { name: 'func-1', path: '/path/to/func-1.ts' }, - { name: 'func-2', path: '/path/to/func-2.ts' }, - { name: 'func-3', path: '/path/to/func-3.ts' }, - ] - - const declarations: Declaration[] = [ - { function: 'func-1', generator: '@netlify/fake-plugin@1.0.0', path: '/f1/*' }, - { function: 'func-2', path: '/f2/*' }, - { function: 'func-3', generator: '@netlify/fake-plugin@1.0.0', cache: 'manual', path: '/f3' }, - ] - const manifest = generateManifest({ bundles: [], declarations, functions }) + const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }] - const expectedRoutes = [ - { function: 'func-1', generator: '@netlify/fake-plugin@1.0.0', pattern: '^/f1/.*/?$' }, - { function: 'func-2', pattern: '^/f2/.*/?$' }, - ] - const expectedPostCacheRoutes = [{ function: 'func-3', generator: '@netlify/fake-plugin@1.0.0', pattern: '^/f3/?$' }] + const declarations: Declaration[] = [{ function: 'func-1', path: '/f1/*' }] + const internalFunctionConfig: Record = { + 'func-1': { + generator: '@netlify/fake-plugin@1.0.0', + }, + } + const manifest = generateManifest({ bundles: [], declarations, functions, internalFunctionConfig }) + const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/.*/?$' }] + const expectedFunctionConfig = { 'func-1': { generator: '@netlify/fake-plugin@1.0.0' } } expect(manifest.routes).toEqual(expectedRoutes) - expect(manifest.post_cache_routes).toEqual(expectedPostCacheRoutes) - expect(manifest.bundler_version).toBe(env.npm_package_version as string) + expect(manifest.function_config).toEqual(expectedFunctionConfig) }) test('Generates a manifest with excluded paths and patterns', () => { @@ -84,13 +75,13 @@ test('Generates a manifest with excluded paths and patterns', () => { { name: 'func-2', path: '/path/to/func-2.ts' }, ] const declarations: Declaration[] = [ - { function: 'func-1', name: 'Display Name', path: '/f1/*', excludedPath: '/f1/exclude' }, + { function: 'func-1', path: '/f1/*', excludedPath: '/f1/exclude' }, { function: 'func-2', pattern: '^/f2/.*/?$', excludedPattern: '^/f2/exclude$' }, ] const manifest = generateManifest({ bundles: [], declarations, functions }) const expectedRoutes = [ - { function: 'func-1', name: 'Display Name', pattern: '^/f1/.*/?$' }, + { function: 'func-1', pattern: '^/f1/.*/?$' }, { function: 'func-2', pattern: '^/f2/.*/?$' }, ] @@ -108,17 +99,18 @@ test('Includes failure modes in manifest', () => { { name: 'func-2', path: '/path/to/func-2.ts' }, ] const declarations: Declaration[] = [ - { function: 'func-1', name: 'Display Name', path: '/f1/*' }, + { function: 'func-1', path: '/f1/*' }, { function: 'func-2', pattern: '^/f2/.*/?$' }, ] const userFunctionConfig: Record = { 'func-1': { onError: '/custom-error', + name: 'Display Name', }, } const manifest = generateManifest({ bundles: [], declarations, functions, userFunctionConfig }) expect(manifest.function_config).toEqual({ - 'func-1': { excluded_patterns: [], on_error: '/custom-error' }, + 'func-1': { on_error: '/custom-error', name: 'Display Name' }, }) }) diff --git a/node/manifest.ts b/node/manifest.ts index 68fea720..a9519d8c 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -44,6 +44,17 @@ interface GenerateManifestOptions { userFunctionConfig?: Record } +const removeEmptyConfigValues = (functionConfig: EdgeFunctionConfig) => + Object.entries(functionConfig).reduce((acc, [key, value]) => { + if (value && !(Array.isArray(value) && value.length === 0)) { + return { ...acc, [key]: value } + } + return acc + }, {} as EdgeFunctionConfig) + +const hasAnyConfigValues = (functionConfig: EdgeFunctionConfig) => + functionConfig.excluded_patterns || functionConfig.on_error || functionConfig.generator || functionConfig.name + // JavaScript regular expressions are converted to strings with leading and // trailing slashes, so any slashes inside the expression itself are escaped // as `//`. This function deserializes that back into a single slash, which @@ -54,13 +65,10 @@ const sanitizeEdgeFunctionConfig = (config: Record): const newConfig: Record = {} for (const [name, functionConfig] of Object.entries(config)) { - if ( - functionConfig.excluded_patterns.length !== 0 || - functionConfig.on_error || - functionConfig.generator || - functionConfig.name - ) { - newConfig[name] = functionConfig + const newFunctionConfig = removeEmptyConfigValues(functionConfig) + + if (hasAnyConfigValues(newFunctionConfig)) { + newConfig[name] = newFunctionConfig } } @@ -83,7 +91,7 @@ const generateManifest = ({ functions.map(({ name }) => [name, { excluded_patterns: [] }]), ) - for (const [name, { excludedPath, onError, name: displayName, generator }] of Object.entries({ + for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries({ ...internalFunctionConfig, ...userFunctionConfig, })) { @@ -98,9 +106,7 @@ const generateManifest = ({ manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns) } - manifestFunctionConfig[name].on_error = onError - manifestFunctionConfig[name].name = displayName - manifestFunctionConfig[name].generator = generator + manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest } } declarations.forEach((declaration) => { diff --git a/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts b/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts index 29353650..99510abf 100644 --- a/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts +++ b/test/fixtures/with_deploy_config/.netlify/edge-functions/func3.ts @@ -6,4 +6,6 @@ export default async () => { export const config: IntegrationsConfig = { path: '/func-3', + name: 'in-config-function', + onError: 'bypass', } From 5a4d7dc42ac4ebf534fb2bc52aa08f4cf87ccf56 Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Wed, 22 Mar 2023 13:10:39 +0100 Subject: [PATCH 4/9] chore: fix typos en rename some things --- node/bundler.test.ts | 4 ++-- node/declaration.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node/bundler.test.ts b/node/bundler.test.ts index 19ebe409..f53b88c6 100644 --- a/node/bundler.test.ts +++ b/node/bundler.test.ts @@ -321,7 +321,7 @@ test('Processes a function that imports a custom layer', async () => { await cleanup() }) -test('Loads declarations and import maps from the deploy configuration and in-source-config', async () => { +test('Loads declarations and import maps from the deploy configuration and in-source config', async () => { const { basePath, cleanup, distPath } = await useFixture('with_deploy_config') const declarations: Declaration[] = [ { @@ -354,7 +354,7 @@ test('Loads declarations and import maps from the deploy configuration and in-so generator: '@netlify/fake-plugin@1.0.0', }) - // respects in-source-config + // respects in-source config expect(functionConfig.func3).toEqual({ name: 'in-config-function', on_error: 'bypass', diff --git a/node/declaration.ts b/node/declaration.ts index 0e9c4fb8..6153ac10 100644 --- a/node/declaration.ts +++ b/node/declaration.ts @@ -5,7 +5,7 @@ import { FunctionConfig, Path } from './config.js' interface BaseDeclaration { cache?: string function: string - // todo: remove these two after a while and only support in-source-config for non-route related configs + // todo: remove these two after a while and only support in-source config for non-route related configs name?: string generator?: string } From 48086cacf6e378aa1f8fc62c01f3071f5cdf553a Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Fri, 24 Mar 2023 12:03:23 +0100 Subject: [PATCH 5/9] feat: add failsafe for internal in-source configurations --- node/manifest.test.ts | 56 ++++++++++++++++++++++++++++++++++++++++--- node/manifest.ts | 34 +++++++++++++++++++------- 2 files changed, 78 insertions(+), 12 deletions(-) diff --git a/node/manifest.test.ts b/node/manifest.test.ts index 1bfa4dd4..4e9575a5 100644 --- a/node/manifest.test.ts +++ b/node/manifest.test.ts @@ -3,7 +3,7 @@ import { env } from 'process' import { test, expect, vi } from 'vitest' import { BundleFormat } from './bundle.js' -import { FunctionConfig } from './config.js' +import { Cache, FunctionConfig } from './config.js' import { Declaration } from './declaration.js' import { generateManifest } from './manifest.js' @@ -93,6 +93,57 @@ test('Generates a manifest with excluded paths and patterns', () => { expect(manifest.bundler_version).toBe(env.npm_package_version as string) }) +test('Filters out internal in-source configurations in user created functions', () => { + const functions = [ + { name: 'func-1', path: '/path/to/func-1.ts' }, + { name: 'func-2', path: '/path/to/func-2.ts' }, + ] + const declarations: Declaration[] = [ + { function: 'func-1', path: '/f1/*' }, + { function: 'func-2', pattern: '^/f2/.*/?$' }, + ] + const userFunctionConfig: Record = { + 'func-1': { + onError: '/custom-error', + cache: Cache.Manual, + excludedPath: '/f1/exclude', + path: '/path/to/func-1.ts', + name: 'User function', + generator: 'fake-generator', + }, + } + const internalFunctionConfig: Record = { + 'func-2': { + onError: 'bypass', + cache: Cache.Off, + excludedPath: '/f2/exclude', + path: '/path/to/func-2.ts', + name: 'Internal function', + generator: 'internal-generator', + }, + } + const manifest = generateManifest({ + bundles: [], + declarations, + functions, + userFunctionConfig, + internalFunctionConfig, + }) + expect(manifest.function_config).toEqual({ + 'func-1': { + on_error: '/custom-error', + excluded_patterns: ['^/f1/exclude/?$'], + }, + 'func-2': { + on_error: 'bypass', + cache: Cache.Off, + name: 'Internal function', + generator: 'internal-generator', + excluded_patterns: ['^/f2/exclude/?$'], + }, + }) +}) + test('Includes failure modes in manifest', () => { const functions = [ { name: 'func-1', path: '/path/to/func-1.ts' }, @@ -105,12 +156,11 @@ test('Includes failure modes in manifest', () => { const userFunctionConfig: Record = { 'func-1': { onError: '/custom-error', - name: 'Display Name', }, } const manifest = generateManifest({ bundles: [], declarations, functions, userFunctionConfig }) expect(manifest.function_config).toEqual({ - 'func-1': { on_error: '/custom-error', name: 'Display Name' }, + 'func-1': { on_error: '/custom-error' }, }) }) diff --git a/node/manifest.ts b/node/manifest.ts index a9519d8c..d35272e9 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -4,7 +4,7 @@ import { join } from 'path' import globToRegExp from 'glob-to-regexp' import type { Bundle } from './bundle.js' -import { Cache, FunctionConfig } from './config.js' +import { Cache, FunctionConfig, Path } from './config.js' import { Declaration, parsePattern } from './declaration.js' import { EdgeFunction } from './edge_function.js' import { FeatureFlags } from './feature_flags.js' @@ -75,6 +75,19 @@ const sanitizeEdgeFunctionConfig = (config: Record): return newConfig } +const addExcludedPatterns = ( + name: string, + manifestFunctionConfig: Record, + excludedPath?: Path | Path[], +) => { + if (excludedPath) { + const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath] + const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern) + + manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns) + } +} + const generateManifest = ({ bundles = [], declarations = [], @@ -91,20 +104,23 @@ const generateManifest = ({ functions.map(({ name }) => [name, { excluded_patterns: [] }]), ) - for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries({ - ...internalFunctionConfig, - ...userFunctionConfig, - })) { + for (const [name, { excludedPath, onError }] of Object.entries(userFunctionConfig)) { // If the config block is for a function that is not defined, discard it. if (manifestFunctionConfig[name] === undefined) { continue } - if (excludedPath) { - const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath] - const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern) + addExcludedPatterns(name, manifestFunctionConfig, excludedPath) - manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns) + manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError } + } + + for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) { + // If the config block is for a function that is not defined, discard it. + console.log({ rest }) + if (manifestFunctionConfig[name] === undefined) { + continue } + addExcludedPatterns(name, manifestFunctionConfig, excludedPath) manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest } } From 61170efc8897988a40dbc94c8c9201fb1bc79330 Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Fri, 24 Mar 2023 13:11:31 +0100 Subject: [PATCH 6/9] chore: make name more explicit as functionName in mergeWithDeclarationConfig --- node/bundler.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/node/bundler.ts b/node/bundler.ts index 752a3859..b7cc1ff8 100644 --- a/node/bundler.ts +++ b/node/bundler.ts @@ -184,14 +184,14 @@ const getBasePath = (sourceDirectories: string[], inputBasePath?: string) => { } interface MergeWithDeclarationConfigOptions { - name: string + functionName: string config: FunctionConfig declarations: Declaration[] } // Merged any old-style non-in-source configs from declaration. -const mergeWithDeclarationConfig = ({ name, config, declarations }: MergeWithDeclarationConfigOptions) => { - const declaration = declarations?.find((decl) => decl.function === name) +const mergeWithDeclarationConfig = ({ functionName, config, declarations }: MergeWithDeclarationConfigOptions) => { + const declaration = declarations?.find((decl) => decl.function === functionName) return { ...config, @@ -211,12 +211,12 @@ interface CreateFunctionConfigOptions { } const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }: CreateFunctionConfigOptions) => - Object.entries(internalFunctionsWithConfig).reduce((acc, [name, config]) => { - const mergedConfigFields = mergeWithDeclarationConfig({ name, config, declarations }) + Object.entries(internalFunctionsWithConfig).reduce((acc, [functionName, config]) => { + const mergedConfigFields = mergeWithDeclarationConfig({ functionName, config, declarations }) return { ...acc, - [name]: addGeneratorFallback(mergedConfigFields), + [functionName]: addGeneratorFallback(mergedConfigFields), } }, {} as Record) From 3158e8603438e889792e945f5793b563b3a99d74 Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Fri, 24 Mar 2023 13:23:48 +0100 Subject: [PATCH 7/9] chore: remove hasAnyConfigValues --- node/manifest.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/node/manifest.ts b/node/manifest.ts index d35272e9..15369c82 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -52,9 +52,6 @@ const removeEmptyConfigValues = (functionConfig: EdgeFunctionConfig) => return acc }, {} as EdgeFunctionConfig) -const hasAnyConfigValues = (functionConfig: EdgeFunctionConfig) => - functionConfig.excluded_patterns || functionConfig.on_error || functionConfig.generator || functionConfig.name - // JavaScript regular expressions are converted to strings with leading and // trailing slashes, so any slashes inside the expression itself are escaped // as `//`. This function deserializes that back into a single slash, which @@ -67,7 +64,7 @@ const sanitizeEdgeFunctionConfig = (config: Record): for (const [name, functionConfig] of Object.entries(config)) { const newFunctionConfig = removeEmptyConfigValues(functionConfig) - if (hasAnyConfigValues(newFunctionConfig)) { + if (Object.keys(functionConfig).length !== 0) { newConfig[name] = newFunctionConfig } } @@ -116,7 +113,6 @@ const generateManifest = ({ for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) { // If the config block is for a function that is not defined, discard it. - console.log({ rest }) if (manifestFunctionConfig[name] === undefined) { continue } From bbf521cc2a64c89e2719709c63febd7fc1016fa3 Mon Sep 17 00:00:00 2001 From: khen <30577427+khendrikse@users.noreply.github.com> Date: Fri, 24 Mar 2023 13:35:26 +0100 Subject: [PATCH 8/9] test: fix test --- node/manifest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/manifest.ts b/node/manifest.ts index 15369c82..491bb873 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -64,7 +64,7 @@ const sanitizeEdgeFunctionConfig = (config: Record): for (const [name, functionConfig] of Object.entries(config)) { const newFunctionConfig = removeEmptyConfigValues(functionConfig) - if (Object.keys(functionConfig).length !== 0) { + if (Object.keys(newFunctionConfig).length !== 0) { newConfig[name] = newFunctionConfig } } From 19d414701db9c073b19e9145900ee0ec9b1cd31b Mon Sep 17 00:00:00 2001 From: Karin Hendrikse <30577427+khendrikse@users.noreply.github.com> Date: Fri, 24 Mar 2023 13:36:38 +0100 Subject: [PATCH 9/9] Update node/bundler.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add better comment Co-authored-by: Eduardo Bouças --- node/bundler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/bundler.ts b/node/bundler.ts index b7cc1ff8..c23bdaa8 100644 --- a/node/bundler.ts +++ b/node/bundler.ts @@ -189,7 +189,9 @@ interface MergeWithDeclarationConfigOptions { declarations: Declaration[] } -// Merged any old-style non-in-source configs from declaration. +// We used to allow the `name` and `generator` fields to be defined at the +// declaration level. We want these properties to live at the function level +// in their config object, so we translate that for backwards-compatibility. const mergeWithDeclarationConfig = ({ functionName, config, declarations }: MergeWithDeclarationConfigOptions) => { const declaration = declarations?.find((decl) => decl.function === functionName)