@netlify/edge-bundler
Advanced tools
Comparing version 8.12.3 to 8.13.0
import { OnAfterDownloadHook, OnBeforeDownloadHook } from './bridge.js'; | ||
import { Declaration } from './declaration.js'; | ||
import { EdgeFunction } from './edge_function.js'; | ||
import { FeatureFlags } from './feature_flags.js'; | ||
@@ -20,21 +19,6 @@ import { LogFunction } from './logger.js'; | ||
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, }?: BundleOptions) => Promise<{ | ||
functions: EdgeFunction[]; | ||
functions: import("./edge_function.js").EdgeFunction[]; | ||
manifest: import("./manifest.js").Manifest; | ||
}>; | ||
export declare const addGeneratorFieldIfMissing: (declaration: Declaration, functions: EdgeFunction[], internalFunctionsPath?: string) => { | ||
generator: string | undefined; | ||
cache?: string | undefined; | ||
function: string; | ||
name?: string | undefined; | ||
path: `/${string}`; | ||
excludedPath?: `/${string}` | undefined; | ||
} | { | ||
generator: string | undefined; | ||
cache?: string | undefined; | ||
function: string; | ||
name?: string | undefined; | ||
pattern: string; | ||
excludedPattern?: string | undefined; | ||
}; | ||
export { bundle }; | ||
export type { BundleOptions }; |
import { promises as fs } from 'fs'; | ||
import { join, resolve } from 'path'; | ||
import { join } from 'path'; | ||
import commonPathPrefix from 'common-path-prefix'; | ||
import isPathInside from 'is-path-inside'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
@@ -28,3 +27,2 @@ import { importMapSpecifier } from '../shared/consts.js'; | ||
}; | ||
const internalFunctionsPath = internalSrcFolder && resolve(internalSrcFolder); | ||
if (cacheDirectory !== undefined) { | ||
@@ -46,5 +44,8 @@ options.denoDir = join(cacheDirectory, 'deno_dir'); | ||
const externals = deployConfig.layers.map((layer) => layer.name); | ||
const userSourceDirectories = sourceDirectories.filter((dir) => dir !== internalSrcFolder); | ||
const importMap = new ImportMap(); | ||
await importMap.addFiles([deployConfig === null || deployConfig === void 0 ? void 0 : deployConfig.importMap, ...importMapPaths], logger); | ||
const functions = await findFunctions(sourceDirectories); | ||
const userFunctions = userSourceDirectories.length === 0 ? [] : await findFunctions(userSourceDirectories); | ||
const internalFunctions = internalSrcFolder ? await findFunctions([internalSrcFolder]) : []; | ||
const functions = [...internalFunctions, ...userFunctions]; | ||
const functionBundle = await bundleESZIP({ | ||
@@ -66,13 +67,15 @@ basePath, | ||
// Retrieving a configuration object for each function. | ||
const functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig(func, importMap, deno, logger, featureFlags))); | ||
// Run `getFunctionConfig` in parallel as it is a non-trivial operation and spins up deno | ||
const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig(func, importMap, deno, logger, featureFlags)]); | ||
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig(func, importMap, deno, logger, featureFlags)]); | ||
// Creating a hash of function names to configuration objects. | ||
const functionsWithConfig = functions.reduce((acc, func, index) => ({ ...acc, [func.name]: functionsConfig[index] }), {}); | ||
const internalFunctionsWithConfig = Object.fromEntries(await Promise.all(internalConfigPromises)); | ||
const userFunctionsWithConfig = Object.fromEntries(await Promise.all(userConfigPromises)); | ||
// Creating a final declarations array by combining the TOML file with the | ||
// deploy configuration API and the in-source configuration. | ||
const declarationsFromConfig = mergeDeclarations(tomlDeclarations, functionsWithConfig, deployConfig.declarations); | ||
// If any declarations are autogenerated and are missing the generator field | ||
// add a default string. | ||
const declarations = internalFunctionsPath | ||
? declarationsFromConfig.map((declaration) => addGeneratorFieldIfMissing(declaration, functions, internalFunctionsPath)) | ||
: declarationsFromConfig; | ||
const declarations = mergeDeclarations(tomlDeclarations, userFunctionsWithConfig, internalFunctionsWithConfig, deployConfig.declarations); | ||
const internalFunctionConfig = createFunctionConfig({ | ||
internalFunctionsWithConfig, | ||
declarations, | ||
}); | ||
const manifest = await writeManifest({ | ||
@@ -84,3 +87,4 @@ bundles: [functionBundle], | ||
functions, | ||
functionConfig: functionsWithConfig, | ||
userFunctionConfig: userFunctionsWithConfig, | ||
internalFunctionConfig, | ||
importMap: importMapSpecifier, | ||
@@ -114,10 +118,24 @@ layers: deployConfig.layers, | ||
}; | ||
export const addGeneratorFieldIfMissing = (declaration, functions, internalFunctionsPath) => { | ||
var _a; | ||
const fullFuncPath = (_a = functions === null || functions === void 0 ? void 0 : functions.find((func) => func.name === declaration.function)) === null || _a === void 0 ? void 0 : _a.path; | ||
// If function path is in the internalFunctionsPath, we assume it is autogenerated. | ||
const isInternal = Boolean(internalFunctionsPath && fullFuncPath && isPathInside(fullFuncPath, internalFunctionsPath)); | ||
const generatorFallback = isInternal ? 'internalFunc' : undefined; | ||
return { ...declaration, generator: declaration.generator || generatorFallback }; | ||
// 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 }) => { | ||
const declaration = declarations === null || declarations === void 0 ? void 0 : declarations.find((decl) => decl.function === functionName); | ||
return { | ||
...config, | ||
name: (declaration === null || declaration === void 0 ? void 0 : declaration.name) || config.name, | ||
generator: (declaration === null || declaration === void 0 ? void 0 : declaration.generator) || config.generator, | ||
}; | ||
}; | ||
const addGeneratorFallback = (config) => ({ | ||
...config, | ||
generator: config.generator || 'internalFunc', | ||
}); | ||
const createFunctionConfig = ({ internalFunctionsWithConfig, declarations }) => Object.entries(internalFunctionsWithConfig).reduce((acc, [functionName, config]) => { | ||
const mergedConfigFields = mergeWithDeclarationConfig({ functionName, config, declarations }); | ||
return { | ||
...acc, | ||
[functionName]: addGeneratorFallback(mergedConfigFields), | ||
}; | ||
}, {}); | ||
export { bundle }; |
@@ -267,3 +267,3 @@ import { promises as fs } from 'fs'; | ||
}); | ||
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'); | ||
@@ -287,12 +287,18 @@ const declarations = [ | ||
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({ | ||
name: 'in-config-function', | ||
on_error: 'bypass', | ||
generator: 'internalFunc', | ||
}); | ||
await cleanup(); | ||
@@ -299,0 +305,0 @@ }); |
@@ -11,3 +11,3 @@ import { DenoBridge } from './bridge.js'; | ||
export type Path = `/${string}`; | ||
export type OnError = 'fail' | 'bypass' | `/${string}`; | ||
export type OnError = 'fail' | 'bypass' | Path; | ||
export declare const isValidOnError: (value: unknown) => value is OnError; | ||
@@ -19,3 +19,5 @@ export interface FunctionConfig { | ||
onError?: OnError; | ||
name?: string; | ||
generator?: string; | ||
} | ||
export declare const getFunctionConfig: (func: EdgeFunction, importMap: ImportMap, deno: DenoBridge, log: Logger, featureFlags: FeatureFlags) => Promise<FunctionConfig>; |
@@ -120,2 +120,21 @@ import { promises as fs } from 'fs'; | ||
}, | ||
{ | ||
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', | ||
} | ||
`, | ||
}, | ||
]; | ||
@@ -122,0 +141,0 @@ describe('`getFunctionConfig` extracts configuration properties from function file', () => { |
@@ -17,4 +17,4 @@ import { FunctionConfig, Path } from './config.js'; | ||
export type Declaration = DeclarationWithPath | DeclarationWithPattern; | ||
export declare const mergeDeclarations: (tomlDeclarations: Declaration[], functionsConfig: Record<string, FunctionConfig>, deployConfigDeclarations: Declaration[]) => Declaration[]; | ||
export declare const mergeDeclarations: (tomlDeclarations: Declaration[], userFunctionsConfig: Record<string, FunctionConfig>, internalFunctionsConfig: Record<string, FunctionConfig>, deployConfigDeclarations: Declaration[]) => Declaration[]; | ||
export declare const parsePattern: (pattern: string) => string; | ||
export {}; |
import regexpAST from 'regexp-tree'; | ||
export const mergeDeclarations = (tomlDeclarations, functionsConfig, deployConfigDeclarations) => { | ||
export const mergeDeclarations = (tomlDeclarations, userFunctionsConfig, internalFunctionsConfig, deployConfigDeclarations) => { | ||
var _a; | ||
@@ -11,3 +11,3 @@ const declarations = []; | ||
for (const declaration of [...tomlDeclarations, ...deployConfigDeclarations]) { | ||
const config = functionsConfig[declaration.function]; | ||
const config = userFunctionsConfig[declaration.function] || internalFunctionsConfig[declaration.function]; | ||
if (!config) { | ||
@@ -34,4 +34,4 @@ // If no config is found, add the declaration as is. | ||
// in the TOML at all. | ||
for (const name in functionsConfig) { | ||
const { cache, path } = functionsConfig[name]; | ||
for (const name in { ...internalFunctionsConfig, ...userFunctionsConfig }) { | ||
const { cache, path } = internalFunctionsConfig[name] || userFunctionsConfig[name]; | ||
// If we have a path specified, create a declaration for each path. | ||
@@ -41,3 +41,7 @@ if (!functionsVisited.has(name) && path) { | ||
paths.forEach((singlePath) => { | ||
declarations.push({ cache, function: name, path: singlePath }); | ||
const declaration = { function: name, path: singlePath }; | ||
if (cache) { | ||
declaration.cache = cache; | ||
} | ||
declarations.push(declaration); | ||
}); | ||
@@ -44,0 +48,0 @@ } |
import { test, expect } from 'vitest'; | ||
import { mergeDeclarations } from './declaration.js'; | ||
// TODO: Add tests with the deploy config. | ||
const deployConfigDeclarations = []; | ||
test('Deploy config takes precedence over user config', () => { | ||
const deployConfigDeclarations = [ | ||
{ function: 'framework-a', path: '/path1' }, | ||
{ function: 'framework-b', path: '/path2' }, | ||
]; | ||
const tomlConfig = [ | ||
{ function: 'user-a', path: '/path1' }, | ||
{ function: 'user-b', path: '/path2' }, | ||
]; | ||
const userFuncConfig = { | ||
'user-c': { path: ['/path1', '/path2'] }, | ||
}; | ||
const internalFuncConfig = { | ||
'framework-c': { path: ['/path1', '/path2'] }, | ||
}; | ||
expect(mergeDeclarations(tomlConfig, userFuncConfig, internalFuncConfig, deployConfigDeclarations)).toMatchSnapshot(); | ||
}); | ||
test('In-source config takes precedence over netlify.toml config', () => { | ||
@@ -10,3 +26,3 @@ const tomlConfig = [ | ||
]; | ||
const funcConfig = { | ||
const userFuncConfig = { | ||
geolocation: { path: ['/geo-isc', '/*'], cache: 'manual' }, | ||
@@ -20,3 +36,3 @@ json: { path: '/json', cache: 'off' }, | ||
]; | ||
const declarations = mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations); | ||
const declarations = mergeDeclarations(tomlConfig, userFuncConfig, {}, deployConfigDeclarations); | ||
expect(declarations).toEqual(expectedDeclarations); | ||
@@ -29,3 +45,3 @@ }); | ||
]; | ||
const funcConfig = { | ||
const userFuncConfig = { | ||
geolocation: { path: ['/geo-isc'], cache: 'manual' }, | ||
@@ -38,3 +54,3 @@ json: {}, | ||
]; | ||
const declarations = mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations); | ||
const declarations = mergeDeclarations(tomlConfig, userFuncConfig, {}, deployConfigDeclarations); | ||
expect(declarations).toEqual(expectedDeclarations); | ||
@@ -56,5 +72,5 @@ }); | ||
const expectedDeclarationsWithoutISCPath = [{ function: 'geolocation', path: '/geo', cache: 'off' }]; | ||
const declarationsWithISCPath = mergeDeclarations(tomlConfig, funcConfigWithPath, deployConfigDeclarations); | ||
const declarationsWithISCPath = mergeDeclarations(tomlConfig, funcConfigWithPath, {}, deployConfigDeclarations); | ||
expect(declarationsWithISCPath).toEqual(expectedDeclarationsWithISCPath); | ||
const declarationsWithoutISCPath = mergeDeclarations(tomlConfig, funcConfigWithoutPath, deployConfigDeclarations); | ||
const declarationsWithoutISCPath = mergeDeclarations(tomlConfig, funcConfigWithoutPath, {}, deployConfigDeclarations); | ||
expect(declarationsWithoutISCPath).toEqual(expectedDeclarationsWithoutISCPath); | ||
@@ -68,3 +84,3 @@ }); | ||
const expectedDeclarations = [{ function: 'geolocation', path: '/geo', cache: 'manual' }]; | ||
expect(mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
expect(mergeDeclarations(tomlConfig, funcConfig, {}, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
}); | ||
@@ -77,3 +93,3 @@ test("In-source config path property works if it's not an array", () => { | ||
const expectedDeclarations = [{ function: 'json', path: '/json', cache: 'manual' }]; | ||
expect(mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
expect(mergeDeclarations(tomlConfig, funcConfig, {}, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
}); | ||
@@ -89,3 +105,3 @@ test("In-source config path property works if it's not an array and it's not present in toml or deploy config", () => { | ||
]; | ||
expect(mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
expect(mergeDeclarations(tomlConfig, funcConfig, {}, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
}); | ||
@@ -98,3 +114,3 @@ test('In-source config works if path property is an empty array with cache value specified', () => { | ||
const expectedDeclarations = [{ function: 'json', path: '/json-toml', cache: 'manual' }]; | ||
expect(mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
expect(mergeDeclarations(tomlConfig, funcConfig, {}, deployConfigDeclarations)).toEqual(expectedDeclarations); | ||
}); | ||
@@ -105,4 +121,4 @@ test('netlify.toml-defined excludedPath are respected', () => { | ||
const expectedDeclarations = [{ function: 'geolocation', path: '/geo/*', excludedPath: '/geo/exclude' }]; | ||
const declarations = mergeDeclarations(tomlConfig, funcConfig, deployConfigDeclarations); | ||
const declarations = mergeDeclarations(tomlConfig, funcConfig, {}, deployConfigDeclarations); | ||
expect(declarations).toEqual(expectedDeclarations); | ||
}); |
@@ -7,6 +7,16 @@ import { rm } from 'fs/promises'; | ||
import tmp from 'tmp-promise'; | ||
import { beforeEach, afterEach, test, expect } from 'vitest'; | ||
import { beforeEach, afterEach, test, expect, vi } from 'vitest'; | ||
import { fixturesDir, testLogger } from '../test/util.js'; | ||
import { download } from './downloader.js'; | ||
import { getPlatformTarget } from './platform.js'; | ||
// This changes the defaults for p-retry | ||
// minTimeout 1000 -> 10 | ||
// factor 2 -> 1 | ||
// This reduces the wait time in the tests from `2s, 4s, 8s` to `10ms, 10ms, 10ms` for 3 retries | ||
vi.mock('p-retry', async (importOriginal) => { | ||
const pRetry = (await importOriginal()); | ||
return { | ||
default: (func, options) => pRetry.default(func, { minTimeout: 10, factor: 1, ...options }), | ||
}; | ||
}); | ||
const streamError = () => { | ||
@@ -13,0 +23,0 @@ const stream = new PassThrough(); |
@@ -9,5 +9,3 @@ import type { Bundle } from './bundle.js'; | ||
function: string; | ||
name?: string; | ||
pattern: string; | ||
generator?: string; | ||
} | ||
@@ -17,2 +15,4 @@ interface EdgeFunctionConfig { | ||
on_error?: string; | ||
generator?: string; | ||
name?: string; | ||
} | ||
@@ -39,7 +39,8 @@ interface Manifest { | ||
functions: EdgeFunction[]; | ||
functionConfig?: Record<string, FunctionConfig>; | ||
importMap?: string; | ||
internalFunctionConfig?: Record<string, FunctionConfig>; | ||
layers?: Layer[]; | ||
userFunctionConfig?: Record<string, FunctionConfig>; | ||
} | ||
declare const generateManifest: ({ bundles, declarations, featureFlags, functions, functionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest; | ||
declare const generateManifest: ({ bundles, declarations, featureFlags, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest; | ||
interface WriteManifestOptions extends GenerateManifestOptions { | ||
@@ -46,0 +47,0 @@ distDirectory: string; |
@@ -7,2 +7,8 @@ import { promises as fs } from 'fs'; | ||
import { nonNullable } from './utils/non_nullable.js'; | ||
const removeEmptyConfigValues = (functionConfig) => Object.entries(functionConfig).reduce((acc, [key, value]) => { | ||
if (value && !(Array.isArray(value) && value.length === 0)) { | ||
return { ...acc, [key]: value }; | ||
} | ||
return acc; | ||
}, {}); | ||
// JavaScript regular expressions are converted to strings with leading and | ||
@@ -16,4 +22,5 @@ // trailing slashes, so any slashes inside the expression itself are escaped | ||
for (const [name, functionConfig] of Object.entries(config)) { | ||
if (functionConfig.excluded_patterns.length !== 0 || functionConfig.on_error) { | ||
newConfig[name] = functionConfig; | ||
const newFunctionConfig = removeEmptyConfigValues(functionConfig); | ||
if (Object.keys(newFunctionConfig).length !== 0) { | ||
newConfig[name] = newFunctionConfig; | ||
} | ||
@@ -23,7 +30,14 @@ } | ||
}; | ||
const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, functionConfig = {}, importMap, layers = [], }) => { | ||
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => { | ||
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 = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => { | ||
const preCacheRoutes = []; | ||
const postCacheRoutes = []; | ||
const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }])); | ||
for (const [name, { excludedPath, onError }] of Object.entries(functionConfig)) { | ||
for (const [name, { excludedPath, onError }] of Object.entries(userFunctionConfig)) { | ||
// If the config block is for a function that is not defined, discard it. | ||
@@ -33,10 +47,12 @@ if (manifestFunctionConfig[name] === undefined) { | ||
} | ||
if (excludedPath) { | ||
const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath]; | ||
const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern); | ||
manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns); | ||
addExcludedPatterns(name, manifestFunctionConfig, excludedPath); | ||
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. | ||
if (manifestFunctionConfig[name] === undefined) { | ||
continue; | ||
} | ||
if (onError) { | ||
manifestFunctionConfig[name].on_error = onError; | ||
} | ||
addExcludedPatterns(name, manifestFunctionConfig, excludedPath); | ||
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest }; | ||
} | ||
@@ -51,5 +67,3 @@ declarations.forEach((declaration) => { | ||
function: func.name, | ||
name: declaration.name, | ||
pattern: serializePattern(pattern), | ||
generator: declaration.generator, | ||
}; | ||
@@ -56,0 +70,0 @@ const excludedPattern = getExcludedRegularExpression(declaration, featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_fail_unsupported_regex); |
@@ -29,15 +29,14 @@ import { env } from 'process'; | ||
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 = [ | ||
{ function: 'func-1', name: 'Display Name', path: '/f1/*' }, | ||
{ function: 'func-2', path: '/f2/*' }, | ||
]; | ||
const manifest = generateManifest({ bundles: [], declarations, functions }); | ||
const expectedRoutes = [ | ||
{ function: 'func-1', name: 'Display Name', pattern: '^/f1/.*/?$' }, | ||
{ function: 'func-2', pattern: '^/f2/.*/?$' }, | ||
]; | ||
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]; | ||
const declarations = [{ function: 'func-1', path: '/f1/*' }]; | ||
const internalFunctionConfig = { | ||
'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); | ||
@@ -47,23 +46,37 @@ expect(manifest.bundler_version).toBe(env.npm_package_version); | ||
test('Generates a manifest with a generator field', () => { | ||
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }]; | ||
const declarations = [{ function: 'func-1', path: '/f1/*' }]; | ||
const internalFunctionConfig = { | ||
'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.function_config).toEqual(expectedFunctionConfig); | ||
}); | ||
test('Generates a manifest with excluded paths and patterns', () => { | ||
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 = [ | ||
{ 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' }, | ||
{ 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', generator: '@netlify/fake-plugin@1.0.0', pattern: '^/f1/.*/?$' }, | ||
{ function: 'func-1', pattern: '^/f1/.*/?$' }, | ||
{ function: 'func-2', pattern: '^/f2/.*/?$' }, | ||
]; | ||
const expectedPostCacheRoutes = [{ function: 'func-3', generator: '@netlify/fake-plugin@1.0.0', pattern: '^/f3/?$' }]; | ||
expect(manifest.routes).toEqual(expectedRoutes); | ||
expect(manifest.post_cache_routes).toEqual(expectedPostCacheRoutes); | ||
expect(manifest.function_config).toEqual({ | ||
'func-1': { excluded_patterns: ['^/f1/exclude/?$'] }, | ||
'func-2': { excluded_patterns: ['^/f2/exclude$'] }, | ||
}); | ||
expect(manifest.bundler_version).toBe(env.npm_package_version); | ||
}); | ||
test('Generates a manifest with excluded paths and patterns', () => { | ||
test('Filters out internal in-source configurations in user created functions', () => { | ||
const functions = [ | ||
@@ -74,16 +87,45 @@ { name: 'func-1', path: '/path/to/func-1.ts' }, | ||
const declarations = [ | ||
{ function: 'func-1', name: 'Display Name', 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', path: '/f1/*' }, | ||
{ function: 'func-2', pattern: '^/f2/.*/?$' }, | ||
]; | ||
expect(manifest.routes).toEqual(expectedRoutes); | ||
const userFunctionConfig = { | ||
'func-1': { | ||
onError: '/custom-error', | ||
cache: "manual" /* Cache.Manual */, | ||
excludedPath: '/f1/exclude', | ||
path: '/path/to/func-1.ts', | ||
name: 'User function', | ||
generator: 'fake-generator', | ||
}, | ||
}; | ||
const internalFunctionConfig = { | ||
'func-2': { | ||
onError: 'bypass', | ||
cache: "off" /* 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': { excluded_patterns: ['^/f1/exclude/?$'] }, | ||
'func-2': { excluded_patterns: ['^/f2/exclude$'] }, | ||
'func-1': { | ||
on_error: '/custom-error', | ||
excluded_patterns: ['^/f1/exclude/?$'], | ||
}, | ||
'func-2': { | ||
on_error: 'bypass', | ||
cache: "off" /* Cache.Off */, | ||
name: 'Internal function', | ||
generator: 'internal-generator', | ||
excluded_patterns: ['^/f2/exclude/?$'], | ||
}, | ||
}); | ||
expect(manifest.bundler_version).toBe(env.npm_package_version); | ||
}); | ||
@@ -96,6 +138,6 @@ test('Includes failure modes in manifest', () => { | ||
const declarations = [ | ||
{ function: 'func-1', name: 'Display Name', path: '/f1/*' }, | ||
{ function: 'func-1', path: '/f1/*' }, | ||
{ function: 'func-2', pattern: '^/f2/.*/?$' }, | ||
]; | ||
const functionConfig = { | ||
const userFunctionConfig = { | ||
'func-1': { | ||
@@ -105,5 +147,5 @@ onError: '/custom-error', | ||
}; | ||
const manifest = generateManifest({ bundles: [], declarations, functions, functionConfig }); | ||
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' }, | ||
}); | ||
@@ -110,0 +152,0 @@ }); |
{ | ||
"name": "@netlify/edge-bundler", | ||
"version": "8.12.3", | ||
"version": "8.13.0", | ||
"description": "Intelligently prepare Netlify Edge Functions for deployment", | ||
@@ -61,3 +61,3 @@ "type": "module", | ||
"@types/uuid": "^9.0.0", | ||
"@vitest/coverage-c8": "^0.29.2", | ||
"@vitest/coverage-c8": "^0.29.7", | ||
"archiver": "^5.3.1", | ||
@@ -71,3 +71,3 @@ "chalk": "^4.1.2", | ||
"typescript": "^4.5.4", | ||
"vitest": "^0.29.2" | ||
"vitest": "^0.29.7" | ||
}, | ||
@@ -74,0 +74,0 @@ "engines": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3074201
7743