@netlify/edge-bundler 8.18.0 → 8.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/deno/bundle.ts CHANGED
@@ -3,4 +3,12 @@ import { writeStage2 } from './lib/stage2.ts'
3
3
  const [payload] = Deno.args
4
4
  const { basePath, destPath, externals, functions, importMapData } = JSON.parse(payload)
5
5
 
6
- await writeStage2({ basePath, destPath, externals, functions, importMapData })
6
+ try {
7
+ await writeStage2({ basePath, destPath, externals, functions, importMapData })
8
+ } catch (error) {
9
+ if (error instanceof Error && error.message.includes("The module's source code could not be parsed")) {
10
+ delete error.stack
11
+ }
12
+
13
+ throw error
14
+ }
package/deno/config.ts CHANGED
@@ -1,7 +1,9 @@
1
- const [functionURL, collectorURL, bootstrapURL, rawExitCodes] = Deno.args
1
+ // this needs to be updated whenever there's a change to globalThis.Netlify in bootstrap
2
+ import { Netlify } from "https://64e8753eae24930008fac6d9--edge.netlify.app/bootstrap/index-combined.ts"
3
+
4
+ const [functionURL, collectorURL, rawExitCodes] = Deno.args
2
5
  const exitCodes = JSON.parse(rawExitCodes)
3
6
 
4
- const { Netlify } = await import(bootstrapURL)
5
7
  globalThis.Netlify = Netlify
6
8
 
7
9
  let func
@@ -1,4 +1,5 @@
1
- export const PUBLIC_SPECIFIER = 'netlify:edge'
1
+ export const LEGACY_PUBLIC_SPECIFIER = 'netlify:edge'
2
+ export const PUBLIC_SPECIFIER = '@netlify/edge-functions'
2
3
  export const STAGE1_SPECIFIER = 'netlify:bootstrap-stage1'
3
4
  export const STAGE2_SPECIFIER = 'netlify:bootstrap-stage2'
4
5
  export const virtualRoot = 'file:///root/'
@@ -4,7 +4,7 @@ import * as path from 'https://deno.land/std@0.177.0/path/mod.ts'
4
4
 
5
5
  import type { InputFunction, WriteStage2Options } from '../../shared/stage2.ts'
6
6
  import { importMapSpecifier, virtualRoot } from '../../shared/consts.ts'
7
- import { PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts'
7
+ import { LEGACY_PUBLIC_SPECIFIER, PUBLIC_SPECIFIER, STAGE2_SPECIFIER } from './consts.ts'
8
8
  import { inlineModule, loadFromVirtualRoot, loadWithRetry } from './common.ts'
9
9
 
10
10
  interface FunctionReference {
@@ -75,7 +75,12 @@ const stage2Loader = (basePath: string, functions: InputFunction[], externals: S
75
75
  return inlineModule(specifier, importMapData)
76
76
  }
77
77
 
78
- if (specifier === PUBLIC_SPECIFIER || externals.has(specifier) || specifier.startsWith('node:')) {
78
+ if (
79
+ specifier === LEGACY_PUBLIC_SPECIFIER ||
80
+ specifier === PUBLIC_SPECIFIER ||
81
+ externals.has(specifier) ||
82
+ specifier.startsWith('node:')
83
+ ) {
79
84
  return {
80
85
  kind: 'external',
81
86
  specifier,
@@ -20,6 +20,10 @@ class BundleError extends Error {
20
20
  */
21
21
  const wrapBundleError = (input, options) => {
22
22
  if (input instanceof Error) {
23
+ if (input.message.includes("The module's source code could not be parsed")) {
24
+ // eslint-disable-next-line no-param-reassign
25
+ input.message = input.stderr;
26
+ }
23
27
  return new BundleError(input, options);
24
28
  }
25
29
  return input;
@@ -16,7 +16,7 @@ interface BundleOptions {
16
16
  internalSrcFolder?: string;
17
17
  bootstrapURL?: string;
18
18
  }
19
- declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, bootstrapURL, }?: BundleOptions) => Promise<{
19
+ declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, }?: BundleOptions) => Promise<{
20
20
  functions: import("./edge_function.js").EdgeFunction[];
21
21
  manifest: import("./manifest.js").Manifest;
22
22
  }>;
@@ -14,7 +14,7 @@ import { ImportMap } from './import_map.js';
14
14
  import { getLogger } from './logger.js';
15
15
  import { writeManifest } from './manifest.js';
16
16
  import { ensureLatestTypes } from './types.js';
17
- const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, bootstrapURL = 'https://edge.netlify.com/bootstrap/index-combined.ts', } = {}) => {
17
+ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths = [], onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, } = {}) => {
18
18
  const logger = getLogger(systemLogger, debug);
19
19
  const featureFlags = getFlags(inputFeatureFlags);
20
20
  const options = {
@@ -63,8 +63,8 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
63
63
  await createFinalBundles([functionBundle], distDirectory, buildID);
64
64
  // Retrieving a configuration object for each function.
65
65
  // Run `getFunctionConfig` in parallel as it is a non-trivial operation and spins up deno
66
- const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger, bootstrapURL })]);
67
- const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger, bootstrapURL })]);
66
+ const internalConfigPromises = internalFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger })]);
67
+ const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig({ func, importMap, deno, log: logger })]);
68
68
  // Creating a hash of function names to configuration objects.
69
69
  const internalFunctionsWithConfig = Object.fromEntries(await Promise.all(internalConfigPromises));
70
70
  const userFunctionsWithConfig = Object.fromEntries(await Promise.all(userConfigPromises));
@@ -68,7 +68,8 @@ test('Uses the vendored eszip module instead of fetching it from deno.land', asy
68
68
  await cleanup();
69
69
  });
70
70
  test('Adds a custom error property to user errors during bundling', async () => {
71
- expect.assertions(2);
71
+ process.env.NO_COLOR = 'true';
72
+ expect.assertions(3);
72
73
  const { basePath, cleanup, distPath } = await useFixture('invalid_functions');
73
74
  const sourceDirectory = join(basePath, 'functions');
74
75
  const declarations = [
@@ -82,6 +83,16 @@ test('Adds a custom error property to user errors during bundling', async () =>
82
83
  }
83
84
  catch (error) {
84
85
  expect(error).toBeInstanceOf(BundleError);
86
+ const [messageBeforeStack] = error.message.split('at <anonymous> (file://');
87
+ expect(messageBeforeStack).toMatchInlineSnapshot(`
88
+ "error: Uncaught (in promise) Error: The module's source code could not be parsed: Unexpected eof at file:///root/functions/func1.ts:1:27
89
+
90
+ export default async () =>
91
+ ~
92
+ const ret = new Error(getStringFromWasm0(arg0, arg1));
93
+ ^
94
+ "
95
+ `);
85
96
  expect(error.customErrorInfo).toEqual({
86
97
  location: {
87
98
  format: 'eszip',
@@ -6,6 +6,7 @@ export declare const enum Cache {
6
6
  Off = "off",
7
7
  Manual = "manual"
8
8
  }
9
+ export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
9
10
  export type Path = `/${string}`;
10
11
  export type OnError = 'fail' | 'bypass' | Path;
11
12
  export declare const isValidOnError: (value: unknown) => value is OnError;
@@ -16,11 +17,11 @@ export interface FunctionConfig {
16
17
  onError?: OnError;
17
18
  name?: string;
18
19
  generator?: string;
20
+ method?: HTTPMethod | HTTPMethod[];
19
21
  }
20
- export declare const getFunctionConfig: ({ func, importMap, deno, bootstrapURL, log, }: {
22
+ export declare const getFunctionConfig: ({ func, importMap, deno, log, }: {
21
23
  func: EdgeFunction;
22
24
  importMap: ImportMap;
23
25
  deno: DenoBridge;
24
- bootstrapURL: string;
25
26
  log: Logger;
26
27
  }) => Promise<FunctionConfig>;
@@ -26,7 +26,7 @@ const getConfigExtractor = () => {
26
26
  const configExtractorPath = join(packagePath, 'deno', 'config.ts');
27
27
  return configExtractorPath;
28
28
  };
29
- export const getFunctionConfig = async ({ func, importMap, deno, bootstrapURL, log, }) => {
29
+ export const getFunctionConfig = async ({ func, importMap, deno, log, }) => {
30
30
  // The extractor is a Deno script that will import the function and run its
31
31
  // `config` export, if one exists.
32
32
  const extractorPath = getConfigExtractor();
@@ -50,7 +50,6 @@ export const getFunctionConfig = async ({ func, importMap, deno, bootstrapURL, l
50
50
  extractorPath,
51
51
  pathToFileURL(func.path).href,
52
52
  pathToFileURL(collector.path).href,
53
- bootstrapURL,
54
53
  JSON.stringify(ConfigExitCode),
55
54
  ], { rejectOnExitCode: false });
56
55
  if (exitCode !== ConfigExitCode.Success) {
@@ -9,7 +9,6 @@ import { DenoBridge } from './bridge.js';
9
9
  import { bundle } from './bundler.js';
10
10
  import { getFunctionConfig } from './config.js';
11
11
  import { ImportMap } from './import_map.js';
12
- const bootstrapURL = 'https://edge.netlify.com/bootstrap/index-combined.ts';
13
12
  const importMapFile = {
14
13
  baseURL: new URL('file:///some/path/import-map.json'),
15
14
  imports: {
@@ -122,7 +121,6 @@ describe('`getFunctionConfig` extracts configuration properties from function fi
122
121
  importMap: new ImportMap([importMapFile]),
123
122
  deno,
124
123
  log: logger,
125
- bootstrapURL,
126
124
  });
127
125
  if (func.error) {
128
126
  await expect(funcCall()).rejects.toThrowError(func.error);
@@ -151,12 +149,12 @@ test('Loads function paths from the in-source `config` function', async () => {
151
149
  {
152
150
  function: 'user-func2',
153
151
  path: '/user-func2',
152
+ method: ['PATCH'],
154
153
  },
155
154
  ];
156
155
  const result = await bundle([internalDirectory, userDirectory], distPath, declarations, {
157
156
  basePath,
158
157
  configPath: join(internalDirectory, 'config.json'),
159
- featureFlags: { edge_functions_path_urlpattern: true },
160
158
  });
161
159
  const generatedFiles = await fs.readdir(distPath);
162
160
  expect(result.functions.length).toBe(7);
@@ -179,6 +177,7 @@ test('Loads function paths from the in-source `config` function', async () => {
179
177
  pattern: '^/user-func2/?$',
180
178
  excluded_patterns: [],
181
179
  path: '/user-func2',
180
+ methods: ['PATCH'],
182
181
  });
183
182
  expect(routes[2]).toEqual({
184
183
  function: 'framework-func1',
@@ -203,6 +202,7 @@ test('Loads function paths from the in-source `config` function', async () => {
203
202
  pattern: '^/user-func5(?:/(.*))/?$',
204
203
  excluded_patterns: [],
205
204
  path: '/user-func5/*',
205
+ methods: ['GET'],
206
206
  });
207
207
  expect(postCacheRoutes.length).toBe(1);
208
208
  expect(postCacheRoutes[0]).toEqual({
@@ -210,6 +210,7 @@ test('Loads function paths from the in-source `config` function', async () => {
210
210
  pattern: '^/user-func4/?$',
211
211
  excluded_patterns: [],
212
212
  path: '/user-func4',
213
+ methods: ['POST', 'PUT'],
213
214
  });
214
215
  expect(Object.keys(functionConfig)).toHaveLength(1);
215
216
  expect(functionConfig['user-func5']).toEqual({
@@ -243,7 +244,6 @@ test('Passes validation if default export exists and is a function', async () =>
243
244
  importMap: new ImportMap([importMapFile]),
244
245
  deno,
245
246
  log: logger,
246
- bootstrapURL,
247
247
  })).resolves.not.toThrow();
248
248
  await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
249
249
  });
@@ -273,7 +273,6 @@ test('Fails validation if default export is not function', async () => {
273
273
  importMap: new ImportMap([importMapFile]),
274
274
  deno,
275
275
  log: logger,
276
- bootstrapURL,
277
276
  });
278
277
  await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
279
278
  await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
@@ -303,7 +302,6 @@ test('Fails validation if default export is not present', async () => {
303
302
  importMap: new ImportMap([importMapFile]),
304
303
  deno,
305
304
  log: logger,
306
- bootstrapURL,
307
305
  });
308
306
  await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
309
307
  await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
@@ -1,8 +1,9 @@
1
- import { FunctionConfig, Path } from './config.js';
1
+ import { FunctionConfig, HTTPMethod, Path } from './config.js';
2
2
  import { FeatureFlags } from './feature_flags.js';
3
3
  interface BaseDeclaration {
4
4
  cache?: string;
5
5
  function: string;
6
+ method?: HTTPMethod | HTTPMethod[];
6
7
  name?: string;
7
8
  generator?: string;
8
9
  }
@@ -48,7 +48,7 @@ const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsV
48
48
  const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited) => {
49
49
  const declarations = [];
50
50
  for (const name in functionConfigs) {
51
- const { cache, path } = functionConfigs[name];
51
+ const { cache, path, method } = functionConfigs[name];
52
52
  // If we have a path specified, create a declaration for each path.
53
53
  if (!functionsVisited.has(name) && path) {
54
54
  const paths = Array.isArray(path) ? path : [path];
@@ -57,6 +57,9 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
57
57
  if (cache) {
58
58
  declaration.cache = cache;
59
59
  }
60
+ if (method) {
61
+ declaration.method = method;
62
+ }
60
63
  declarations.push(declaration);
61
64
  });
62
65
  }
@@ -1,12 +1,10 @@
1
1
  declare const defaultFlags: {
2
2
  edge_functions_fail_unsupported_regex: boolean;
3
- edge_functions_path_urlpattern: boolean;
4
3
  };
5
4
  type FeatureFlag = keyof typeof defaultFlags;
6
5
  type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
7
6
  declare const getFlags: (input?: Record<string, boolean>, flags?: {
8
7
  edge_functions_fail_unsupported_regex: boolean;
9
- edge_functions_path_urlpattern: boolean;
10
8
  }) => FeatureFlags;
11
9
  export { defaultFlags, getFlags };
12
10
  export type { FeatureFlag, FeatureFlags };
@@ -1,6 +1,5 @@
1
1
  const defaultFlags = {
2
2
  edge_functions_fail_unsupported_regex: false,
3
- edge_functions_path_urlpattern: false,
4
3
  };
5
4
  const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({
6
5
  ...result,
@@ -5,7 +5,8 @@ import { fileURLToPath, pathToFileURL } from 'url';
5
5
  import { parse } from '@import-maps/resolve';
6
6
  import { isFileNotFoundError } from './utils/error.js';
7
7
  const INTERNAL_IMPORTS = {
8
- 'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
8
+ '@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
9
+ 'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
9
10
  };
10
11
  // ImportMap can take several import map files and merge them into a final
11
12
  // import map object, also adding the internal imports in the right order.
@@ -21,7 +21,8 @@ test('Handles import maps with full URLs without specifying a base URL', () => {
21
21
  };
22
22
  const map = new ImportMap([inputFile1, inputFile2]);
23
23
  const { imports } = map.getContents();
24
- expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
24
+ expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
25
+ expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
25
26
  expect(imports['alias:jamstack']).toBe('https://jamstack.org/');
26
27
  expect(imports['alias:pets']).toBe('https://petsofnetlify.com/');
27
28
  });
@@ -36,7 +37,8 @@ test('Resolves relative paths to absolute paths if a base path is not provided',
36
37
  const map = new ImportMap([inputFile1]);
37
38
  const { imports } = map.getContents();
38
39
  const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets');
39
- expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
40
+ expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
41
+ expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
40
42
  expect(imports['alias:pets']).toBe(`${pathToFileURL(expectedPath).toString()}/`);
41
43
  });
42
44
  describe('Returns the fully resolved import map', () => {
@@ -69,7 +71,8 @@ describe('Returns the fully resolved import map', () => {
69
71
  specifier3: 'file:///different/full/path/file3.js',
70
72
  specifier2: 'file:///some/full/path/file2.js',
71
73
  specifier1: 'file:///some/full/path/file.js',
72
- 'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
74
+ '@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
75
+ 'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
73
76
  });
74
77
  expect(scopes).toStrictEqual({
75
78
  'file:///some/cool/path/with/scopes/': {
@@ -90,7 +93,8 @@ describe('Returns the fully resolved import map', () => {
90
93
  specifier3: 'file:///vendor/full/path/file3.js',
91
94
  specifier2: 'file:///root/full/path/file2.js',
92
95
  specifier1: 'file:///root/full/path/file.js',
93
- 'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
96
+ '@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
97
+ 'netlify:edge': 'https://edge.netlify.com/v1/index.ts?v=legacy',
94
98
  });
95
99
  expect(scopes).toStrictEqual({
96
100
  'file:///root/cool/path/with/scopes/': {
@@ -126,6 +130,7 @@ test('Writes import map file to disk', async () => {
126
130
  const { imports } = JSON.parse(createdFile);
127
131
  const expectedPath = join(cwd(), 'my-cool-site', 'heart', 'pets', 'file.ts');
128
132
  await file.cleanup();
129
- expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
133
+ expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts?v=legacy');
134
+ expect(imports['@netlify/edge-functions']).toBe('https://edge.netlify.com/v1/index.ts');
130
135
  expect(imports['alias:pets']).toBe(pathToFileURL(expectedPath).toString());
131
136
  });
@@ -9,6 +9,7 @@ interface Route {
9
9
  pattern: string;
10
10
  excluded_patterns: string[];
11
11
  path?: string;
12
+ methods?: string[];
12
13
  }
13
14
  interface EdgeFunctionConfig {
14
15
  excluded_patterns: string[];
@@ -1,6 +1,5 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { join } from 'path';
3
- import globToRegExp from 'glob-to-regexp';
4
3
  import { wrapBundleError } from './bundle_error.js';
5
4
  import { parsePattern } from './declaration.js';
6
5
  import { getPackageVersion } from './package_json.js';
@@ -27,13 +26,26 @@ const sanitizeEdgeFunctionConfig = (config) => {
27
26
  }
28
27
  return newConfig;
29
28
  };
30
- const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath, featureFlags) => {
29
+ const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => {
31
30
  if (excludedPath) {
32
31
  const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
33
- const excludedPatterns = paths.map((path) => pathToRegularExpression(path, featureFlags)).map(serializePattern);
32
+ const excludedPatterns = paths.map((path) => pathToRegularExpression(path)).map(serializePattern);
34
33
  manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
35
34
  }
36
35
  };
36
+ /**
37
+ * Normalizes method names into arrays of uppercase strings.
38
+ * (e.g. "get" becomes ["GET"])
39
+ */
40
+ const normalizeMethods = (method, name) => {
41
+ const methods = Array.isArray(method) ? method : [method];
42
+ return methods.map((method) => {
43
+ if (typeof method !== 'string') {
44
+ throw new TypeError(`Could not parse method declaration of function '${name}'. Expecting HTTP Method, got ${method}`);
45
+ }
46
+ return method.toUpperCase();
47
+ });
48
+ };
37
49
  const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => {
38
50
  const preCacheRoutes = [];
39
51
  const postCacheRoutes = [];
@@ -43,7 +55,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
43
55
  if (manifestFunctionConfig[name] === undefined) {
44
56
  continue;
45
57
  }
46
- addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags);
58
+ addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
47
59
  manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError };
48
60
  }
49
61
  for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) {
@@ -51,7 +63,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
51
63
  if (manifestFunctionConfig[name] === undefined) {
52
64
  continue;
53
65
  }
54
- addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags);
66
+ addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
55
67
  manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest };
56
68
  }
57
69
  declarations.forEach((declaration) => {
@@ -66,6 +78,9 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
66
78
  pattern: serializePattern(pattern),
67
79
  excluded_patterns: excludedPattern.map(serializePattern),
68
80
  };
81
+ if ('method' in declaration) {
82
+ route.methods = normalizeMethods(declaration.method, func.name);
83
+ }
69
84
  if ('path' in declaration) {
70
85
  route.path = declaration.path;
71
86
  }
@@ -91,31 +106,21 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
91
106
  };
92
107
  return manifest;
93
108
  };
94
- const pathToRegularExpression = (path, featureFlags) => {
95
- if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_path_urlpattern) {
96
- try {
97
- const pattern = new ExtendedURLPattern({ pathname: path });
98
- // Removing the `^` and `$` delimiters because we'll need to modify what's
99
- // between them.
100
- const source = pattern.regexp.pathname.source.slice(1, -1);
101
- // Wrapping the expression source with `^` and `$`. Also, adding an optional
102
- // trailing slash, so that a declaration of `path: "/foo"` matches requests
103
- // for both `/foo` and `/foo/`.
104
- const normalizedSource = `^${source}\\/?$`;
105
- return normalizedSource;
106
- }
107
- catch (error) {
108
- throw wrapBundleError(error);
109
- }
109
+ const pathToRegularExpression = (path) => {
110
+ try {
111
+ const pattern = new ExtendedURLPattern({ pathname: path });
112
+ // Removing the `^` and `$` delimiters because we'll need to modify what's
113
+ // between them.
114
+ const source = pattern.regexp.pathname.source.slice(1, -1);
115
+ // Wrapping the expression source with `^` and `$`. Also, adding an optional
116
+ // trailing slash, so that a declaration of `path: "/foo"` matches requests
117
+ // for both `/foo` and `/foo/`.
118
+ const normalizedSource = `^${source}\\/?$`;
119
+ return normalizedSource;
120
+ }
121
+ catch (error) {
122
+ throw wrapBundleError(error);
110
123
  }
111
- // We use the global flag so that `globToRegExp` will not wrap the expression
112
- // with `^` and `$`. We'll do that ourselves.
113
- const regularExpression = globToRegExp(path, { flags: 'g' });
114
- // Wrapping the expression source with `^` and `$`. Also, adding an optional
115
- // trailing slash, so that a declaration of `path: "/foo"` matches requests
116
- // for both `/foo` and `/foo/`.
117
- const normalizedSource = `^${regularExpression.source}\\/?$`;
118
- return normalizedSource;
119
124
  };
120
125
  const getRegularExpression = (declaration, featureFlags) => {
121
126
  if ('pattern' in declaration) {
@@ -131,7 +136,7 @@ const getRegularExpression = (declaration, featureFlags) => {
131
136
  return declaration.pattern;
132
137
  }
133
138
  }
134
- return pathToRegularExpression(declaration.path, featureFlags);
139
+ return pathToRegularExpression(declaration.path);
135
140
  };
136
141
  const getExcludedRegularExpressions = (declaration, featureFlags) => {
137
142
  if ('excludedPattern' in declaration && declaration.excludedPattern) {
@@ -153,7 +158,7 @@ const getExcludedRegularExpressions = (declaration, featureFlags) => {
153
158
  }
154
159
  if ('path' in declaration && declaration.excludedPath) {
155
160
  const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath];
156
- return paths.map((path) => pathToRegularExpression(path, featureFlags));
161
+ return paths.map((path) => pathToRegularExpression(path));
157
162
  }
158
163
  return [];
159
164
  };
@@ -40,7 +40,6 @@ test('Generates a manifest with display names', () => {
40
40
  declarations,
41
41
  functions,
42
42
  internalFunctionConfig,
43
- featureFlags: { edge_functions_path_urlpattern: true },
44
43
  });
45
44
  const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
46
45
  expect(manifest.function_config).toEqual({
@@ -62,7 +61,6 @@ test('Generates a manifest with a generator field', () => {
62
61
  declarations,
63
62
  functions,
64
63
  internalFunctionConfig,
65
- featureFlags: { edge_functions_path_urlpattern: true },
66
64
  });
67
65
  const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
68
66
  const expectedFunctionConfig = { 'func-1': { generator: '@netlify/fake-plugin@1.0.0' } };
@@ -85,7 +83,6 @@ test('Generates a manifest with excluded paths and patterns', () => {
85
83
  bundles: [],
86
84
  declarations,
87
85
  functions,
88
- featureFlags: { edge_functions_path_urlpattern: true },
89
86
  });
90
87
  const expectedRoutes = [
91
88
  { function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: ['^/f1/exclude/?$'], path: '/f1/*' },
@@ -119,7 +116,6 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
119
116
  declarations,
120
117
  functions,
121
118
  userFunctionConfig,
122
- featureFlags: { edge_functions_path_urlpattern: true },
123
119
  });
124
120
  const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
125
121
  expect(manifest.routes).toEqual(expectedRoutes);
@@ -196,7 +192,6 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
196
192
  functions,
197
193
  userFunctionConfig,
198
194
  internalFunctionConfig,
199
- featureFlags: { edge_functions_path_urlpattern: true },
200
195
  });
201
196
  expect(manifest.routes).toEqual([
202
197
  {
@@ -235,7 +230,6 @@ test('URLPattern named groups are supported', () => {
235
230
  functions,
236
231
  userFunctionConfig,
237
232
  internalFunctionConfig,
238
- featureFlags: { edge_functions_path_urlpattern: true },
239
233
  });
240
234
  expect(manifest.routes).toEqual([
241
235
  {
@@ -259,7 +253,6 @@ test('Invalid Path patterns throw bundling errors', () => {
259
253
  functions,
260
254
  userFunctionConfig,
261
255
  internalFunctionConfig,
262
- featureFlags: { edge_functions_path_urlpattern: true },
263
256
  })).toThrowError(BundleError);
264
257
  });
265
258
  test('Includes failure modes in manifest', () => {
@@ -381,14 +374,12 @@ test('Generates a manifest with layers', () => {
381
374
  bundles: [],
382
375
  declarations,
383
376
  functions,
384
- featureFlags: { edge_functions_path_urlpattern: true },
385
377
  });
386
378
  const manifest2 = generateManifest({
387
379
  bundles: [],
388
380
  declarations,
389
381
  functions,
390
382
  layers,
391
- featureFlags: { edge_functions_path_urlpattern: true },
392
383
  });
393
384
  expect(manifest1.routes).toEqual(expectedRoutes);
394
385
  expect(manifest1.layers).toEqual([]);
@@ -43,7 +43,7 @@ const prepareServer = ({ bootstrapURL, deno, distDirectory, flags: denoFlags, fo
43
43
  });
44
44
  let functionsConfig = [];
45
45
  if (options.getFunctionsConfig) {
46
- functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, bootstrapURL, log: logger })));
46
+ functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, log: logger })));
47
47
  }
48
48
  const success = await waitForServer(port, processRef.ps);
49
49
  return {
@@ -50,6 +50,13 @@ declare const edgeManifestSchema: {
50
50
  path: {
51
51
  type: string;
52
52
  };
53
+ methods: {
54
+ type: string;
55
+ items: {
56
+ type: string;
57
+ enum: string[];
58
+ };
59
+ };
53
60
  };
54
61
  additionalProperties: boolean;
55
62
  };
@@ -85,6 +92,13 @@ declare const edgeManifestSchema: {
85
92
  path: {
86
93
  type: string;
87
94
  };
95
+ methods: {
96
+ type: string;
97
+ items: {
98
+ type: string;
99
+ enum: string[];
100
+ };
101
+ };
88
102
  };
89
103
  additionalProperties: boolean;
90
104
  };
@@ -29,6 +29,10 @@ const routesSchema = {
29
29
  excluded_patterns: excludedPatternsSchema,
30
30
  generator: { type: 'string' },
31
31
  path: { type: 'string' },
32
+ methods: {
33
+ type: 'array',
34
+ items: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] },
35
+ },
32
36
  },
33
37
  additionalProperties: false,
34
38
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "8.18.0",
3
+ "version": "8.19.1",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -82,7 +82,6 @@
82
82
  "execa": "^6.0.0",
83
83
  "find-up": "^6.3.0",
84
84
  "get-port": "^6.1.2",
85
- "glob-to-regexp": "^0.4.1",
86
85
  "is-path-inside": "^4.0.0",
87
86
  "jsonc-parser": "^3.2.0",
88
87
  "node-fetch": "^3.1.1",