@netlify/edge-bundler 8.16.4 → 8.17.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/config.ts +4 -1
- package/dist/node/bundler.d.ts +2 -1
- package/dist/node/bundler.js +3 -3
- package/dist/node/config.d.ts +7 -1
- package/dist/node/config.js +2 -1
- package/dist/node/config.test.js +39 -13
- package/dist/node/declaration.js +6 -1
- package/dist/node/declaration.test.js +6 -0
- package/dist/node/feature_flags.d.ts +2 -0
- package/dist/node/feature_flags.js +1 -0
- package/dist/node/manifest.js +25 -13
- package/dist/node/manifest.test.js +60 -21
- package/dist/node/server/server.js +1 -1
- package/dist/node/server/server.test.js +16 -1
- package/dist/node/utils/urlpattern.d.ts +4 -0
- package/dist/node/utils/urlpattern.js +3 -0
- package/package.json +2 -1
package/deno/config.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
const [functionURL, collectorURL, rawExitCodes] = Deno.args
|
|
1
|
+
const [functionURL, collectorURL, bootstrapURL, rawExitCodes] = Deno.args
|
|
2
2
|
const exitCodes = JSON.parse(rawExitCodes)
|
|
3
3
|
|
|
4
|
+
const { Netlify } = await import(bootstrapURL)
|
|
5
|
+
globalThis.Netlify = Netlify
|
|
6
|
+
|
|
4
7
|
let func
|
|
5
8
|
|
|
6
9
|
try {
|
package/dist/node/bundler.d.ts
CHANGED
|
@@ -14,8 +14,9 @@ interface BundleOptions {
|
|
|
14
14
|
onBeforeDownload?: OnBeforeDownloadHook;
|
|
15
15
|
systemLogger?: LogFunction;
|
|
16
16
|
internalSrcFolder?: string;
|
|
17
|
+
bootstrapURL?: string;
|
|
17
18
|
}
|
|
18
|
-
declare const bundle: (sourceDirectories: string[], distDirectory: string, tomlDeclarations?: Declaration[], { basePath: inputBasePath, cacheDirectory, configPath, debug, distImportMapPath, featureFlags: inputFeatureFlags, importMapPaths, onAfterDownload, onBeforeDownload, systemLogger, internalSrcFolder, }?: 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, bootstrapURL, }?: BundleOptions) => Promise<{
|
|
19
20
|
functions: import("./edge_function.js").EdgeFunction[];
|
|
20
21
|
manifest: import("./manifest.js").Manifest;
|
|
21
22
|
}>;
|
package/dist/node/bundler.js
CHANGED
|
@@ -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, } = {}) => {
|
|
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', } = {}) => {
|
|
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, logger)]);
|
|
67
|
-
const userConfigPromises = userFunctions.map(async (func) => [func.name, await getFunctionConfig(func, importMap, deno, logger)]);
|
|
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 })]);
|
|
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));
|
package/dist/node/config.d.ts
CHANGED
|
@@ -17,4 +17,10 @@ export interface FunctionConfig {
|
|
|
17
17
|
name?: string;
|
|
18
18
|
generator?: string;
|
|
19
19
|
}
|
|
20
|
-
export declare const getFunctionConfig: (func
|
|
20
|
+
export declare const getFunctionConfig: ({ func, importMap, deno, bootstrapURL, log, }: {
|
|
21
|
+
func: EdgeFunction;
|
|
22
|
+
importMap: ImportMap;
|
|
23
|
+
deno: DenoBridge;
|
|
24
|
+
bootstrapURL: string;
|
|
25
|
+
log: Logger;
|
|
26
|
+
}) => Promise<FunctionConfig>;
|
package/dist/node/config.js
CHANGED
|
@@ -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, log) => {
|
|
29
|
+
export const getFunctionConfig = async ({ func, importMap, deno, bootstrapURL, 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,6 +50,7 @@ export const getFunctionConfig = async (func, importMap, deno, log) => {
|
|
|
50
50
|
extractorPath,
|
|
51
51
|
pathToFileURL(func.path).href,
|
|
52
52
|
pathToFileURL(collector.path).href,
|
|
53
|
+
bootstrapURL,
|
|
53
54
|
JSON.stringify(ConfigExitCode),
|
|
54
55
|
], { rejectOnExitCode: false });
|
|
55
56
|
if (exitCode !== ConfigExitCode.Success) {
|
package/dist/node/config.test.js
CHANGED
|
@@ -9,6 +9,7 @@ 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';
|
|
12
13
|
const importMapFile = {
|
|
13
14
|
baseURL: new URL('file:///some/path/import-map.json'),
|
|
14
15
|
imports: {
|
|
@@ -114,9 +115,15 @@ describe('`getFunctionConfig` extracts configuration properties from function fi
|
|
|
114
115
|
const path = join(tmpDir, `${func.name}.js`);
|
|
115
116
|
await fs.writeFile(path, func.source);
|
|
116
117
|
const funcCall = () => getFunctionConfig({
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
func: {
|
|
119
|
+
name: func.name,
|
|
120
|
+
path,
|
|
121
|
+
},
|
|
122
|
+
importMap: new ImportMap([importMapFile]),
|
|
123
|
+
deno,
|
|
124
|
+
log: logger,
|
|
125
|
+
bootstrapURL,
|
|
126
|
+
});
|
|
120
127
|
if (func.error) {
|
|
121
128
|
await expect(funcCall()).rejects.toThrowError(func.error);
|
|
122
129
|
}
|
|
@@ -149,6 +156,7 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
149
156
|
const result = await bundle([internalDirectory, userDirectory], distPath, declarations, {
|
|
150
157
|
basePath,
|
|
151
158
|
configPath: join(internalDirectory, 'config.json'),
|
|
159
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
152
160
|
});
|
|
153
161
|
const generatedFiles = await fs.readdir(distPath);
|
|
154
162
|
expect(result.functions.length).toBe(7);
|
|
@@ -165,7 +173,7 @@ test('Loads function paths from the in-source `config` function', async () => {
|
|
|
165
173
|
expect(routes[2]).toEqual({ function: 'framework-func1', pattern: '^/framework-func1/?$', excluded_patterns: [] });
|
|
166
174
|
expect(routes[3]).toEqual({ function: 'user-func1', pattern: '^/user-func1/?$', excluded_patterns: [] });
|
|
167
175
|
expect(routes[4]).toEqual({ function: 'user-func3', pattern: '^/user-func3/?$', excluded_patterns: [] });
|
|
168
|
-
expect(routes[5]).toEqual({ function: 'user-func5', pattern: '^/user-func5
|
|
176
|
+
expect(routes[5]).toEqual({ function: 'user-func5', pattern: '^/user-func5(?:/(.*))/?$', excluded_patterns: [] });
|
|
169
177
|
expect(postCacheRoutes.length).toBe(1);
|
|
170
178
|
expect(postCacheRoutes[0]).toEqual({ function: 'user-func4', pattern: '^/user-func4/?$', excluded_patterns: [] });
|
|
171
179
|
expect(Object.keys(functionConfig)).toHaveLength(1);
|
|
@@ -193,9 +201,15 @@ test('Passes validation if default export exists and is a function', async () =>
|
|
|
193
201
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
194
202
|
await fs.writeFile(path, func.source);
|
|
195
203
|
await expect(getFunctionConfig({
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
func: {
|
|
205
|
+
name: func.name,
|
|
206
|
+
path,
|
|
207
|
+
},
|
|
208
|
+
importMap: new ImportMap([importMapFile]),
|
|
209
|
+
deno,
|
|
210
|
+
log: logger,
|
|
211
|
+
bootstrapURL,
|
|
212
|
+
})).resolves.not.toThrow();
|
|
199
213
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
200
214
|
});
|
|
201
215
|
test('Fails validation if default export is not function', async () => {
|
|
@@ -217,9 +231,15 @@ test('Fails validation if default export is not function', async () => {
|
|
|
217
231
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
218
232
|
await fs.writeFile(path, func.source);
|
|
219
233
|
const config = getFunctionConfig({
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
234
|
+
func: {
|
|
235
|
+
name: func.name,
|
|
236
|
+
path,
|
|
237
|
+
},
|
|
238
|
+
importMap: new ImportMap([importMapFile]),
|
|
239
|
+
deno,
|
|
240
|
+
log: logger,
|
|
241
|
+
bootstrapURL,
|
|
242
|
+
});
|
|
223
243
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
224
244
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
225
245
|
});
|
|
@@ -241,9 +261,15 @@ test('Fails validation if default export is not present', async () => {
|
|
|
241
261
|
const path = join(tmpDir, `${func.name}.ts`);
|
|
242
262
|
await fs.writeFile(path, func.source);
|
|
243
263
|
const config = getFunctionConfig({
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
264
|
+
func: {
|
|
265
|
+
name: func.name,
|
|
266
|
+
path,
|
|
267
|
+
},
|
|
268
|
+
importMap: new ImportMap([importMapFile]),
|
|
269
|
+
deno,
|
|
270
|
+
log: logger,
|
|
271
|
+
bootstrapURL,
|
|
272
|
+
});
|
|
247
273
|
await expect(config).rejects.toThrowError(invalidDefaultExportErr(path));
|
|
248
274
|
await rm(tmpDir, { force: true, recursive: true, maxRetries: 10 });
|
|
249
275
|
});
|
package/dist/node/declaration.js
CHANGED
|
@@ -66,7 +66,12 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
|
|
|
66
66
|
// Validates and normalizes a pattern so that it's a valid regular expression
|
|
67
67
|
// in Go, which is the engine used by our edge nodes.
|
|
68
68
|
export const parsePattern = (pattern) => {
|
|
69
|
-
|
|
69
|
+
let enclosedPattern = pattern;
|
|
70
|
+
if (!pattern.startsWith('^'))
|
|
71
|
+
enclosedPattern = `^${enclosedPattern}`;
|
|
72
|
+
if (!pattern.endsWith('$'))
|
|
73
|
+
enclosedPattern = `${enclosedPattern}$`;
|
|
74
|
+
const regexp = new RegExp(enclosedPattern);
|
|
70
75
|
const newRegexp = regexpAST.transform(regexp, {
|
|
71
76
|
Assertion(path) {
|
|
72
77
|
// Lookaheads are not supported. If we find one, throw an error.
|
|
@@ -126,3 +126,9 @@ test('Escapes front slashes in a regex pattern', () => {
|
|
|
126
126
|
const actual = parsePattern(regexPattern);
|
|
127
127
|
expect(actual).toEqual(expected);
|
|
128
128
|
});
|
|
129
|
+
test('Ensures pattern match on the whole path', () => {
|
|
130
|
+
const regexPattern = '/foo/.*/bar';
|
|
131
|
+
const expected = '^\\/foo\\/.*\\/bar$';
|
|
132
|
+
const actual = parsePattern(regexPattern);
|
|
133
|
+
expect(actual).toEqual(expected);
|
|
134
|
+
});
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
declare const defaultFlags: {
|
|
2
2
|
edge_functions_fail_unsupported_regex: boolean;
|
|
3
|
+
edge_functions_path_urlpattern: boolean;
|
|
3
4
|
};
|
|
4
5
|
type FeatureFlag = keyof typeof defaultFlags;
|
|
5
6
|
type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
|
|
6
7
|
declare const getFlags: (input?: Record<string, boolean>, flags?: {
|
|
7
8
|
edge_functions_fail_unsupported_regex: boolean;
|
|
9
|
+
edge_functions_path_urlpattern: boolean;
|
|
8
10
|
}) => FeatureFlags;
|
|
9
11
|
export { defaultFlags, getFlags };
|
|
10
12
|
export type { FeatureFlag, FeatureFlags };
|
package/dist/node/manifest.js
CHANGED
|
@@ -4,6 +4,7 @@ import globToRegExp from 'glob-to-regexp';
|
|
|
4
4
|
import { parsePattern } from './declaration.js';
|
|
5
5
|
import { getPackageVersion } from './package_json.js';
|
|
6
6
|
import { nonNullable } from './utils/non_nullable.js';
|
|
7
|
+
import { ExtendedURLPattern } from './utils/urlpattern.js';
|
|
7
8
|
const removeEmptyConfigValues = (functionConfig) => Object.entries(functionConfig).reduce((acc, [key, value]) => {
|
|
8
9
|
if (value && !(Array.isArray(value) && value.length === 0)) {
|
|
9
10
|
return { ...acc, [key]: value };
|
|
@@ -25,10 +26,10 @@ const sanitizeEdgeFunctionConfig = (config) => {
|
|
|
25
26
|
}
|
|
26
27
|
return newConfig;
|
|
27
28
|
};
|
|
28
|
-
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath) => {
|
|
29
|
+
const addExcludedPatterns = (name, manifestFunctionConfig, excludedPath, featureFlags) => {
|
|
29
30
|
if (excludedPath) {
|
|
30
31
|
const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
|
|
31
|
-
const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern);
|
|
32
|
+
const excludedPatterns = paths.map((path) => pathToRegularExpression(path, featureFlags)).map(serializePattern);
|
|
32
33
|
manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
|
|
33
34
|
}
|
|
34
35
|
};
|
|
@@ -41,7 +42,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
41
42
|
if (manifestFunctionConfig[name] === undefined) {
|
|
42
43
|
continue;
|
|
43
44
|
}
|
|
44
|
-
addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
|
|
45
|
+
addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags);
|
|
45
46
|
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError };
|
|
46
47
|
}
|
|
47
48
|
for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) {
|
|
@@ -49,7 +50,7 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
49
50
|
if (manifestFunctionConfig[name] === undefined) {
|
|
50
51
|
continue;
|
|
51
52
|
}
|
|
52
|
-
addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
|
|
53
|
+
addExcludedPatterns(name, manifestFunctionConfig, excludedPath, featureFlags);
|
|
53
54
|
manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest };
|
|
54
55
|
}
|
|
55
56
|
declarations.forEach((declaration) => {
|
|
@@ -57,8 +58,8 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
57
58
|
if (func === undefined) {
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
|
-
const pattern = getRegularExpression(declaration, featureFlags
|
|
61
|
-
const excludedPattern = getExcludedRegularExpressions(declaration, featureFlags
|
|
61
|
+
const pattern = getRegularExpression(declaration, featureFlags);
|
|
62
|
+
const excludedPattern = getExcludedRegularExpressions(declaration, featureFlags);
|
|
62
63
|
const route = {
|
|
63
64
|
function: func.name,
|
|
64
65
|
pattern: serializePattern(pattern),
|
|
@@ -86,7 +87,18 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
|
|
|
86
87
|
};
|
|
87
88
|
return manifest;
|
|
88
89
|
};
|
|
89
|
-
const pathToRegularExpression = (path) => {
|
|
90
|
+
const pathToRegularExpression = (path, featureFlags) => {
|
|
91
|
+
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_path_urlpattern) {
|
|
92
|
+
const pattern = new ExtendedURLPattern({ pathname: path });
|
|
93
|
+
// Removing the `^` and `$` delimiters because we'll need to modify what's
|
|
94
|
+
// between them.
|
|
95
|
+
const source = pattern.regexp.pathname.source.slice(1, -1);
|
|
96
|
+
// Wrapping the expression source with `^` and `$`. Also, adding an optional
|
|
97
|
+
// trailing slash, so that a declaration of `path: "/foo"` matches requests
|
|
98
|
+
// for both `/foo` and `/foo/`.
|
|
99
|
+
const normalizedSource = `^${source}\\/?$`;
|
|
100
|
+
return normalizedSource;
|
|
101
|
+
}
|
|
90
102
|
// We use the global flag so that `globToRegExp` will not wrap the expression
|
|
91
103
|
// with `^` and `$`. We'll do that ourselves.
|
|
92
104
|
const regularExpression = globToRegExp(path, { flags: 'g' });
|
|
@@ -96,23 +108,23 @@ const pathToRegularExpression = (path) => {
|
|
|
96
108
|
const normalizedSource = `^${regularExpression.source}\\/?$`;
|
|
97
109
|
return normalizedSource;
|
|
98
110
|
};
|
|
99
|
-
const getRegularExpression = (declaration,
|
|
111
|
+
const getRegularExpression = (declaration, featureFlags) => {
|
|
100
112
|
if ('pattern' in declaration) {
|
|
101
113
|
try {
|
|
102
114
|
return parsePattern(declaration.pattern);
|
|
103
115
|
}
|
|
104
116
|
catch (error) {
|
|
105
117
|
// eslint-disable-next-line max-depth
|
|
106
|
-
if (
|
|
118
|
+
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_fail_unsupported_regex) {
|
|
107
119
|
throw new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`);
|
|
108
120
|
}
|
|
109
121
|
console.warn(`Function '${declaration.function}' uses an unsupported regular expression and will not be invoked: ${error.message}`);
|
|
110
122
|
return declaration.pattern;
|
|
111
123
|
}
|
|
112
124
|
}
|
|
113
|
-
return pathToRegularExpression(declaration.path);
|
|
125
|
+
return pathToRegularExpression(declaration.path, featureFlags);
|
|
114
126
|
};
|
|
115
|
-
const getExcludedRegularExpressions = (declaration,
|
|
127
|
+
const getExcludedRegularExpressions = (declaration, featureFlags) => {
|
|
116
128
|
if ('excludedPattern' in declaration && declaration.excludedPattern) {
|
|
117
129
|
const excludedPatterns = Array.isArray(declaration.excludedPattern)
|
|
118
130
|
? declaration.excludedPattern
|
|
@@ -122,7 +134,7 @@ const getExcludedRegularExpressions = (declaration, failUnsupportedRegex = false
|
|
|
122
134
|
return parsePattern(excludedPattern);
|
|
123
135
|
}
|
|
124
136
|
catch (error) {
|
|
125
|
-
if (
|
|
137
|
+
if (featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_functions_fail_unsupported_regex) {
|
|
126
138
|
throw new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`);
|
|
127
139
|
}
|
|
128
140
|
console.warn(`Function '${declaration.function}' uses an unsupported regular expression and will therefore not be invoked: ${error.message}`);
|
|
@@ -132,7 +144,7 @@ const getExcludedRegularExpressions = (declaration, failUnsupportedRegex = false
|
|
|
132
144
|
}
|
|
133
145
|
if ('path' in declaration && declaration.excludedPath) {
|
|
134
146
|
const paths = Array.isArray(declaration.excludedPath) ? declaration.excludedPath : [declaration.excludedPath];
|
|
135
|
-
return paths.map(pathToRegularExpression);
|
|
147
|
+
return paths.map((path) => pathToRegularExpression(path, featureFlags));
|
|
136
148
|
}
|
|
137
149
|
return [];
|
|
138
150
|
};
|
|
@@ -34,8 +34,14 @@ test('Generates a manifest with display names', () => {
|
|
|
34
34
|
name: 'Display Name',
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
|
-
const manifest = generateManifest({
|
|
38
|
-
|
|
37
|
+
const manifest = generateManifest({
|
|
38
|
+
bundles: [],
|
|
39
|
+
declarations,
|
|
40
|
+
functions,
|
|
41
|
+
internalFunctionConfig,
|
|
42
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
43
|
+
});
|
|
44
|
+
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] }];
|
|
39
45
|
expect(manifest.function_config).toEqual({
|
|
40
46
|
'func-1': { name: 'Display Name' },
|
|
41
47
|
});
|
|
@@ -50,8 +56,14 @@ test('Generates a manifest with a generator field', () => {
|
|
|
50
56
|
generator: '@netlify/fake-plugin@1.0.0',
|
|
51
57
|
},
|
|
52
58
|
};
|
|
53
|
-
const manifest = generateManifest({
|
|
54
|
-
|
|
59
|
+
const manifest = generateManifest({
|
|
60
|
+
bundles: [],
|
|
61
|
+
declarations,
|
|
62
|
+
functions,
|
|
63
|
+
internalFunctionConfig,
|
|
64
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
65
|
+
});
|
|
66
|
+
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] }];
|
|
55
67
|
const expectedFunctionConfig = { 'func-1': { generator: '@netlify/fake-plugin@1.0.0' } };
|
|
56
68
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
57
69
|
expect(manifest.function_config).toEqual(expectedFunctionConfig);
|
|
@@ -65,14 +77,23 @@ test('Generates a manifest with excluded paths and patterns', () => {
|
|
|
65
77
|
];
|
|
66
78
|
const declarations = [
|
|
67
79
|
{ function: 'func-1', path: '/f1/*', excludedPath: '/f1/exclude' },
|
|
68
|
-
{ function: 'func-2', pattern: '^/f2
|
|
80
|
+
{ function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excludedPattern: ['^/f2/exclude$', '^/f2/exclude-as-well$'] },
|
|
69
81
|
{ function: 'func-3', path: '/*', excludedPath: '/**/*.html' },
|
|
70
82
|
];
|
|
71
|
-
const manifest = generateManifest({
|
|
83
|
+
const manifest = generateManifest({
|
|
84
|
+
bundles: [],
|
|
85
|
+
declarations,
|
|
86
|
+
functions,
|
|
87
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
88
|
+
});
|
|
72
89
|
const expectedRoutes = [
|
|
73
|
-
{ function: 'func-1', pattern: '^/f1
|
|
74
|
-
{ function: 'func-2', pattern: '^/f2
|
|
75
|
-
{
|
|
90
|
+
{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: ['^/f1/exclude/?$'] },
|
|
91
|
+
{ function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excluded_patterns: ['^/f2/exclude$', '^/f2/exclude-as-well$'] },
|
|
92
|
+
{
|
|
93
|
+
function: 'func-3',
|
|
94
|
+
pattern: '^(?:/(.*))/?$',
|
|
95
|
+
excluded_patterns: ['^(?:/((?:.*)(?:/(?:.*))*))?(?:/(.*))\\.html/?$'],
|
|
96
|
+
},
|
|
76
97
|
];
|
|
77
98
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
78
99
|
expect(manifest.function_config).toEqual({});
|
|
@@ -87,8 +108,14 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
|
|
|
87
108
|
const userFunctionConfig = {
|
|
88
109
|
'func-1': { excludedPath: '/f1/exclude' },
|
|
89
110
|
};
|
|
90
|
-
const manifest = generateManifest({
|
|
91
|
-
|
|
111
|
+
const manifest = generateManifest({
|
|
112
|
+
bundles: [],
|
|
113
|
+
declarations,
|
|
114
|
+
functions,
|
|
115
|
+
userFunctionConfig,
|
|
116
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
117
|
+
});
|
|
118
|
+
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] }];
|
|
92
119
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
93
120
|
expect(manifest.function_config).toEqual({
|
|
94
121
|
'func-1': { excluded_patterns: ['^/f1/exclude/?$'] },
|
|
@@ -102,7 +129,7 @@ test('Filters out internal in-source configurations in user created functions',
|
|
|
102
129
|
];
|
|
103
130
|
const declarations = [
|
|
104
131
|
{ function: 'func-1', path: '/f1/*' },
|
|
105
|
-
{ function: 'func-2', pattern: '^/f2
|
|
132
|
+
{ function: 'func-2', pattern: '^/f2(?:/(.*))/?$' },
|
|
106
133
|
];
|
|
107
134
|
const userFunctionConfig = {
|
|
108
135
|
'func-1': {
|
|
@@ -163,22 +190,23 @@ test('excludedPath from ISC goes into function_config, TOML goes into routes', (
|
|
|
163
190
|
functions,
|
|
164
191
|
userFunctionConfig,
|
|
165
192
|
internalFunctionConfig,
|
|
193
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
166
194
|
});
|
|
167
195
|
expect(manifest.routes).toEqual([
|
|
168
196
|
{
|
|
169
197
|
function: 'customisation',
|
|
170
|
-
pattern: '^/showcases
|
|
198
|
+
pattern: '^/showcases(?:/(.*))/?$',
|
|
171
199
|
excluded_patterns: [],
|
|
172
200
|
},
|
|
173
201
|
{
|
|
174
202
|
function: 'customisation',
|
|
175
|
-
pattern: '^/checkout
|
|
176
|
-
excluded_patterns: ['
|
|
203
|
+
pattern: '^/checkout(?:/(.*))/?$',
|
|
204
|
+
excluded_patterns: ['^(?:/(.*))/terms-and-conditions/?$'],
|
|
177
205
|
},
|
|
178
206
|
]);
|
|
179
207
|
expect(manifest.function_config).toEqual({
|
|
180
208
|
customisation: {
|
|
181
|
-
excluded_patterns: ['
|
|
209
|
+
excluded_patterns: ['^(?:/(.*))\\.css/?$', '^(?:/(.*))\\.jpg/?$'],
|
|
182
210
|
},
|
|
183
211
|
});
|
|
184
212
|
const matcher = getRouteMatcher(manifest);
|
|
@@ -195,7 +223,7 @@ test('Includes failure modes in manifest', () => {
|
|
|
195
223
|
];
|
|
196
224
|
const declarations = [
|
|
197
225
|
{ function: 'func-1', path: '/f1/*' },
|
|
198
|
-
{ function: 'func-2', pattern: '^/f2
|
|
226
|
+
{ function: 'func-2', pattern: '^/f2(?:/(.*))/?$' },
|
|
199
227
|
];
|
|
200
228
|
const userFunctionConfig = {
|
|
201
229
|
'func-1': {
|
|
@@ -292,8 +320,8 @@ test('Generates a manifest with layers', () => {
|
|
|
292
320
|
{ function: 'func-2', path: '/f2/*' },
|
|
293
321
|
];
|
|
294
322
|
const expectedRoutes = [
|
|
295
|
-
{ function: 'func-1', pattern: '^/f1
|
|
296
|
-
{ function: 'func-2', pattern: '^/f2
|
|
323
|
+
{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [] },
|
|
324
|
+
{ function: 'func-2', pattern: '^/f2(?:/(.*))/?$', excluded_patterns: [] },
|
|
297
325
|
];
|
|
298
326
|
const layers = [
|
|
299
327
|
{
|
|
@@ -301,8 +329,19 @@ test('Generates a manifest with layers', () => {
|
|
|
301
329
|
flag: 'edge_functions_onion_layer',
|
|
302
330
|
},
|
|
303
331
|
];
|
|
304
|
-
const manifest1 = generateManifest({
|
|
305
|
-
|
|
332
|
+
const manifest1 = generateManifest({
|
|
333
|
+
bundles: [],
|
|
334
|
+
declarations,
|
|
335
|
+
functions,
|
|
336
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
337
|
+
});
|
|
338
|
+
const manifest2 = generateManifest({
|
|
339
|
+
bundles: [],
|
|
340
|
+
declarations,
|
|
341
|
+
functions,
|
|
342
|
+
layers,
|
|
343
|
+
featureFlags: { edge_functions_path_urlpattern: true },
|
|
344
|
+
});
|
|
306
345
|
expect(manifest1.routes).toEqual(expectedRoutes);
|
|
307
346
|
expect(manifest1.layers).toEqual([]);
|
|
308
347
|
expect(manifest2.routes).toEqual(expectedRoutes);
|
|
@@ -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, logger)));
|
|
46
|
+
functionsConfig = await Promise.all(functions.map((func) => getFunctionConfig({ func, importMap, deno, bootstrapURL, log: logger })));
|
|
47
47
|
}
|
|
48
48
|
const success = await waitForServer(port, processRef.ps);
|
|
49
49
|
return {
|
|
@@ -27,6 +27,10 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
27
27
|
name: 'greet',
|
|
28
28
|
path: join(paths.internal, 'greet.ts'),
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
name: 'global_netlify',
|
|
32
|
+
path: join(paths.user, 'global_netlify.ts'),
|
|
33
|
+
},
|
|
30
34
|
];
|
|
31
35
|
const options = {
|
|
32
36
|
getFunctionsConfig: true,
|
|
@@ -35,7 +39,7 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
35
39
|
very_secret_secret: 'i love netlify',
|
|
36
40
|
}, options);
|
|
37
41
|
expect(success).toBe(true);
|
|
38
|
-
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}]);
|
|
42
|
+
expect(functionsConfig).toEqual([{ path: '/my-function' }, {}, { path: '/global-netlify' }]);
|
|
39
43
|
for (const key in functions) {
|
|
40
44
|
const graphEntry = graph === null || graph === void 0 ? void 0 : graph.modules.some(
|
|
41
45
|
// @ts-expect-error TODO: Module graph is currently not typed
|
|
@@ -60,4 +64,15 @@ test('Starts a server and serves requests for edge functions', async () => {
|
|
|
60
64
|
});
|
|
61
65
|
expect(response2.status).toBe(200);
|
|
62
66
|
expect(await response2.text()).toBe('HELLO!');
|
|
67
|
+
const response3 = await fetch(`http://0.0.0.0:${port}/global-netlify`, {
|
|
68
|
+
headers: {
|
|
69
|
+
'x-nf-edge-functions': 'global_netlify',
|
|
70
|
+
'x-ef-passthrough': 'passthrough',
|
|
71
|
+
'X-NF-Request-ID': uuidv4(),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
expect(await response3.json()).toEqual({
|
|
75
|
+
global: 'i love netlify',
|
|
76
|
+
local: 'i love netlify',
|
|
77
|
+
});
|
|
63
78
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.17.1",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"regexp-tree": "^0.1.24",
|
|
94
94
|
"semver": "^7.3.8",
|
|
95
95
|
"tmp-promise": "^3.0.3",
|
|
96
|
+
"urlpattern-polyfill": "8.0.2",
|
|
96
97
|
"uuid": "^9.0.0"
|
|
97
98
|
}
|
|
98
99
|
}
|