@netlify/edge-bundler
Advanced tools
Comparing version 8.18.0 to 8.19.0
@@ -1,4 +0,5 @@ | ||
export const PUBLIC_SPECIFIER = 'netlify:edge' | ||
export const LEGACY_PUBLIC_SPECIFIER = 'netlify:edge' | ||
export const PUBLIC_SPECIFIER = '@netlify/edge-functions' | ||
export const STAGE1_SPECIFIER = 'netlify:bootstrap-stage1' | ||
export const STAGE2_SPECIFIER = 'netlify:bootstrap-stage2' | ||
export const virtualRoot = 'file:///root/' |
@@ -7,3 +7,3 @@ import { build, LoadResponse } from 'https://deno.land/x/eszip@v0.40.0/mod.ts' | ||
import { importMapSpecifier, virtualRoot } from '../../shared/consts.ts' | ||
import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts' | ||
import { LEGACY_PUBLIC_SPECIFIER, PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts' | ||
import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts' | ||
@@ -79,3 +79,8 @@ | ||
if (specifier === PUBLIC_SPECIFIER || externals.has(specifier) || specifier.startsWith('node:')) { | ||
if ( | ||
specifier === LEGACY_PUBLIC_SPECIFIER || | ||
specifier === PUBLIC_SPECIFIER || | ||
externals.has(specifier) || | ||
specifier.startsWith('node:') | ||
) { | ||
return { | ||
@@ -82,0 +87,0 @@ kind: 'external', |
@@ -9,2 +9,3 @@ import { DenoBridge } from './bridge.js'; | ||
} | ||
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'; | ||
export type Path = `/${string}`; | ||
@@ -20,2 +21,3 @@ export type OnError = 'fail' | 'bypass' | Path; | ||
generator?: string; | ||
method?: HTTPMethod | HTTPMethod[]; | ||
} | ||
@@ -22,0 +24,0 @@ export declare const getFunctionConfig: ({ func, importMap, deno, bootstrapURL, log, }: { |
@@ -154,2 +154,3 @@ import { promises as fs } from 'fs'; | ||
path: '/user-func2', | ||
method: ['PATCH'], | ||
}, | ||
@@ -160,3 +161,2 @@ ]; | ||
configPath: join(internalDirectory, 'config.json'), | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -184,2 +184,3 @@ const generatedFiles = await fs.readdir(distPath); | ||
path: '/user-func2', | ||
methods: ['PATCH'], | ||
}); | ||
@@ -209,2 +210,3 @@ expect(routes[2]).toEqual({ | ||
path: '/user-func5/*', | ||
methods: ['GET'], | ||
}); | ||
@@ -217,2 +219,3 @@ expect(postCacheRoutes.length).toBe(1); | ||
path: '/user-func4', | ||
methods: ['POST', 'PUT'], | ||
}); | ||
@@ -219,0 +222,0 @@ expect(Object.keys(functionConfig)).toHaveLength(1); |
@@ -1,2 +0,2 @@ | ||
import { FunctionConfig, Path } from './config.js'; | ||
import { FunctionConfig, HTTPMethod, Path } from './config.js'; | ||
import { FeatureFlags } from './feature_flags.js'; | ||
@@ -6,2 +6,3 @@ interface BaseDeclaration { | ||
function: string; | ||
method?: HTTPMethod | HTTPMethod[]; | ||
name?: string; | ||
@@ -8,0 +9,0 @@ generator?: string; |
@@ -51,3 +51,3 @@ import regexpAST from 'regexp-tree'; | ||
for (const name in functionConfigs) { | ||
const { cache, path } = functionConfigs[name]; | ||
const { cache, path, method } = functionConfigs[name]; | ||
// If we have a path specified, create a declaration for each path. | ||
@@ -61,2 +61,5 @@ if (!functionsVisited.has(name) && path) { | ||
} | ||
if (method) { | ||
declaration.method = method; | ||
} | ||
declarations.push(declaration); | ||
@@ -63,0 +66,0 @@ }); |
declare const defaultFlags: { | ||
edge_functions_fail_unsupported_regex: boolean; | ||
edge_functions_path_urlpattern: boolean; | ||
}; | ||
@@ -9,5 +8,4 @@ type FeatureFlag = keyof typeof defaultFlags; | ||
edge_functions_fail_unsupported_regex: boolean; | ||
edge_functions_path_urlpattern: boolean; | ||
}) => FeatureFlags; | ||
export { defaultFlags, getFlags }; | ||
export type { FeatureFlag, FeatureFlags }; |
const defaultFlags = { | ||
edge_functions_fail_unsupported_regex: false, | ||
edge_functions_path_urlpattern: false, | ||
}; | ||
@@ -5,0 +4,0 @@ const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({ |
@@ -8,2 +8,3 @@ import { Buffer } from 'buffer'; | ||
const INTERNAL_IMPORTS = { | ||
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts', | ||
'netlify:edge': 'https://edge.netlify.com/v1/index.ts', | ||
@@ -10,0 +11,0 @@ }; |
@@ -72,2 +72,3 @@ import { promises as fs } from 'fs'; | ||
specifier1: 'file:///some/full/path/file.js', | ||
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts', | ||
'netlify:edge': 'https://edge.netlify.com/v1/index.ts', | ||
@@ -94,2 +95,3 @@ }); | ||
specifier1: 'file:///root/full/path/file.js', | ||
'@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts', | ||
'netlify:edge': 'https://edge.netlify.com/v1/index.ts', | ||
@@ -96,0 +98,0 @@ }); |
@@ -12,2 +12,3 @@ import type { Bundle } from './bundle.js'; | ||
path?: string; | ||
methods?: string[]; | ||
} | ||
@@ -14,0 +15,0 @@ interface EdgeFunctionConfig { |
import { promises as fs } from 'fs'; | ||
import { join } from 'path'; | ||
import globToRegExp from 'glob-to-regexp'; | ||
import { wrapBundleError } from './bundle_error.js'; | ||
@@ -30,9 +29,22 @@ import { parsePattern } from './declaration.js'; | ||
}; | ||
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath, featureFlags) => { | ||
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => { | ||
if (excludedPath) { | ||
const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath]; | ||
const excludedPatterns = paths.map((path) => pathToRegularExpression(path, featureFlags)).map(serializePattern); | ||
const excludedPatterns = paths.map((path) => pathToRegularExpression(path)).map(serializePattern); | ||
manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns); | ||
} | ||
}; | ||
/** | ||
* Normalizes method names into arrays of uppercase strings. | ||
* (e.g. "get" becomes ["GET"]) | ||
*/ | ||
const normalizeMethods = (method, name) => { | ||
const methods = Array.isArray(method) ? method : [method]; | ||
return methods.map((method) => { | ||
if (typeof method !== 'string') { | ||
throw new TypeError(`Could not parse method declaration of function '${name}'. Expecting HTTP Method, got ${method}`); | ||
} | ||
return method.toUpperCase(); | ||
}); | ||
}; | ||
const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => { | ||
@@ -47,3 +59,3 @@ const preCacheRoutes = []; | ||
} | ||
addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags); | ||
addExcludedPatterns(name, manifestFunctionConfig, excludedPath); | ||
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError }; | ||
@@ -56,3 +68,3 @@ } | ||
} | ||
addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags); | ||
addExcludedPatterns(name, manifestFunctionConfig, excludedPath); | ||
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest }; | ||
@@ -72,2 +84,5 @@ } | ||
}; | ||
if ('method' in declaration) { | ||
route.methods = normalizeMethods(declaration.method, func.name); | ||
} | ||
if ('path' in declaration) { | ||
@@ -98,27 +113,17 @@ route.path = declaration.path; | ||
}; | ||
const pathToRegularExpression = (path, featureFlags) => { | ||
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_path_urlpattern) { | ||
try { | ||
const pattern = new ExtendedURLPattern({ pathname: path }); | ||
// Removing the `^` and `$` delimiters because we'll need to modify what's | ||
// between them. | ||
const source = pattern.regexp.pathname.source.slice(1, -1); | ||
// Wrapping the expression source with `^` and `$`. Also, adding an optional | ||
// trailing slash, so that a declaration of `path: "/foo"` matches requests | ||
// for both `/foo` and `/foo/`. | ||
const normalizedSource = `^${source}\\/?$`; | ||
return normalizedSource; | ||
} | ||
catch (error) { | ||
throw wrapBundleError(error); | ||
} | ||
const pathToRegularExpression = (path) => { | ||
try { | ||
const pattern = new ExtendedURLPattern({ pathname: path }); | ||
// Removing the `^` and `$` delimiters because we'll need to modify what's | ||
// between them. | ||
const source = pattern.regexp.pathname.source.slice(1, -1); | ||
// Wrapping the expression source with `^` and `$`. Also, adding an optional | ||
// trailing slash, so that a declaration of `path: "/foo"` matches requests | ||
// for both `/foo` and `/foo/`. | ||
const normalizedSource = `^${source}\\/?$`; | ||
return normalizedSource; | ||
} | ||
// We use the global flag so that `globToRegExp` will not wrap the expression | ||
// with `^` and `$`. We'll do that ourselves. | ||
const regularExpression = globToRegExp(path, { flags: 'g' }); | ||
// Wrapping the expression source with `^` and `$`. Also, adding an optional | ||
// trailing slash, so that a declaration of `path: "/foo"` matches requests | ||
// for both `/foo` and `/foo/`. | ||
const normalizedSource = `^${regularExpression.source}\\/?$`; | ||
return normalizedSource; | ||
catch (error) { | ||
throw wrapBundleError(error); | ||
} | ||
}; | ||
@@ -139,3 +144,3 @@ const getRegularExpression = (declaration, featureFlags) => { | ||
} | ||
return pathToRegularExpression(declaration.path, featureFlags); | ||
return pathToRegularExpression(declaration.path); | ||
}; | ||
@@ -162,3 +167,3 @@ const getExcludedRegularExpressions = (declaration, featureFlags) => { | ||
const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath]; | ||
return paths.map((path) => pathToRegularExpression(path, featureFlags)); | ||
return paths.map((path) => pathToRegularExpression(path)); | ||
} | ||
@@ -165,0 +170,0 @@ return []; |
@@ -43,3 +43,2 @@ import { env } from 'process'; | ||
internalFunctionConfig, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -66,3 +65,2 @@ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }]; | ||
internalFunctionConfig, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -90,3 +88,2 @@ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }]; | ||
functions, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -125,3 +122,2 @@ const expectedRoutes = [ | ||
userFunctionConfig, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -203,3 +199,2 @@ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }]; | ||
internalFunctionConfig, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -243,3 +238,2 @@ expect(manifest.routes).toEqual([ | ||
internalFunctionConfig, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -268,3 +262,2 @@ expect(manifest.routes).toEqual([ | ||
internalFunctionConfig, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
})).toThrowError(BundleError); | ||
@@ -391,3 +384,2 @@ }); | ||
functions, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -399,3 +391,2 @@ const manifest2 = generateManifest({ | ||
layers, | ||
featureFlags: { edge_functions_path_urlpattern: true }, | ||
}); | ||
@@ -402,0 +393,0 @@ expect(manifest1.routes).toEqual(expectedRoutes); |
@@ -53,2 +53,9 @@ declare const edgeManifestSchema: { | ||
}; | ||
methods: { | ||
type: string; | ||
items: { | ||
type: string; | ||
enum: string[]; | ||
}; | ||
}; | ||
}; | ||
@@ -89,2 +96,9 @@ additionalProperties: boolean; | ||
}; | ||
methods: { | ||
type: string; | ||
items: { | ||
type: string; | ||
enum: string[]; | ||
}; | ||
}; | ||
}; | ||
@@ -91,0 +105,0 @@ additionalProperties: boolean; |
@@ -32,2 +32,6 @@ const bundlesSchema = { | ||
path: { type: 'string' }, | ||
methods: { | ||
type: 'array', | ||
items: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] }, | ||
}, | ||
}, | ||
@@ -34,0 +38,0 @@ additionalProperties: false, |
{ | ||
"name": "@netlify/edge-bundler", | ||
"version": "8.18.0", | ||
"version": "8.19.0", | ||
"description": "Intelligently prepare Netlify Edge Functions for deployment", | ||
@@ -85,3 +85,2 @@ "type": "module", | ||
"get-port": "^6.1.2", | ||
"glob-to-regexp": "^0.4.1", | ||
"is-path-inside": "^4.0.0", | ||
@@ -88,0 +87,0 @@ "jsonc-parser": "^3.2.0", |
3111850
21
8186
- Removedglob-to-regexp@^0.4.1
- Removedglob-to-regexp@0.4.1(transitive)