@netlify/edge-bundler 11.3.0 → 12.0.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/deno/lib/stage2.test.ts +58 -0
- package/dist/node/bridge.js +0 -1
- package/dist/node/bundle_error.js +0 -1
- package/dist/node/config.d.ts +2 -0
- package/dist/node/config.test.js +62 -4
- package/dist/node/declaration.js +1 -0
- package/dist/node/downloader.test.js +0 -1
- package/dist/node/finder.js +0 -1
- package/dist/node/manifest.d.ts +19 -0
- package/dist/node/manifest.js +38 -4
- package/dist/node/manifest.test.js +88 -7
- package/dist/node/npm_dependencies.js +0 -1
- package/dist/node/rate_limit.d.ts +25 -0
- package/dist/node/rate_limit.js +14 -0
- package/dist/node/server/util.js +0 -1
- package/dist/node/types.js +1 -0
- package/dist/node/utils/error.js +0 -2
- package/dist/node/validation/manifest/index.js +0 -1
- package/dist/node/validation/manifest/index.test.js +0 -1
- package/package.json +12 -25
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { assertEquals, assertStringIncludes } from 'https://deno.land/std@0.177.0/testing/asserts.ts'
|
|
2
|
+
|
|
3
|
+
import { join } from 'https://deno.land/std@0.177.0/path/mod.ts'
|
|
4
|
+
import { pathToFileURL } from 'https://deno.land/std@0.177.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
|
+
})
|
package/dist/node/bridge.js
CHANGED
|
@@ -157,7 +157,6 @@ class DenoBridge {
|
|
|
157
157
|
const options = { env, extendEnv };
|
|
158
158
|
const ps = DenoBridge.runWithBinary(binaryPath, args, { options, pipeOutput, stderr, stdout });
|
|
159
159
|
if (ref !== undefined) {
|
|
160
|
-
// eslint-disable-next-line no-param-reassign
|
|
161
160
|
ref.ps = ps;
|
|
162
161
|
}
|
|
163
162
|
}
|
|
@@ -21,7 +21,6 @@ class BundleError extends Error {
|
|
|
21
21
|
const wrapBundleError = (input, options) => {
|
|
22
22
|
if (input instanceof Error) {
|
|
23
23
|
if (input.message.includes("The module's source code could not be parsed")) {
|
|
24
|
-
// eslint-disable-next-line no-param-reassign
|
|
25
24
|
input.message = input.stderr;
|
|
26
25
|
}
|
|
27
26
|
return new BundleError(input, options);
|
package/dist/node/config.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DenoBridge } from './bridge.js';
|
|
|
2
2
|
import { EdgeFunction } from './edge_function.js';
|
|
3
3
|
import { ImportMap } from './import_map.js';
|
|
4
4
|
import { Logger } from './logger.js';
|
|
5
|
+
import { RateLimit } from './rate_limit.js';
|
|
5
6
|
export declare const enum Cache {
|
|
6
7
|
Off = "off",
|
|
7
8
|
Manual = "manual"
|
|
@@ -18,6 +19,7 @@ export interface FunctionConfig {
|
|
|
18
19
|
name?: string;
|
|
19
20
|
generator?: string;
|
|
20
21
|
method?: HTTPMethod | HTTPMethod[];
|
|
22
|
+
rateLimit?: RateLimit;
|
|
21
23
|
}
|
|
22
24
|
export declare const getFunctionConfig: ({ func, importMap, deno, log, }: {
|
|
23
25
|
func: EdgeFunction;
|
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
|
+
import { RateLimitAction, RateLimitAggregator } from './rate_limit.js';
|
|
12
13
|
const importMapFile = {
|
|
13
14
|
baseURL: new URL('file:///some/path/import-map.json'),
|
|
14
15
|
imports: {
|
|
@@ -64,7 +65,7 @@ const functions = [
|
|
|
64
65
|
},
|
|
65
66
|
{
|
|
66
67
|
testName: 'config with wrong onError',
|
|
67
|
-
name: '
|
|
68
|
+
name: 'func6',
|
|
68
69
|
source: `
|
|
69
70
|
export default async () => new Response("Hello from function two")
|
|
70
71
|
export const config = { onError: "foo" }
|
|
@@ -74,7 +75,7 @@ const functions = [
|
|
|
74
75
|
{
|
|
75
76
|
testName: 'config with `path`',
|
|
76
77
|
expectedConfig: { path: '/home' },
|
|
77
|
-
name: '
|
|
78
|
+
name: 'func7',
|
|
78
79
|
source: `
|
|
79
80
|
export default async () => new Response("Hello from function three")
|
|
80
81
|
|
|
@@ -89,17 +90,74 @@ const functions = [
|
|
|
89
90
|
name: 'a displayName',
|
|
90
91
|
onError: 'bypass',
|
|
91
92
|
},
|
|
92
|
-
name: '
|
|
93
|
+
name: 'func8',
|
|
93
94
|
source: `
|
|
94
95
|
export default async () => new Response("Hello from function three")
|
|
95
96
|
|
|
96
|
-
export const config = {
|
|
97
|
+
export const config = {
|
|
98
|
+
path: "/home",
|
|
97
99
|
generator: '@netlify/fake-plugin@1.0.0',
|
|
98
100
|
name: 'a displayName',
|
|
99
101
|
onError: 'bypass',
|
|
100
102
|
}
|
|
101
103
|
`,
|
|
102
104
|
},
|
|
105
|
+
{
|
|
106
|
+
testName: 'config with ratelimit',
|
|
107
|
+
expectedConfig: {
|
|
108
|
+
path: '/ratelimit',
|
|
109
|
+
name: 'a limit rate',
|
|
110
|
+
rateLimit: {
|
|
111
|
+
windowSize: 10,
|
|
112
|
+
windowLimit: 100,
|
|
113
|
+
aggregateBy: [RateLimitAggregator.IP, RateLimitAggregator.Domain],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
name: 'func9',
|
|
117
|
+
source: `
|
|
118
|
+
export default async () => new Response("Rate my limits")
|
|
119
|
+
|
|
120
|
+
export const config = {
|
|
121
|
+
path: "/ratelimit",
|
|
122
|
+
rateLimit: {
|
|
123
|
+
windowSize: 10,
|
|
124
|
+
windowLimit: 100,
|
|
125
|
+
aggregateBy: ["ip", "domain"],
|
|
126
|
+
},
|
|
127
|
+
name: 'a limit rate',
|
|
128
|
+
}
|
|
129
|
+
`,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
testName: 'config with rewrite',
|
|
133
|
+
expectedConfig: {
|
|
134
|
+
path: '/rewrite',
|
|
135
|
+
name: 'a limit rewrite',
|
|
136
|
+
rateLimit: {
|
|
137
|
+
action: RateLimitAction.Rewrite,
|
|
138
|
+
to: '/rewritten',
|
|
139
|
+
windowSize: 20,
|
|
140
|
+
windowLimit: 200,
|
|
141
|
+
aggregateBy: [RateLimitAggregator.IP, RateLimitAggregator.Domain],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
name: 'func9',
|
|
145
|
+
source: `
|
|
146
|
+
export default async () => new Response("Rate my limits")
|
|
147
|
+
|
|
148
|
+
export const config = {
|
|
149
|
+
path: "/rewrite",
|
|
150
|
+
rateLimit: {
|
|
151
|
+
action: "rewrite",
|
|
152
|
+
to: "/rewritten",
|
|
153
|
+
windowSize: 20,
|
|
154
|
+
windowLimit: 200,
|
|
155
|
+
aggregateBy: ["ip", "domain"],
|
|
156
|
+
},
|
|
157
|
+
name: 'a limit rewrite',
|
|
158
|
+
}
|
|
159
|
+
`,
|
|
160
|
+
},
|
|
103
161
|
];
|
|
104
162
|
describe('`getFunctionConfig` extracts configuration properties from function file', () => {
|
|
105
163
|
test.each(functions)('$testName', async (func) => {
|
package/dist/node/declaration.js
CHANGED
|
@@ -37,6 +37,7 @@ const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsV
|
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
39
|
// With an in-source config without a path, add the config to the declaration.
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
40
41
|
const { path, excludedPath, ...rest } = config;
|
|
41
42
|
declarations.push({ ...declaration, ...rest });
|
|
42
43
|
}
|
package/dist/node/finder.js
CHANGED
|
@@ -23,7 +23,6 @@ const findFunctionInDirectory = async (directory) => {
|
|
|
23
23
|
for (const candidatePath of candidatePaths) {
|
|
24
24
|
try {
|
|
25
25
|
const stats = await fs.stat(candidatePath);
|
|
26
|
-
// eslint-disable-next-line max-depth
|
|
27
26
|
if (stats.isFile()) {
|
|
28
27
|
functionPath = candidatePath;
|
|
29
28
|
break;
|
package/dist/node/manifest.d.ts
CHANGED
|
@@ -11,11 +11,30 @@ interface Route {
|
|
|
11
11
|
path?: string;
|
|
12
12
|
methods?: string[];
|
|
13
13
|
}
|
|
14
|
+
interface TrafficRules {
|
|
15
|
+
action: {
|
|
16
|
+
type: string;
|
|
17
|
+
config: {
|
|
18
|
+
rate_limit_config: {
|
|
19
|
+
algorithm: string;
|
|
20
|
+
window_size: number;
|
|
21
|
+
window_limit: number;
|
|
22
|
+
};
|
|
23
|
+
aggregate: {
|
|
24
|
+
keys: {
|
|
25
|
+
type: string;
|
|
26
|
+
}[];
|
|
27
|
+
};
|
|
28
|
+
to?: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
14
32
|
export interface EdgeFunctionConfig {
|
|
15
33
|
excluded_patterns: string[];
|
|
16
34
|
on_error?: string;
|
|
17
35
|
generator?: string;
|
|
18
36
|
name?: string;
|
|
37
|
+
traffic_rules?: TrafficRules;
|
|
19
38
|
}
|
|
20
39
|
interface Manifest {
|
|
21
40
|
bundler_version: string;
|
package/dist/node/manifest.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'path';
|
|
|
3
3
|
import { wrapBundleError } from './bundle_error.js';
|
|
4
4
|
import { normalizePattern } from './declaration.js';
|
|
5
5
|
import { getPackageVersion } from './package_json.js';
|
|
6
|
+
import { RateLimitAction, RateLimitAlgorithm, RateLimitAggregator } from './rate_limit.js';
|
|
6
7
|
import { nonNullable } from './utils/non_nullable.js';
|
|
7
8
|
import { ExtendedURLPattern } from './utils/urlpattern.js';
|
|
8
9
|
const removeEmptyConfigValues = (functionConfig) => Object.entries(functionConfig).reduce((acc, [key, value]) => {
|
|
@@ -52,21 +53,31 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
|
|
|
52
53
|
const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
|
|
53
54
|
const routedFunctions = new Set();
|
|
54
55
|
const declarationsWithoutFunction = new Set();
|
|
55
|
-
for (const [name, { excludedPath, onError }] of Object.entries(userFunctionConfig)) {
|
|
56
|
+
for (const [name, { excludedPath, onError, rateLimit }] of Object.entries(userFunctionConfig)) {
|
|
56
57
|
// If the config block is for a function that is not defined, discard it.
|
|
57
58
|
if (manifestFunctionConfig[name] === undefined) {
|
|
58
59
|
continue;
|
|
59
60
|
}
|
|
60
61
|
addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
|
|
61
|
-
manifestFunctionConfig[name] = {
|
|
62
|
+
manifestFunctionConfig[name] = {
|
|
63
|
+
...manifestFunctionConfig[name],
|
|
64
|
+
on_error: onError,
|
|
65
|
+
traffic_rules: getTrafficRulesConfig(rateLimit),
|
|
66
|
+
};
|
|
62
67
|
}
|
|
63
|
-
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
69
|
+
for (const [name, { excludedPath, path, onError, rateLimit, ...rest }] of Object.entries(internalFunctionConfig)) {
|
|
64
70
|
// If the config block is for a function that is not defined, discard it.
|
|
65
71
|
if (manifestFunctionConfig[name] === undefined) {
|
|
66
72
|
continue;
|
|
67
73
|
}
|
|
68
74
|
addExcludedPatterns(name, manifestFunctionConfig, excludedPath);
|
|
69
|
-
manifestFunctionConfig[name] = {
|
|
75
|
+
manifestFunctionConfig[name] = {
|
|
76
|
+
...manifestFunctionConfig[name],
|
|
77
|
+
on_error: onError,
|
|
78
|
+
traffic_rules: getTrafficRulesConfig(rateLimit),
|
|
79
|
+
...rest,
|
|
80
|
+
};
|
|
70
81
|
}
|
|
71
82
|
declarations.forEach((declaration) => {
|
|
72
83
|
const func = functions.find(({ name }) => declaration.function === name);
|
|
@@ -116,6 +127,29 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
|
|
|
116
127
|
const unroutedFunctions = functions.filter(({ name }) => !routedFunctions.has(name)).map(({ name }) => name);
|
|
117
128
|
return { declarationsWithoutFunction: [...declarationsWithoutFunction], manifest, unroutedFunctions };
|
|
118
129
|
};
|
|
130
|
+
const getTrafficRulesConfig = (rl) => {
|
|
131
|
+
if (rl === undefined) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const rateLimitAgg = Array.isArray(rl.aggregateBy) ? rl.aggregateBy : [RateLimitAggregator.Domain];
|
|
135
|
+
const rewriteConfig = 'to' in rl && typeof rl.to === 'string' ? { to: rl.to } : undefined;
|
|
136
|
+
return {
|
|
137
|
+
action: {
|
|
138
|
+
type: rl.action || RateLimitAction.Limit,
|
|
139
|
+
config: {
|
|
140
|
+
...rewriteConfig,
|
|
141
|
+
rate_limit_config: {
|
|
142
|
+
window_limit: rl.windowLimit,
|
|
143
|
+
window_size: rl.windowSize,
|
|
144
|
+
algorithm: RateLimitAlgorithm.SlidingWindow,
|
|
145
|
+
},
|
|
146
|
+
aggregate: {
|
|
147
|
+
keys: rateLimitAgg.map((agg) => ({ type: agg })),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
};
|
|
119
153
|
const pathToRegularExpression = (path) => {
|
|
120
154
|
if (!path) {
|
|
121
155
|
return null;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { env } from 'process';
|
|
2
1
|
import { test, expect } from 'vitest';
|
|
2
|
+
// @ts-expect-error current tsconfig.json doesn't allow this, but I don't want to change it
|
|
3
|
+
import { version } from '../package.json' assert { type: 'json' };
|
|
3
4
|
import { getRouteMatcher } from '../test/util.js';
|
|
4
5
|
import { BundleFormat } from './bundle.js';
|
|
5
6
|
import { BundleError } from './bundle_error.js';
|
|
6
7
|
import { generateManifest } from './manifest.js';
|
|
8
|
+
import { RateLimitAction, RateLimitAggregator } from './rate_limit.js';
|
|
7
9
|
test('Generates a manifest with different bundles', () => {
|
|
8
10
|
const bundle1 = {
|
|
9
11
|
extension: '.ext1',
|
|
@@ -25,7 +27,7 @@ test('Generates a manifest with different bundles', () => {
|
|
|
25
27
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
|
|
26
28
|
expect(manifest.bundles).toEqual(expectedBundles);
|
|
27
29
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
28
|
-
expect(manifest.bundler_version).toBe(
|
|
30
|
+
expect(manifest.bundler_version).toBe(version);
|
|
29
31
|
});
|
|
30
32
|
test('Generates a manifest with display names', () => {
|
|
31
33
|
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
@@ -46,7 +48,7 @@ test('Generates a manifest with display names', () => {
|
|
|
46
48
|
'func-1': { name: 'Display Name' },
|
|
47
49
|
});
|
|
48
50
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
49
|
-
expect(manifest.bundler_version).toBe(
|
|
51
|
+
expect(manifest.bundler_version).toBe(version);
|
|
50
52
|
});
|
|
51
53
|
test('Generates a manifest with a generator field', () => {
|
|
52
54
|
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
@@ -100,7 +102,7 @@ test('Generates a manifest with excluded paths and patterns', () => {
|
|
|
100
102
|
];
|
|
101
103
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
102
104
|
expect(manifest.function_config).toEqual({});
|
|
103
|
-
expect(manifest.bundler_version).toBe(
|
|
105
|
+
expect(manifest.bundler_version).toBe(version);
|
|
104
106
|
const matcher = getRouteMatcher(manifest);
|
|
105
107
|
expect((_a = matcher('/f1/hello')) === null || _a === void 0 ? void 0 : _a.function).toBe('func-1');
|
|
106
108
|
expect((_b = matcher('/grandparent/parent/child/grandchild.html')) === null || _b === void 0 ? void 0 : _b.function).toBeUndefined();
|
|
@@ -122,7 +124,7 @@ test('TOML-defined paths can be combined with ISC-defined excluded paths', () =>
|
|
|
122
124
|
expect(manifest.function_config).toEqual({
|
|
123
125
|
'func-1': { excluded_patterns: ['^/f1/exclude/?$'] },
|
|
124
126
|
});
|
|
125
|
-
expect(manifest.bundler_version).toBe(
|
|
127
|
+
expect(manifest.bundler_version).toBe(version);
|
|
126
128
|
});
|
|
127
129
|
test('Filters out internal in-source configurations in user created functions', () => {
|
|
128
130
|
const functions = [
|
|
@@ -311,7 +313,7 @@ test('Generates a manifest without bundles', () => {
|
|
|
311
313
|
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1/?$', excluded_patterns: [], path: '/f1' }];
|
|
312
314
|
expect(manifest.bundles).toEqual([]);
|
|
313
315
|
expect(manifest.routes).toEqual(expectedRoutes);
|
|
314
|
-
expect(manifest.bundler_version).toBe(
|
|
316
|
+
expect(manifest.bundler_version).toBe(version);
|
|
315
317
|
});
|
|
316
318
|
test('Generates a manifest with pre and post-cache routes', () => {
|
|
317
319
|
const bundle1 = {
|
|
@@ -349,7 +351,7 @@ test('Generates a manifest with pre and post-cache routes', () => {
|
|
|
349
351
|
expect(manifest.bundles).toEqual(expectedBundles);
|
|
350
352
|
expect(manifest.routes).toEqual(expectedPreCacheRoutes);
|
|
351
353
|
expect(manifest.post_cache_routes).toEqual(expectedPostCacheRoutes);
|
|
352
|
-
expect(manifest.bundler_version).toBe(
|
|
354
|
+
expect(manifest.bundler_version).toBe(version);
|
|
353
355
|
});
|
|
354
356
|
test('Generates a manifest with layers', () => {
|
|
355
357
|
const functions = [
|
|
@@ -433,3 +435,82 @@ test('Returns functions without a declaration and unrouted functions', () => {
|
|
|
433
435
|
expect(declarationsWithoutFunction).toEqual(['func-3']);
|
|
434
436
|
expect(unroutedFunctions).toEqual(['func-2', 'func-4']);
|
|
435
437
|
});
|
|
438
|
+
test('Generates a manifest with rate limit config', () => {
|
|
439
|
+
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
440
|
+
const declarations = [{ function: 'func-1', path: '/f1/*' }];
|
|
441
|
+
const userFunctionConfig = {
|
|
442
|
+
'func-1': { rateLimit: { windowLimit: 100, windowSize: 60 } },
|
|
443
|
+
};
|
|
444
|
+
const { manifest } = generateManifest({
|
|
445
|
+
bundles: [],
|
|
446
|
+
declarations,
|
|
447
|
+
functions,
|
|
448
|
+
userFunctionConfig,
|
|
449
|
+
});
|
|
450
|
+
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
|
|
451
|
+
const expectedFunctionConfig = {
|
|
452
|
+
'func-1': {
|
|
453
|
+
traffic_rules: {
|
|
454
|
+
action: {
|
|
455
|
+
type: 'rate_limit',
|
|
456
|
+
config: {
|
|
457
|
+
rate_limit_config: {
|
|
458
|
+
window_limit: 100,
|
|
459
|
+
window_size: 60,
|
|
460
|
+
algorithm: 'sliding_window',
|
|
461
|
+
},
|
|
462
|
+
aggregate: {
|
|
463
|
+
keys: [{ type: 'domain' }],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
expect(manifest.routes).toEqual(expectedRoutes);
|
|
471
|
+
expect(manifest.function_config).toEqual(expectedFunctionConfig);
|
|
472
|
+
});
|
|
473
|
+
test('Generates a manifest with rewrite config', () => {
|
|
474
|
+
const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
|
|
475
|
+
const declarations = [{ function: 'func-1', path: '/f1/*' }];
|
|
476
|
+
const userFunctionConfig = {
|
|
477
|
+
'func-1': {
|
|
478
|
+
rateLimit: {
|
|
479
|
+
action: RateLimitAction.Rewrite,
|
|
480
|
+
to: '/new_path',
|
|
481
|
+
windowLimit: 100,
|
|
482
|
+
windowSize: 60,
|
|
483
|
+
aggregateBy: [RateLimitAggregator.Domain, RateLimitAggregator.IP],
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const { manifest } = generateManifest({
|
|
488
|
+
bundles: [],
|
|
489
|
+
declarations,
|
|
490
|
+
functions,
|
|
491
|
+
userFunctionConfig,
|
|
492
|
+
});
|
|
493
|
+
const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }];
|
|
494
|
+
const expectedFunctionConfig = {
|
|
495
|
+
'func-1': {
|
|
496
|
+
traffic_rules: {
|
|
497
|
+
action: {
|
|
498
|
+
type: 'rewrite',
|
|
499
|
+
config: {
|
|
500
|
+
to: '/new_path',
|
|
501
|
+
rate_limit_config: {
|
|
502
|
+
window_limit: 100,
|
|
503
|
+
window_size: 60,
|
|
504
|
+
algorithm: 'sliding_window',
|
|
505
|
+
},
|
|
506
|
+
aggregate: {
|
|
507
|
+
keys: [{ type: 'domain' }, { type: 'ip' }],
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
expect(manifest.routes).toEqual(expectedRoutes);
|
|
515
|
+
expect(manifest.function_config).toEqual(expectedFunctionConfig);
|
|
516
|
+
});
|
|
@@ -105,7 +105,6 @@ const getNPMSpecifiers = async ({ basePath, functions, importMap, environment, r
|
|
|
105
105
|
}
|
|
106
106
|
return fs.readFile(filePath, 'utf8');
|
|
107
107
|
},
|
|
108
|
-
// eslint-disable-next-line require-await
|
|
109
108
|
resolve: async (specifier, ...args) => {
|
|
110
109
|
// Start by checking whether the specifier matches any import map defined
|
|
111
110
|
// by the user.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare enum RateLimitAlgorithm {
|
|
2
|
+
SlidingWindow = "sliding_window"
|
|
3
|
+
}
|
|
4
|
+
export declare enum RateLimitAggregator {
|
|
5
|
+
Domain = "domain",
|
|
6
|
+
IP = "ip"
|
|
7
|
+
}
|
|
8
|
+
export declare enum RateLimitAction {
|
|
9
|
+
Limit = "rate_limit",
|
|
10
|
+
Rewrite = "rewrite"
|
|
11
|
+
}
|
|
12
|
+
interface SlidingWindow {
|
|
13
|
+
windowLimit: number;
|
|
14
|
+
windowSize: number;
|
|
15
|
+
}
|
|
16
|
+
export type RewriteActionConfig = SlidingWindow & {
|
|
17
|
+
to: string;
|
|
18
|
+
};
|
|
19
|
+
interface RateLimitConfig {
|
|
20
|
+
action?: RateLimitAction;
|
|
21
|
+
aggregateBy?: RateLimitAggregator | RateLimitAggregator[];
|
|
22
|
+
algorithm?: RateLimitAlgorithm;
|
|
23
|
+
}
|
|
24
|
+
export type RateLimit = RateLimitConfig & (SlidingWindow | RewriteActionConfig);
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export var RateLimitAlgorithm;
|
|
2
|
+
(function (RateLimitAlgorithm) {
|
|
3
|
+
RateLimitAlgorithm["SlidingWindow"] = "sliding_window";
|
|
4
|
+
})(RateLimitAlgorithm || (RateLimitAlgorithm = {}));
|
|
5
|
+
export var RateLimitAggregator;
|
|
6
|
+
(function (RateLimitAggregator) {
|
|
7
|
+
RateLimitAggregator["Domain"] = "domain";
|
|
8
|
+
RateLimitAggregator["IP"] = "ip";
|
|
9
|
+
})(RateLimitAggregator || (RateLimitAggregator = {}));
|
|
10
|
+
export var RateLimitAction;
|
|
11
|
+
(function (RateLimitAction) {
|
|
12
|
+
RateLimitAction["Limit"] = "rate_limit";
|
|
13
|
+
RateLimitAction["Rewrite"] = "rewrite";
|
|
14
|
+
})(RateLimitAction || (RateLimitAction = {}));
|
package/dist/node/server/util.js
CHANGED
package/dist/node/types.js
CHANGED
|
@@ -4,6 +4,7 @@ import fetch from 'node-fetch';
|
|
|
4
4
|
const TYPES_URL = 'https://edge.netlify.com';
|
|
5
5
|
const ensureLatestTypes = async (deno, logger, customTypesURL) => {
|
|
6
6
|
const typesURL = customTypesURL !== null && customTypesURL !== void 0 ? customTypesURL : TYPES_URL;
|
|
7
|
+
// eslint-disable-next-line prefer-const
|
|
7
8
|
let [localVersion, remoteVersion] = [await getLocalVersion(deno), ''];
|
|
8
9
|
try {
|
|
9
10
|
remoteVersion = await getRemoteVersion(typesURL);
|
package/dist/node/utils/error.js
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2
1
|
export const isNodeError = (error) => error instanceof Error;
|
|
3
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
2
|
export const isFileNotFoundError = (error) => isNodeError(error) && error.code === 'ENOENT';
|
|
@@ -5,7 +5,6 @@ import { validateManifest, ManifestValidationError } from './index.js';
|
|
|
5
5
|
// This only works if this is the same instance of chalk that better-ajv-errors uses
|
|
6
6
|
chalk.level = 0;
|
|
7
7
|
// Factory so we have a new object per test
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
8
|
const getBaseManifest = () => ({
|
|
10
9
|
bundles: [
|
|
11
10
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/edge-bundler",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "Intelligently prepare Netlify Edge Functions for deployment",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/node/index.js",
|
|
@@ -15,20 +15,10 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc",
|
|
17
17
|
"build:dev": "tsc -w",
|
|
18
|
-
"prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/",
|
|
19
|
-
"prepublishOnly": "npm ci && npm test",
|
|
20
18
|
"prepack": "npm run build",
|
|
21
|
-
"test": "run-s build
|
|
22
|
-
"format": "run-s format:check-fix:*",
|
|
23
|
-
"format:ci": "run-s format:check:*",
|
|
24
|
-
"format:check-fix:lint": "run-e format:check:lint format:fix:lint",
|
|
25
|
-
"format:check:lint": "cross-env-shell eslint $npm_package_config_eslint",
|
|
26
|
-
"format:fix:lint": "cross-env-shell eslint --fix $npm_package_config_eslint",
|
|
27
|
-
"format:check-fix:prettier": "run-e format:check:prettier format:fix:prettier",
|
|
28
|
-
"format:check:prettier": "cross-env-shell prettier --check $npm_package_config_prettier",
|
|
29
|
-
"format:fix:prettier": "cross-env-shell prettier --write $npm_package_config_prettier",
|
|
19
|
+
"test": "run-s build test:dev",
|
|
30
20
|
"test:dev": "run-s test:dev:*",
|
|
31
|
-
"test:ci": "run-s test:ci:*",
|
|
21
|
+
"test:ci": "run-s test:integration test:ci:*",
|
|
32
22
|
"test:dev:vitest": "vitest run",
|
|
33
23
|
"test:dev:vitest:watch": "vitest watch",
|
|
34
24
|
"test:dev:deno": "deno test --allow-all deno",
|
|
@@ -37,24 +27,21 @@
|
|
|
37
27
|
"test:integration": "node --experimental-modules test/integration/test.js",
|
|
38
28
|
"vendor": "deno vendor --force --output deno/vendor https://deno.land/x/eszip@v0.55.2/mod.ts https://deno.land/x/retry@v2.0.0/mod.ts https://deno.land/x/std@0.177.0/path/mod.ts"
|
|
39
29
|
},
|
|
40
|
-
"config": {
|
|
41
|
-
"eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{node,scripts,.github}/**/*.{js,ts,md,html}\" \"*.{js,ts,md,html}\"",
|
|
42
|
-
"prettier": "--ignore-path .gitignore --loglevel=warn \"{node,scripts,.github}/**/*.{js,ts,md,yml,json,html}\" \"*.{js,ts,yml,json,html}\" \".*.{js,ts,yml,json,html}\" \"!**/package-lock.json\" \"!package-lock.json\" \"!node/vendor/**\""
|
|
43
|
-
},
|
|
44
30
|
"keywords": [],
|
|
45
31
|
"license": "MIT",
|
|
46
|
-
"repository":
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/netlify/build.git",
|
|
35
|
+
"directory": "packages/edge-bundler"
|
|
36
|
+
},
|
|
47
37
|
"bugs": {
|
|
48
|
-
"url": "https://github.com/netlify/
|
|
38
|
+
"url": "https://github.com/netlify/build/issues"
|
|
49
39
|
},
|
|
50
40
|
"author": "Netlify Inc.",
|
|
51
41
|
"directories": {
|
|
52
42
|
"test": "test/node"
|
|
53
43
|
},
|
|
54
44
|
"devDependencies": {
|
|
55
|
-
"@commitlint/cli": "^17.0.0",
|
|
56
|
-
"@commitlint/config-conventional": "^17.0.0",
|
|
57
|
-
"@netlify/eslint-config-node": "^7.0.1",
|
|
58
45
|
"@types/glob-to-regexp": "^0.4.1",
|
|
59
46
|
"@types/node": "^14.18.32",
|
|
60
47
|
"@types/semver": "^7.3.9",
|
|
@@ -64,7 +51,6 @@
|
|
|
64
51
|
"chalk": "^4.1.2",
|
|
65
52
|
"cpy": "^9.0.1",
|
|
66
53
|
"cross-env": "^7.0.3",
|
|
67
|
-
"husky": "^8.0.0",
|
|
68
54
|
"nock": "^13.2.4",
|
|
69
55
|
"tar": "^6.1.11",
|
|
70
56
|
"typescript": "^5.0.0",
|
|
@@ -81,7 +67,7 @@
|
|
|
81
67
|
"better-ajv-errors": "^1.2.0",
|
|
82
68
|
"common-path-prefix": "^3.0.0",
|
|
83
69
|
"env-paths": "^3.0.0",
|
|
84
|
-
"esbuild": "0.20.
|
|
70
|
+
"esbuild": "0.20.2",
|
|
85
71
|
"execa": "^6.0.0",
|
|
86
72
|
"find-up": "^6.3.0",
|
|
87
73
|
"get-package-name": "^2.2.0",
|
|
@@ -97,5 +83,6 @@
|
|
|
97
83
|
"tmp-promise": "^3.0.3",
|
|
98
84
|
"urlpattern-polyfill": "8.0.2",
|
|
99
85
|
"uuid": "^9.0.0"
|
|
100
|
-
}
|
|
86
|
+
},
|
|
87
|
+
"gitHead": "cf765526c7970810cc1812ac0b2b3d9bb11ea926"
|
|
101
88
|
}
|