@netlify/edge-bundler 8.17.1 → 8.19.0

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.
@@ -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,
@@ -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,6 +17,7 @@ export interface FunctionConfig {
16
17
  onError?: OnError;
17
18
  name?: string;
18
19
  generator?: string;
20
+ method?: HTTPMethod | HTTPMethod[];
19
21
  }
20
22
  export declare const getFunctionConfig: ({ func, importMap, deno, bootstrapURL, log, }: {
21
23
  func: EdgeFunction;
@@ -151,12 +151,12 @@ test('Loads function paths from the in-source `config` function', async () => {
151
151
  {
152
152
  function: 'user-func2',
153
153
  path: '/user-func2',
154
+ method: ['PATCH'],
154
155
  },
155
156
  ];
156
157
  const result = await bundle([internalDirectory, userDirectory], distPath, declarations, {
157
158
  basePath,
158
159
  configPath: join(internalDirectory, 'config.json'),
159
- featureFlags: { edge_functions_path_urlpattern: true },
160
160
  });
161
161
  const generatedFiles = await fs.readdir(distPath);
162
162
  expect(result.functions.length).toBe(7);
@@ -168,14 +168,52 @@ test('Loads function paths from the in-source `config` function', async () => {
168
168
  expect(bundles[0].format).toBe('eszip2');
169
169
  expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
170
170
  expect(routes.length).toBe(6);
171
- expect(routes[0]).toEqual({ function: 'framework-func2', pattern: '^/framework-func2/?$', excluded_patterns: [] });
172
- expect(routes[1]).toEqual({ function: 'user-func2', pattern: '^/user-func2/?$', excluded_patterns: [] });
173
- expect(routes[2]).toEqual({ function: 'framework-func1', pattern: '^/framework-func1/?$', excluded_patterns: [] });
174
- expect(routes[3]).toEqual({ function: 'user-func1', pattern: '^/user-func1/?$', excluded_patterns: [] });
175
- expect(routes[4]).toEqual({ function: 'user-func3', pattern: '^/user-func3/?$', excluded_patterns: [] });
176
- expect(routes[5]).toEqual({ function: 'user-func5', pattern: '^/user-func5(?:/(.*))/?$', excluded_patterns: [] });
171
+ expect(routes[0]).toEqual({
172
+ function: 'framework-func2',
173
+ pattern: '^/framework-func2/?$',
174
+ excluded_patterns: [],
175
+ path: '/framework-func2',
176
+ });
177
+ expect(routes[1]).toEqual({
178
+ function: 'user-func2',
179
+ pattern: '^/user-func2/?$',
180
+ excluded_patterns: [],
181
+ path: '/user-func2',
182
+ methods: ['PATCH'],
183
+ });
184
+ expect(routes[2]).toEqual({
185
+ function: 'framework-func1',
186
+ pattern: '^/framework-func1/?$',
187
+ excluded_patterns: [],
188
+ path: '/framework-func1',
189
+ });
190
+ expect(routes[3]).toEqual({
191
+ function: 'user-func1',
192
+ pattern: '^/user-func1/?$',
193
+ excluded_patterns: [],
194
+ path: '/user-func1',
195
+ });
196
+ expect(routes[4]).toEqual({
197
+ function: 'user-func3',
198
+ pattern: '^/user-func3/?$',
199
+ excluded_patterns: [],
200
+ path: '/user-func3',
201
+ });
202
+ expect(routes[5]).toEqual({
203
+ function: 'user-func5',
204
+ pattern: '^/user-func5(?:/(.*))/?$',
205
+ excluded_patterns: [],
206
+ path: '/user-func5/*',
207
+ methods: ['GET'],
208
+ });
177
209
  expect(postCacheRoutes.length).toBe(1);
178
- expect(postCacheRoutes[0]).toEqual({ function: 'user-func4', pattern: '^/user-func4/?$', excluded_patterns: [] });
210
+ expect(postCacheRoutes[0]).toEqual({
211
+ function: 'user-func4',
212
+ pattern: '^/user-func4/?$',
213
+ excluded_patterns: [],
214
+ path: '/user-func4',
215
+ methods: ['POST', 'PUT'],
216
+ });
179
217
  expect(Object.keys(functionConfig)).toHaveLength(1);
180
218
  expect(functionConfig['user-func5']).toEqual({
181
219
  excluded_patterns: ['^/user-func5/excluded/?$'],
@@ -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,
@@ -1,4 +1,5 @@
1
1
  import { join } from 'path';
2
+ import { pathToFileURL } from 'url';
2
3
  import { virtualRoot } from '../../shared/consts.js';
3
4
  import { BundleFormat } from '../bundle.js';
4
5
  import { wrapBundleError } from '../bundle_error.js';
@@ -9,13 +10,17 @@ const bundleESZIP = async ({ basePath, buildID, debug, deno, distDirectory, exte
9
10
  const extension = '.eszip';
10
11
  const destPath = join(distDirectory, `${buildID}${extension}`);
11
12
  const { bundler, importMap: bundlerImportMap } = getESZIPPaths();
12
- const importMapData = JSON.stringify(importMap.getContents(basePath, virtualRoot));
13
+ // Transforming all paths under `basePath` to use the virtual root prefix.
14
+ const importMapPrefixes = {
15
+ [`${pathToFileURL(basePath)}/`]: virtualRoot,
16
+ };
17
+ const importMapData = importMap.getContents(importMapPrefixes);
13
18
  const payload = {
14
19
  basePath,
15
20
  destPath,
16
21
  externals,
17
22
  functions,
18
- importMapData,
23
+ importMapData: JSON.stringify(importMapData),
19
24
  };
20
25
  const flags = ['--allow-all', '--no-config', `--import-map=${bundlerImportMap}`];
21
26
  if (!debug) {
@@ -1,29 +1,32 @@
1
+ import { ParsedImportMap } from '@import-maps/resolve';
1
2
  import { Logger } from './logger.js';
2
3
  type Imports = Record<string, string>;
3
- interface ImportMapSource {
4
+ export interface ImportMapFile {
4
5
  baseURL: URL;
5
6
  imports: Imports;
6
7
  scopes?: Record<string, Imports>;
7
8
  }
8
- declare class ImportMap {
9
- sources: ImportMapSource[];
10
- constructor(sources?: ImportMapSource[]);
11
- static resolve(source: ImportMapSource, basePath?: string, prefix?: string): {
12
- imports: Record<string, string>;
13
- scopes: Record<string, Imports>;
14
- };
15
- static resolveImports(imports: Record<string, URL | null>, basePath?: string, prefix?: string): Record<string, string>;
16
- static resolvePath(url: URL, basePath?: string, prefix?: string): string;
17
- add(source: ImportMapSource): void;
9
+ export declare class ImportMap {
10
+ rootPath: string | null;
11
+ sources: ImportMapFile[];
12
+ constructor(sources?: ImportMapFile[], rootURL?: string | null);
13
+ add(source: ImportMapFile): void;
18
14
  addFile(path: string, logger: Logger): Promise<void>;
19
15
  addFiles(paths: (string | undefined)[], logger: Logger): Promise<void>;
20
- getContents(basePath?: string, prefix?: string): {
16
+ static applyPrefixesToImports(imports: Imports, prefixes: Record<string, string>): Imports;
17
+ static applyPrefixesToPath(path: string, prefixes: Record<string, string>): string;
18
+ filterImports(imports?: Record<string, URL | null>): Record<string, string>;
19
+ filterScopes(scopes?: ParsedImportMap['scopes']): Record<string, Imports>;
20
+ getContents(prefixes?: Record<string, string>): {
21
21
  imports: Imports;
22
+ scopes: {};
23
+ };
24
+ static readFile(path: string, logger: Logger): Promise<ImportMapFile>;
25
+ resolve(source: ImportMapFile): {
26
+ imports: Record<string, string>;
22
27
  scopes: Record<string, Imports>;
23
28
  };
24
29
  toDataURL(): string;
25
30
  writeToFile(path: string): Promise<void>;
26
31
  }
27
- declare const readFile: (path: string, logger: Logger) => Promise<ImportMapSource>;
28
- export { ImportMap, readFile };
29
- export type { ImportMapSource as ImportMapFile };
32
+ export {};
@@ -1,82 +1,28 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import { promises as fs } from 'fs';
3
- import { dirname, posix, relative, sep } from 'path';
3
+ import { dirname, relative } from 'path';
4
4
  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-functions': 'https://edge.netlify.com/v1/index.ts',
8
9
  'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
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.
12
- class ImportMap {
13
- constructor(sources = []) {
13
+ export class ImportMap {
14
+ constructor(sources = [], rootURL = null) {
15
+ this.rootPath = rootURL ? fileURLToPath(rootURL) : null;
14
16
  this.sources = [];
15
17
  sources.forEach((file) => {
16
18
  this.add(file);
17
19
  });
18
20
  }
19
- // Transforms an import map by making any relative paths use a different path
20
- // as a base.
21
- static resolve(source, basePath, prefix = 'file://') {
22
- const { baseURL, ...importMap } = source;
23
- const parsedImportMap = parse(importMap, baseURL);
24
- const { imports = {}, scopes = {} } = parsedImportMap;
25
- const resolvedImports = ImportMap.resolveImports(imports, basePath, prefix);
26
- const resolvedScopes = {};
27
- Object.keys(scopes).forEach((path) => {
28
- const resolvedPath = ImportMap.resolvePath(new URL(path), basePath, prefix);
29
- resolvedScopes[resolvedPath] = ImportMap.resolveImports(scopes[path], basePath, prefix);
30
- });
31
- return { ...parsedImportMap, imports: resolvedImports, scopes: resolvedScopes };
32
- }
33
- // Takes an imports object and resolves relative specifiers with a given base
34
- // path and URL prefix.
35
- static resolveImports(imports, basePath, prefix) {
36
- const resolvedImports = {};
37
- Object.keys(imports).forEach((specifier) => {
38
- const url = imports[specifier];
39
- // If there's no URL, don't even add the specifier to the final imports.
40
- if (url === null) {
41
- return;
42
- }
43
- // If this is a file URL, we might want to transform it to use another
44
- // base path.
45
- if (url.protocol === 'file:') {
46
- resolvedImports[specifier] = ImportMap.resolvePath(url, basePath, prefix);
47
- return;
48
- }
49
- resolvedImports[specifier] = url.toString();
50
- });
51
- return resolvedImports;
52
- }
53
- // Takes a URL, turns it into a path relative to the given base, and prepends
54
- // a prefix (such as the virtual root prefix).
55
- static resolvePath(url, basePath, prefix) {
56
- if (basePath === undefined) {
57
- return url.toString();
58
- }
59
- const path = fileURLToPath(url);
60
- const relativePath = relative(basePath, path);
61
- if (relativePath.startsWith('..')) {
62
- throw new Error(`Import map cannot reference '${path}' as it's outside of the base directory '${basePath}'`);
63
- }
64
- // We want to use POSIX paths for the import map regardless of the OS
65
- // we're building in.
66
- let normalizedPath = relativePath.split(sep).join(posix.sep);
67
- // If the original URL had a trailing slash, ensure the normalized path
68
- // has one too.
69
- if (normalizedPath !== '' && url.pathname.endsWith(posix.sep) && !normalizedPath.endsWith(posix.sep)) {
70
- normalizedPath += posix.sep;
71
- }
72
- const newURL = new URL(normalizedPath, prefix);
73
- return newURL.toString();
74
- }
75
21
  add(source) {
76
22
  this.sources.push(source);
77
23
  }
78
24
  async addFile(path, logger) {
79
- const source = await readFile(path, logger);
25
+ const source = await ImportMap.readFile(path, logger);
80
26
  if (Object.keys(source.imports).length === 0) {
81
27
  return;
82
28
  }
@@ -90,11 +36,73 @@ class ImportMap {
90
36
  await this.addFile(path, logger);
91
37
  }
92
38
  }
93
- getContents(basePath, prefix) {
39
+ // Applies a list of prefixes to an `imports` block, by transforming values
40
+ // with the `applyPrefixesToPath` method.
41
+ static applyPrefixesToImports(imports, prefixes) {
42
+ return Object.entries(imports).reduce((acc, [key, value]) => ({
43
+ ...acc,
44
+ [key]: ImportMap.applyPrefixesToPath(value, prefixes),
45
+ }), {});
46
+ }
47
+ // Applies a list of prefixes to a given path, returning the replaced path.
48
+ // For example, given a `path` of `file:///foo/bar/baz.js` and a `prefixes`
49
+ // object with `{"file:///foo/": "file:///hello/"}`, this method will return
50
+ // `file:///hello/bar/baz.js`. If no matching prefix is found, the original
51
+ // path is returned.
52
+ static applyPrefixesToPath(path, prefixes) {
53
+ for (const prefix in prefixes) {
54
+ if (path.startsWith(prefix)) {
55
+ return path.replace(prefix, prefixes[prefix]);
56
+ }
57
+ }
58
+ return path;
59
+ }
60
+ // Takes an `imports` object and filters out any entries without a URL. Also,
61
+ // it checks whether the import map is referencing a path outside `rootPath`,
62
+ // if one is set.
63
+ filterImports(imports = {}) {
64
+ const filteredImports = {};
65
+ Object.keys(imports).forEach((specifier) => {
66
+ const url = imports[specifier];
67
+ // If there's no URL, don't even add the specifier to the final imports.
68
+ if (url === null) {
69
+ return;
70
+ }
71
+ if (this.rootPath !== null) {
72
+ const path = fileURLToPath(url);
73
+ const relativePath = relative(this.rootPath, path);
74
+ if (relativePath.startsWith('..')) {
75
+ throw new Error(`Import map cannot reference '${path}' as it's outside of the base directory '${this.rootPath}'`);
76
+ }
77
+ }
78
+ filteredImports[specifier] = url.toString();
79
+ });
80
+ return filteredImports;
81
+ }
82
+ // Takes a `scopes` object and runs all imports through `filterImports`,
83
+ // omitting any scopes for which there are no imports.
84
+ filterScopes(scopes) {
85
+ const filteredScopes = {};
86
+ if (scopes !== undefined) {
87
+ Object.keys(scopes).forEach((url) => {
88
+ const imports = this.filterImports(scopes[url]);
89
+ if (Object.keys(imports).length === 0) {
90
+ return;
91
+ }
92
+ filteredScopes[url] = imports;
93
+ });
94
+ }
95
+ return filteredScopes;
96
+ }
97
+ // Returns the import map as a plain object, with any relative paths resolved
98
+ // to full URLs. It takes an optional `prefixes` object that specifies a list
99
+ // of prefixes to replace path prefixes (see `applyPrefixesToPath`). Prefixes
100
+ // will be applied on both `imports` and `scopes`.
101
+ getContents(prefixes = {}) {
94
102
  let imports = {};
95
103
  let scopes = {};
96
104
  this.sources.forEach((file) => {
97
- const importMap = ImportMap.resolve(file, basePath, prefix);
105
+ const importMap = this.resolve(file);
98
106
  imports = { ...imports, ...importMap.imports };
99
107
  scopes = { ...scopes, ...importMap.scopes };
100
108
  });
@@ -104,11 +112,49 @@ class ImportMap {
104
112
  const [specifier, url] = internalImport;
105
113
  imports[specifier] = url;
106
114
  });
115
+ const transformedImports = ImportMap.applyPrefixesToImports(imports, prefixes);
116
+ const transformedScopes = Object.entries(scopes).reduce((acc, [key, value]) => ({
117
+ ...acc,
118
+ [ImportMap.applyPrefixesToPath(key, prefixes)]: ImportMap.applyPrefixesToImports(value, prefixes),
119
+ }), {});
120
+ return {
121
+ imports: transformedImports,
122
+ scopes: transformedScopes,
123
+ };
124
+ }
125
+ static async readFile(path, logger) {
126
+ const baseURL = pathToFileURL(path);
127
+ try {
128
+ const data = await fs.readFile(path, 'utf8');
129
+ const importMap = JSON.parse(data);
130
+ return {
131
+ ...importMap,
132
+ baseURL,
133
+ };
134
+ }
135
+ catch (error) {
136
+ if (isFileNotFoundError(error)) {
137
+ logger.system(`Did not find an import map file at '${path}'.`);
138
+ }
139
+ else {
140
+ logger.user(`Error while loading import map at '${path}':`, error);
141
+ }
142
+ }
107
143
  return {
108
- imports,
109
- scopes,
144
+ baseURL,
145
+ imports: {},
110
146
  };
111
147
  }
148
+ // Resolves an import map file by transforming all relative paths into full
149
+ // URLs. The `baseURL` property of each file is used to resolve all relative
150
+ // paths against.
151
+ resolve(source) {
152
+ const { baseURL, ...importMap } = source;
153
+ const parsedImportMap = parse(importMap, baseURL);
154
+ const imports = this.filterImports(parsedImportMap.imports);
155
+ const scopes = this.filterScopes(parsedImportMap.scopes);
156
+ return { ...parsedImportMap, imports, scopes };
157
+ }
112
158
  toDataURL() {
113
159
  const data = JSON.stringify(this.getContents());
114
160
  const encodedImportMap = Buffer.from(data).toString('base64');
@@ -121,27 +167,3 @@ class ImportMap {
121
167
  await fs.writeFile(path, JSON.stringify(contents));
122
168
  }
123
169
  }
124
- const readFile = async (path, logger) => {
125
- const baseURL = pathToFileURL(path);
126
- try {
127
- const data = await fs.readFile(path, 'utf8');
128
- const importMap = JSON.parse(data);
129
- return {
130
- ...importMap,
131
- baseURL,
132
- };
133
- }
134
- catch (error) {
135
- if (isFileNotFoundError(error)) {
136
- logger.system(`Did not find an import map file at '${path}'.`);
137
- }
138
- else {
139
- logger.user(`Error while loading import map at '${path}':`, error);
140
- }
141
- }
142
- return {
143
- baseURL,
144
- imports: {},
145
- };
146
- };
147
- export { ImportMap, readFile };
@@ -3,7 +3,7 @@ import { join } from 'path';
3
3
  import { cwd } from 'process';
4
4
  import { pathToFileURL } from 'url';
5
5
  import tmp from 'tmp-promise';
6
- import { test, expect } from 'vitest';
6
+ import { describe, test, expect } from 'vitest';
7
7
  import { ImportMap } from './import_map.js';
8
8
  test('Handles import maps with full URLs without specifying a base URL', () => {
9
9
  const basePath = join(cwd(), 'my-cool-site', 'import-map.json');
@@ -39,35 +39,79 @@ test('Resolves relative paths to absolute paths if a base path is not provided',
39
39
  expect(imports['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
40
40
  expect(imports['alias:pets']).toBe(`${pathToFileURL(expectedPath).toString()}/`);
41
41
  });
42
- test('Transforms relative paths so that they become relative to the base path', () => {
43
- const basePath = join(cwd(), 'my-cool-site', 'import-map.json');
42
+ describe('Returns the fully resolved import map', () => {
44
43
  const inputFile1 = {
45
- baseURL: pathToFileURL(basePath),
44
+ baseURL: new URL('file:///some/full/path/import-map.json'),
46
45
  imports: {
47
- 'alias:pets': './heart/pets/',
46
+ specifier1: 'file:///some/full/path/file.js',
47
+ specifier2: './file2.js',
48
+ specifier3: 'file:///different/full/path/file3.js',
48
49
  },
49
50
  };
50
- // Without a prefix.
51
- const map1 = new ImportMap([inputFile1]);
52
- const { imports: imports1 } = map1.getContents(cwd());
53
- expect(imports1['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
54
- expect(imports1['alias:pets']).toBe('file:///my-cool-site/heart/pets/');
55
- // With a prefix.
56
- const map2 = new ImportMap([inputFile1]);
57
- const { imports: imports2 } = map2.getContents(cwd(), 'file:///root/');
58
- expect(imports2['netlify:edge']).toBe('https://edge.netlify.com/v1/index.ts');
59
- expect(imports2['alias:pets']).toBe('file:///root/my-cool-site/heart/pets/');
51
+ const inputFile2 = {
52
+ baseURL: new URL('file:///some/cool/path/import-map.json'),
53
+ imports: {
54
+ 'lib/*': './library/',
55
+ },
56
+ scopes: {
57
+ 'with/scopes/': {
58
+ 'lib/*': 'https://external.netlify/lib/',
59
+ foo: './foo-alias',
60
+ bar: 'file:///different/full/path/bar.js',
61
+ },
62
+ },
63
+ };
64
+ test('Without prefixes', () => {
65
+ const map = new ImportMap([inputFile1, inputFile2]);
66
+ const { imports, scopes } = map.getContents();
67
+ expect(imports).toStrictEqual({
68
+ 'lib/*': 'file:///some/cool/path/library/',
69
+ specifier3: 'file:///different/full/path/file3.js',
70
+ specifier2: 'file:///some/full/path/file2.js',
71
+ specifier1: 'file:///some/full/path/file.js',
72
+ '@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
73
+ 'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
74
+ });
75
+ expect(scopes).toStrictEqual({
76
+ 'file:///some/cool/path/with/scopes/': {
77
+ 'lib/*': 'https://external.netlify/lib/',
78
+ foo: 'file:///some/cool/path/foo-alias',
79
+ bar: 'file:///different/full/path/bar.js',
80
+ },
81
+ });
82
+ });
83
+ test('With prefixes', () => {
84
+ const map = new ImportMap([inputFile1, inputFile2]);
85
+ const { imports, scopes } = map.getContents({
86
+ 'file:///some/': 'file:///root/',
87
+ 'file:///different/': 'file:///vendor/',
88
+ });
89
+ expect(imports).toStrictEqual({
90
+ 'lib/*': 'file:///root/cool/path/library/',
91
+ specifier3: 'file:///vendor/full/path/file3.js',
92
+ specifier2: 'file:///root/full/path/file2.js',
93
+ specifier1: 'file:///root/full/path/file.js',
94
+ '@netlify/edge-functions': 'https://edge.netlify.com/v1/index.ts',
95
+ 'netlify:edge': 'https://edge.netlify.com/v1/index.ts',
96
+ });
97
+ expect(scopes).toStrictEqual({
98
+ 'file:///root/cool/path/with/scopes/': {
99
+ 'lib/*': 'https://external.netlify/lib/',
100
+ foo: 'file:///root/cool/path/foo-alias',
101
+ bar: 'file:///vendor/full/path/bar.js',
102
+ },
103
+ });
104
+ });
60
105
  });
61
106
  test('Throws when an import map uses a relative path to reference a file outside of the base path', () => {
62
- const basePath = join(cwd(), 'my-cool-site');
63
107
  const inputFile1 = {
64
- baseURL: pathToFileURL(join(basePath, 'import_map.json')),
108
+ baseURL: pathToFileURL(join(cwd(), 'import-map.json')),
65
109
  imports: {
66
110
  'alias:file': '../file.js',
67
111
  },
68
112
  };
69
- const map = new ImportMap([inputFile1]);
70
- expect(() => map.getContents(basePath)).toThrowError(`Import map cannot reference '${join(cwd(), 'file.js')}' as it's outside of the base directory '${basePath}'`);
113
+ const map = new ImportMap([inputFile1], pathToFileURL(cwd()).toString());
114
+ expect(() => map.getContents()).toThrowError(`Import map cannot reference '${join(cwd(), '..', 'file.js')}' as it's outside of the base directory '${cwd()}'`);
71
115
  });
72
116
  test('Writes import map file to disk', async () => {
73
117
  const file = await tmp.file();
@@ -8,6 +8,8 @@ interface Route {
8
8
  function: string;
9
9
  pattern: string;
10
10
  excluded_patterns: string[];
11
+ path?: string;
12
+ methods?: string[];
11
13
  }
12
14
  interface EdgeFunctionConfig {
13
15
  excluded_patterns: string[];
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { join } from 'path';
3
- import globToRegExp from 'glob-to-regexp';
3
+ import { wrapBundleError } from './bundle_error.js';
4
4
  import { parsePattern } from './declaration.js';
5
5
  import { getPackageVersion } from './package_json.js';
6
6
  import { nonNullable } from './utils/non_nullable.js';
@@ -26,13 +26,26 @@ const sanitizeEdgeFunctionConfig = (config) => {
26
26
  }
27
27
  return newConfig;
28
28
  };
29
- const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath, featureFlags) => {
29
+ const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => {
30
30
  if (excludedPath) {
31
31
  const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
32
- const excludedPatterns = paths.map((path) => pathToRegularExpression(path, featureFlags)).map(serializePattern);
32
+ const excludedPatterns = paths.map((path) => pathToRegularExpression(path)).map(serializePattern);
33
33
  manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
34
34
  }
35
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
+ };
36
49
  const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => {
37
50
  const preCacheRoutes = [];
38
51
  const postCacheRoutes = [];
@@ -42,7 +55,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
42
55
  if (manifestFunctionConfig[name] === undefined) {
43
56
  continue;
44
57
  }
45
- addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags);
58
+ addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
46
59
  manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError };
47
60
  }
48
61
  for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) {
@@ -50,7 +63,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
50
63
  if (manifestFunctionConfig[name] === undefined) {
51
64
  continue;
52
65
  }
53
- addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags);
66
+ addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
54
67
  manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest };
55
68
  }
56
69
  declarations.forEach((declaration) => {
@@ -65,6 +78,12 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
65
78
  pattern: serializePattern(pattern),
66
79
  excluded_patterns: excludedPattern.map(serializePattern),
67
80
  };
81
+ if ('method' in declaration) {
82
+ route.methods = normalizeMethods(declaration.method, func.name);
83
+ }
84
+ if ('path' in declaration) {
85
+ route.path = declaration.path;
86
+ }
68
87
  if (declaration.cache === "manual" /* Cache.Manual */) {
69
88
  postCacheRoutes.push(route);
70
89
  }
@@ -87,8 +106,8 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
87
106
  };
88
107
  return manifest;
89
108
  };
90
- const pathToRegularExpression = (path, featureFlags) => {
91
- if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_path_urlpattern) {
109
+ const pathToRegularExpression = (path) => {
110
+ try {
92
111
  const pattern = new ExtendedURLPattern({ pathname: path });
93
112
  // Removing the `^` and `$` delimiters because we'll need to modify what's
94
113
  // between them.
@@ -99,14 +118,9 @@ const pathToRegularExpression = (path, featureFlags) => {
99
118
  const normalizedSource = `^${source}\\/?$`;
100
119
  return normalizedSource;
101
120
  }
102
- // We use the global flag so that `globToRegExp` will not wrap the expression
103
- // with `^` and `$`. We'll do that ourselves.
104
- const regularExpression = globToRegExp(path, { flags: 'g' });
105
- // Wrapping the expression source with `^` and `$`. Also, adding an optional
106
- // trailing slash, so that a declaration of `path: "/foo"` matches requests
107
- // for both `/foo` and `/foo/`.
108
- const normalizedSource = `^${regularExpression.source}\\/?$`;
109
- return normalizedSource;
121
+ catch (error) {
122
+ throw wrapBundleError(error);
123
+ }
110
124
  };
111
125
  const getRegularExpression = (declaration, featureFlags) => {
112
126
  if ('pattern' in declaration) {
@@ -122,7 +136,7 @@ const getRegularExpression = (declaration, featureFlags) => {
122
136
  return declaration.pattern;
123
137
  }
124
138
  }
125
- return pathToRegularExpression(declaration.path, featureFlags);
139
+ return pathToRegularExpression(declaration.path);
126
140
  };
127
141
  const getExcludedRegularExpressions = (declaration, featureFlags) => {
128
142
  if ('excludedPattern' in declaration && declaration.excludedPattern) {
@@ -144,7 +158,7 @@ const getExcludedRegularExpressions = (declaration, featureFlags) => {
144
158
  }
145
159
  if ('path' in declaration && declaration.excludedPath) {
146
160
  const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath];
147
- return paths.map((path) => pathToRegularExpression(path, featureFlags));
161
+ return paths.map((path) => pathToRegularExpression(path));
148
162
  }
149
163
  return [];
150
164
  };
@@ -2,6 +2,7 @@ import { env } from 'process';
2
2
  import { test, expect, vi } from 'vitest';
3
3
  import { getRouteMatcher } from '../test/util.js';
4
4
  import { BundleFormat } from './bundle.js';
5
+ import { BundleError } from './bundle_error.js';
5
6
  import { generateManifest } from './manifest.js';
6
7
  test('Generates a manifest with different bundles', () => {
7
8
  const bundle1 = {
@@ -21,7 +22,7 @@ test('Generates a manifest with different bundles', () => {
21
22
  { asset: bundle1.hash + bundle1.extension, format: bundle1.format },
22
23
  { asset: bundle2.hash + bundle2.extension, format: bundle2.format },
23
24
  ];
24
- const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [] }];
25
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
25
26
  expect(manifest.bundles).toEqual(expectedBundles);
26
27
  expect(manifest.routes).toEqual(expectedRoutes);
27
28
  expect(manifest.bundler_version).toBe(env.npm_package_version);
@@ -39,9 +40,8 @@ test('Generates a manifest with display names', () => {
39
40
  declarations,
40
41
  functions,
41
42
  internalFunctionConfig,
42
- featureFlags: { edge_functions_path_urlpattern: true },
43
43
  });
44
- const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] }];
44
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
45
45
  expect(manifest.function_config).toEqual({
46
46
  'func-1': { name: 'Display Name' },
47
47
  });
@@ -61,9 +61,8 @@ test('Generates a manifest with a generator field', () => {
61
61
  declarations,
62
62
  functions,
63
63
  internalFunctionConfig,
64
- featureFlags: { edge_functions_path_urlpattern: true },
65
64
  });
66
- const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] }];
65
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
67
66
  const expectedFunctionConfig = { 'func-1': { generator: '@netlify/fake-plugin@1.0.0' } };
68
67
  expect(manifest.routes).toEqual(expectedRoutes);
69
68
  expect(manifest.function_config).toEqual(expectedFunctionConfig);
@@ -84,15 +83,19 @@ test('Generates a manifest with excluded paths and patterns', () => {
84
83
  bundles: [],
85
84
  declarations,
86
85
  functions,
87
- featureFlags: { edge_functions_path_urlpattern: true },
88
86
  });
89
87
  const expectedRoutes = [
90
- { function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: ['^/f1/exclude/?$'] },
91
- { function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excluded_patterns: ['^/f2/exclude$', '^/f2/exclude-as-well$'] },
88
+ { function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: ['^/f1/exclude/?$'], path: '/f1/*' },
89
+ {
90
+ function: 'func-2',
91
+ pattern: '^/f2(?:/(.*))/?$',
92
+ excluded_patterns: ['^/f2/exclude$', '^/f2/exclude-as-well$'],
93
+ },
92
94
  {
93
95
  function: 'func-3',
94
96
  pattern: '^(?:/(.*))/?$',
95
97
  excluded_patterns: ['^(?:/((?:.*)(?:/(?:.*))*))?(?:/(.*))\\.html/?$'],
98
+ path: '/*',
96
99
  },
97
100
  ];
98
101
  expect(manifest.routes).toEqual(expectedRoutes);
@@ -113,9 +116,8 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
113
116
  declarations,
114
117
  functions,
115
118
  userFunctionConfig,
116
- featureFlags: { edge_functions_path_urlpattern: true },
117
119
  });
118
- const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] }];
120
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
119
121
  expect(manifest.routes).toEqual(expectedRoutes);
120
122
  expect(manifest.function_config).toEqual({
121
123
  'func-1': { excluded_patterns: ['^/f1/exclude/?$'] },
@@ -190,18 +192,19 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
190
192
  functions,
191
193
  userFunctionConfig,
192
194
  internalFunctionConfig,
193
- featureFlags: { edge_functions_path_urlpattern: true },
194
195
  });
195
196
  expect(manifest.routes).toEqual([
196
197
  {
197
198
  function: 'customisation',
198
199
  pattern: '^/showcases(?:/(.*))/?$',
199
200
  excluded_patterns: [],
201
+ path: '/showcases/*',
200
202
  },
201
203
  {
202
204
  function: 'customisation',
203
205
  pattern: '^/checkout(?:/(.*))/?$',
204
206
  excluded_patterns: ['^(?:/(.*))/terms-and-conditions/?$'],
207
+ path: '/checkout/*',
205
208
  },
206
209
  ]);
207
210
  expect(manifest.function_config).toEqual({
@@ -216,6 +219,42 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
216
219
  expect(matcher('/checkout/scrooge-mc-duck-animation.css')).toBeUndefined();
217
220
  expect(matcher('/showcases/boho-style/expensive-chair.jpg')).toBeUndefined();
218
221
  });
222
+ test('URLPattern named groups are supported', () => {
223
+ const functions = [{ name: 'customisation', path: '/path/to/customisation.ts' }];
224
+ const declarations = [{ function: 'customisation', path: '/products/:productId' }];
225
+ const userFunctionConfig = {};
226
+ const internalFunctionConfig = {};
227
+ const manifest = generateManifest({
228
+ bundles: [],
229
+ declarations,
230
+ functions,
231
+ userFunctionConfig,
232
+ internalFunctionConfig,
233
+ });
234
+ expect(manifest.routes).toEqual([
235
+ {
236
+ function: 'customisation',
237
+ pattern: '^/products(?:/([^/]+?))/?$',
238
+ excluded_patterns: [],
239
+ path: '/products/:productId',
240
+ },
241
+ ]);
242
+ const matcher = getRouteMatcher(manifest);
243
+ expect(matcher('/products/jigsaw-doweling-jig')).toBeDefined();
244
+ });
245
+ test('Invalid Path patterns throw bundling errors', () => {
246
+ const functions = [{ name: 'customisation', path: '/path/to/customisation.ts' }];
247
+ const declarations = [{ function: 'customisation', path: '/https://foo.netlify.app/' }];
248
+ const userFunctionConfig = {};
249
+ const internalFunctionConfig = {};
250
+ expect(() => generateManifest({
251
+ bundles: [],
252
+ declarations,
253
+ functions,
254
+ userFunctionConfig,
255
+ internalFunctionConfig,
256
+ })).toThrowError(BundleError);
257
+ });
219
258
  test('Includes failure modes in manifest', () => {
220
259
  const functions = [
221
260
  { name: 'func-1', path: '/path/to/func-1.ts' },
@@ -247,7 +286,7 @@ test('Excludes functions for which there are function files but no matching conf
247
286
  ];
248
287
  const declarations = [{ function: 'func-1', path: '/f1' }];
249
288
  const manifest = generateManifest({ bundles: [bundle1], declarations, functions });
250
- const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [] }];
289
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
251
290
  expect(manifest.routes).toEqual(expectedRoutes);
252
291
  });
253
292
  test('Excludes functions for which there are config declarations but no matching function files', () => {
@@ -262,14 +301,14 @@ test('Excludes functions for which there are config declarations but no matching
262
301
  { function: 'func-2', path: '/f2' },
263
302
  ];
264
303
  const manifest = generateManifest({ bundles: [bundle1], declarations, functions });
265
- const expectedRoutes = [{ function: 'func-2', pattern: '^/f2/?$', excluded_patterns: [] }];
304
+ const expectedRoutes = [{ function: 'func-2', pattern: '^/f2/?$', excluded_patterns: [], path: '/f2' }];
266
305
  expect(manifest.routes).toEqual(expectedRoutes);
267
306
  });
268
307
  test('Generates a manifest without bundles', () => {
269
308
  const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
270
309
  const declarations = [{ function: 'func-1', path: '/f1' }];
271
310
  const manifest = generateManifest({ bundles: [], declarations, functions });
272
- const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [] }];
311
+ const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
273
312
  expect(manifest.bundles).toEqual([]);
274
313
  expect(manifest.routes).toEqual(expectedRoutes);
275
314
  expect(manifest.bundler_version).toBe(env.npm_package_version);
@@ -301,10 +340,12 @@ test('Generates a manifest with pre and post-cache routes', () => {
301
340
  { asset: bundle2.hash + bundle2.extension, format: bundle2.format },
302
341
  ];
303
342
  const expectedPreCacheRoutes = [
304
- { function: 'func-1', name: undefined, pattern: '^/f1/?$', excluded_patterns: [] },
305
- { function: 'func-2', name: undefined, pattern: '^/f2/?$', excluded_patterns: [] },
343
+ { function: 'func-1', name: undefined, pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' },
344
+ { function: 'func-2', name: undefined, pattern: '^/f2/?$', excluded_patterns: [], path: '/f2' },
345
+ ];
346
+ const expectedPostCacheRoutes = [
347
+ { function: 'func-3', name: undefined, pattern: '^/f3/?$', excluded_patterns: [], path: '/f3' },
306
348
  ];
307
- const expectedPostCacheRoutes = [{ function: 'func-3', name: undefined, pattern: '^/f3/?$', excluded_patterns: [] }];
308
349
  expect(manifest.bundles).toEqual(expectedBundles);
309
350
  expect(manifest.routes).toEqual(expectedPreCacheRoutes);
310
351
  expect(manifest.post_cache_routes).toEqual(expectedPostCacheRoutes);
@@ -320,8 +361,8 @@ test('Generates a manifest with layers', () => {
320
361
  { function: 'func-2', path: '/f2/*' },
321
362
  ];
322
363
  const expectedRoutes = [
323
- { function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] },
324
- { function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excluded_patterns: [] },
364
+ { function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' },
365
+ { function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excluded_patterns: [], path: '/f2/*' },
325
366
  ];
326
367
  const layers = [
327
368
  {
@@ -333,14 +374,12 @@ test('Generates a manifest with layers', () => {
333
374
  bundles: [],
334
375
  declarations,
335
376
  functions,
336
- featureFlags: { edge_functions_path_urlpattern: true },
337
377
  });
338
378
  const manifest2 = generateManifest({
339
379
  bundles: [],
340
380
  declarations,
341
381
  functions,
342
382
  layers,
343
- featureFlags: { edge_functions_path_urlpattern: true },
344
383
  });
345
384
  expect(manifest1.routes).toEqual(expectedRoutes);
346
385
  expect(manifest1.layers).toEqual([]);
@@ -47,6 +47,16 @@ declare const edgeManifestSchema: {
47
47
  generator: {
48
48
  type: string;
49
49
  };
50
+ path: {
51
+ type: string;
52
+ };
53
+ methods: {
54
+ type: string;
55
+ items: {
56
+ type: string;
57
+ enum: string[];
58
+ };
59
+ };
50
60
  };
51
61
  additionalProperties: boolean;
52
62
  };
@@ -79,6 +89,16 @@ declare const edgeManifestSchema: {
79
89
  generator: {
80
90
  type: string;
81
91
  };
92
+ path: {
93
+ type: string;
94
+ };
95
+ methods: {
96
+ type: string;
97
+ items: {
98
+ type: string;
99
+ enum: string[];
100
+ };
101
+ };
82
102
  };
83
103
  additionalProperties: boolean;
84
104
  };
@@ -28,6 +28,11 @@ const routesSchema = {
28
28
  },
29
29
  excluded_patterns: excludedPatternsSchema,
30
30
  generator: { type: 'string' },
31
+ path: { type: 'string' },
32
+ methods: {
33
+ type: 'array',
34
+ items: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] },
35
+ },
31
36
  },
32
37
  additionalProperties: false,
33
38
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "8.17.1",
3
+ "version": "8.19.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -58,7 +58,7 @@
58
58
  "@types/node": "^14.18.32",
59
59
  "@types/semver": "^7.3.9",
60
60
  "@types/uuid": "^9.0.0",
61
- "@vitest/coverage-v8": "^0.33.0",
61
+ "@vitest/coverage-v8": "^0.34.0",
62
62
  "archiver": "^5.3.1",
63
63
  "chalk": "^4.1.2",
64
64
  "cpy": "^9.0.1",
@@ -67,7 +67,7 @@
67
67
  "nock": "^13.2.4",
68
68
  "tar": "^6.1.11",
69
69
  "typescript": "^5.0.0",
70
- "vitest": "^0.33.0"
70
+ "vitest": "^0.34.0"
71
71
  },
72
72
  "engines": {
73
73
  "node": "^14.16.0 || >=16.0.0"
@@ -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",