@netlify/edge-bundler 8.3.0 → 8.5.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.
- package/dist/node/declaration.d.ts +1 -0
- package/dist/node/declaration.js +29 -0
- package/dist/node/formats/javascript.js +1 -1
- package/dist/node/manifest.d.ts +1 -1
- package/dist/node/manifest.js +20 -5
- package/dist/node/manifest.test.js +11 -0
- package/package.json +2 -1
- package/deno/lib/stage2.test.ts +0 -58
|
@@ -15,4 +15,5 @@ type DeclarationWithPattern = BaseDeclaration & {
|
|
|
15
15
|
};
|
|
16
16
|
type Declaration = DeclarationWithPath | DeclarationWithPattern;
|
|
17
17
|
export declare const getDeclarationsFromConfig: (tomlDeclarations: Declaration[], functionsConfig: Record<string, FunctionConfig>, deployConfig: DeployConfig) => Declaration[];
|
|
18
|
+
export declare const parsePattern: (pattern: string) => string;
|
|
18
19
|
export { Declaration, DeclarationWithPath, DeclarationWithPattern };
|
package/dist/node/declaration.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import regexpAST from 'regexp-tree';
|
|
1
2
|
export const getDeclarationsFromConfig = (tomlDeclarations, functionsConfig, deployConfig) => {
|
|
2
3
|
var _a;
|
|
3
4
|
const declarations = [];
|
|
@@ -42,3 +43,31 @@ export const getDeclarationsFromConfig = (tomlDeclarations, functionsConfig, dep
|
|
|
42
43
|
}
|
|
43
44
|
return declarations;
|
|
44
45
|
};
|
|
46
|
+
// Validates and normalizes a pattern so that it's a valid regular expression
|
|
47
|
+
// in Go, which is the engine used by our edge nodes.
|
|
48
|
+
export const parsePattern = (pattern) => {
|
|
49
|
+
// Escaping forward slashes with back slashes.
|
|
50
|
+
const normalizedPattern = pattern.replace(/\//g, '\\/');
|
|
51
|
+
const regex = regexpAST.transform(`/${normalizedPattern}/`, {
|
|
52
|
+
Assertion(path) {
|
|
53
|
+
// Lookaheads are not supported. If we find one, throw an error.
|
|
54
|
+
if (path.node.kind === 'Lookahead') {
|
|
55
|
+
throw new Error('Regular expressions with lookaheads are not supported');
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
Group(path) {
|
|
59
|
+
// Named captured groups in JavaScript use a different syntax than in Go.
|
|
60
|
+
// If we find one, convert it to an unnamed capture group, which is valid
|
|
61
|
+
// in both engines.
|
|
62
|
+
if ('name' in path.node && path.node.name !== undefined) {
|
|
63
|
+
path.replace({
|
|
64
|
+
...path.node,
|
|
65
|
+
name: undefined,
|
|
66
|
+
nameRaw: undefined,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
// Strip leading and forward slashes.
|
|
72
|
+
return regex.toString().slice(1, -1);
|
|
73
|
+
};
|
|
@@ -3,7 +3,7 @@ import { join } from 'path';
|
|
|
3
3
|
import { env } from 'process';
|
|
4
4
|
import { pathToFileURL } from 'url';
|
|
5
5
|
import { deleteAsync } from 'del';
|
|
6
|
-
const BOOTSTRAP_LATEST = 'https://
|
|
6
|
+
const BOOTSTRAP_LATEST = 'https://63c7dea30898f20009dd4da8--edge.netlify.com/bootstrap/index-combined.ts';
|
|
7
7
|
const defaultFormatExportTypeError = (name) => `The Edge Function "${name}" has failed to load. Does it have a function as the default export?`;
|
|
8
8
|
const defaultFormatImpoortError = (name) => `There was an error with Edge Function "${name}".`;
|
|
9
9
|
const generateStage2 = async ({ distDirectory, fileName, formatExportTypeError, formatImportError, functions, }) => {
|
package/dist/node/manifest.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Bundle } from './bundle.js';
|
|
2
2
|
import { FunctionConfig } from './config.js';
|
|
3
|
-
import
|
|
3
|
+
import { Declaration } from './declaration.js';
|
|
4
4
|
import { EdgeFunction } from './edge_function.js';
|
|
5
5
|
import { Layer } from './layer.js';
|
|
6
6
|
interface Route {
|
package/dist/node/manifest.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import globToRegExp from 'glob-to-regexp';
|
|
4
|
+
import { parsePattern } from './declaration.js';
|
|
4
5
|
import { getPackageVersion } from './package_json.js';
|
|
5
6
|
import { nonNullable } from './utils/non_nullable.js';
|
|
6
|
-
|
|
7
|
+
// JavaScript regular expressions are converted to strings with leading and
|
|
8
|
+
// trailing slashes, so any slashes inside the expression itself are escaped
|
|
9
|
+
// as `//`. This function deserializes that back into a single slash, which
|
|
10
|
+
// is the format we want to use in the manifest.
|
|
11
|
+
const serializePattern = (pattern) => pattern.replace(/\\\//g, '/');
|
|
7
12
|
const sanitizeEdgeFunctionConfig = (config) => {
|
|
8
13
|
const newConfig = {};
|
|
9
14
|
for (const [name, functionConfig] of Object.entries(config)) {
|
|
@@ -69,17 +74,27 @@ const pathToRegularExpression = (path) => {
|
|
|
69
74
|
// trailing slash, so that a declaration of `path: "/foo"` matches requests
|
|
70
75
|
// for both `/foo` and `/foo/`.
|
|
71
76
|
const normalizedSource = `^${regularExpression.source}\\/?$`;
|
|
72
|
-
return
|
|
77
|
+
return normalizedSource;
|
|
73
78
|
};
|
|
74
79
|
const getRegularExpression = (declaration) => {
|
|
75
80
|
if ('pattern' in declaration) {
|
|
76
|
-
|
|
81
|
+
try {
|
|
82
|
+
return parsePattern(declaration.pattern);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`);
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
return pathToRegularExpression(declaration.path);
|
|
79
89
|
};
|
|
80
90
|
const getExcludedRegularExpression = (declaration) => {
|
|
81
|
-
if ('
|
|
82
|
-
|
|
91
|
+
if ('excludedPattern' in declaration && declaration.excludedPattern) {
|
|
92
|
+
try {
|
|
93
|
+
return parsePattern(declaration.excludedPattern);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw new Error(`Could not parse path declaration of function '${declaration.function}': ${error.message}`);
|
|
97
|
+
}
|
|
83
98
|
}
|
|
84
99
|
if ('path' in declaration && declaration.excludedPath) {
|
|
85
100
|
return pathToRegularExpression(declaration.excludedPath);
|
|
@@ -164,3 +164,14 @@ test('Generates a manifest with layers', () => {
|
|
|
164
164
|
expect(manifest2.routes).toEqual(expectedRoutes);
|
|
165
165
|
expect(manifest2.layers).toEqual(layers);
|
|
166
166
|
});
|
|
167
|
+
test('Throws an error if the regular expression contains a negative lookahead', () => {
|
|
168
|
+
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
169
|
+
const declarations = [{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }];
|
|
170
|
+
expect(() => generateManifest({ bundles: [], declarations, functions })).toThrowError(/^Could not parse path declaration of function 'func-1': Regular expressions with lookaheads are not supported$/);
|
|
171
|
+
});
|
|
172
|
+
test('Converts named capture groups to unnamed capture groups in regular expressions', () => {
|
|
173
|
+
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
174
|
+
const declarations = [{ function: 'func-1', pattern: '^/(?<name>\\w+)$' }];
|
|
175
|
+
const manifest = generateManifest({ bundles: [], declarations, functions });
|
|
176
|
+
expect(manifest.routes).toEqual([{ function: 'func-1', pattern: '^/(\\w+)$' }]);
|
|
177
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.5.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"p-retry": "^5.1.1",
|
|
91
91
|
"p-wait-for": "^4.1.0",
|
|
92
92
|
"path-key": "^4.0.0",
|
|
93
|
+
"regexp-tree": "^0.1.24",
|
|
93
94
|
"semver": "^7.3.5",
|
|
94
95
|
"tmp-promise": "^3.0.3",
|
|
95
96
|
"uuid": "^9.0.0"
|
package/deno/lib/stage2.test.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { assertEquals, assertStringIncludes } from 'https://deno.land/std@0.127.0/testing/asserts.ts'
|
|
2
|
-
|
|
3
|
-
import { join } from 'https://deno.land/std@0.155.0/path/mod.ts'
|
|
4
|
-
import { pathToFileURL } from 'https://deno.land/std@0.155.0/node/url.ts'
|
|
5
|
-
|
|
6
|
-
import { getStage2Entry } from './stage2.ts'
|
|
7
|
-
import { virtualRoot } from './consts.ts'
|
|
8
|
-
|
|
9
|
-
Deno.test('`getStage2Entry` returns a valid stage 2 file', async () => {
|
|
10
|
-
const directory = await Deno.makeTempDir()
|
|
11
|
-
const functions = [
|
|
12
|
-
{
|
|
13
|
-
name: 'func1',
|
|
14
|
-
path: join(directory, 'func1.ts'),
|
|
15
|
-
response: 'Hello from function 1',
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: 'func2',
|
|
19
|
-
path: join(directory, 'func2.ts'),
|
|
20
|
-
response: 'Hello from function 2',
|
|
21
|
-
},
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
for (const func of functions) {
|
|
25
|
-
const contents = `export default async () => new Response(${JSON.stringify(func.response)})`
|
|
26
|
-
|
|
27
|
-
await Deno.writeTextFile(func.path, contents)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const baseURL = pathToFileURL(directory)
|
|
31
|
-
const stage2 = getStage2Entry(
|
|
32
|
-
directory,
|
|
33
|
-
functions.map(({ name, path }) => ({ name, path })),
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
// Ensuring that the stage 2 paths have the virtual root before we strip it.
|
|
37
|
-
assertStringIncludes(stage2, virtualRoot)
|
|
38
|
-
|
|
39
|
-
// Replacing the virtual root with the URL of the temporary directory so that
|
|
40
|
-
// we can actually import the module.
|
|
41
|
-
const normalizedStage2 = stage2.replaceAll(virtualRoot, `${baseURL.href}/`)
|
|
42
|
-
|
|
43
|
-
const stage2Path = join(directory, 'stage2.ts')
|
|
44
|
-
const stage2URL = pathToFileURL(stage2Path)
|
|
45
|
-
|
|
46
|
-
await Deno.writeTextFile(stage2Path, normalizedStage2)
|
|
47
|
-
|
|
48
|
-
const mod = await import(stage2URL.href)
|
|
49
|
-
|
|
50
|
-
await Deno.remove(directory, { recursive: true })
|
|
51
|
-
|
|
52
|
-
for (const func of functions) {
|
|
53
|
-
const result = await mod.functions[func.name]()
|
|
54
|
-
|
|
55
|
-
assertEquals(await result.text(), func.response)
|
|
56
|
-
assertEquals(mod.metadata.functions[func.name].url, pathToFileURL(func.path).toString())
|
|
57
|
-
}
|
|
58
|
-
})
|