@netlify/edge-bundler 8.0.0 → 8.1.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.
@@ -75,6 +75,7 @@ const bundle = async (sourceDirectories, distDirectory, tomlDeclarations = [], {
75
75
  declarations,
76
76
  distDirectory,
77
77
  functions,
78
+ functionConfig: functionsWithConfig,
78
79
  importMap: importMapSpecifier,
79
80
  layers: deployConfig.layers,
80
81
  });
@@ -299,12 +299,12 @@ test('Loads declarations and import maps from the deploy configuration', async (
299
299
  expect(generatedFiles.length).toBe(2);
300
300
  const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8');
301
301
  const manifest = JSON.parse(manifestFile);
302
- const { bundles, routes } = manifest;
302
+ const { bundles, function_config: functionConfig } = manifest;
303
303
  expect(bundles.length).toBe(1);
304
304
  expect(bundles[0].format).toBe('eszip2');
305
305
  expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
306
306
  // respects excludedPath from deploy config
307
- expect(routes[1].excluded_pattern).toEqual('^/func2/skip/?$');
307
+ expect(functionConfig.func2).toEqual({ excluded_patterns: ['^/func2/skip/?$'] });
308
308
  await cleanup();
309
309
  });
310
310
  test("Ignores entries in `importMapPaths` that don't point to an existing import map file", async () => {
@@ -9,5 +9,6 @@ export declare const enum Cache {
9
9
  export interface FunctionConfig {
10
10
  cache?: Cache;
11
11
  path?: string | string[];
12
+ excludedPath?: string | string[];
12
13
  }
13
14
  export declare const getFunctionConfig: (func: EdgeFunction, importMap: ImportMap, deno: DenoBridge, log: Logger) => Promise<FunctionConfig>;
@@ -102,7 +102,7 @@ test('Ignores function paths from the in-source `config` function if the feature
102
102
  configPath: join(internalDirectory, 'config.json'),
103
103
  });
104
104
  const generatedFiles = await fs.readdir(distPath);
105
- expect(result.functions.length).toBe(6);
105
+ expect(result.functions.length).toBe(7);
106
106
  expect(generatedFiles.length).toBe(2);
107
107
  const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8');
108
108
  const manifest = JSON.parse(manifestFile);
@@ -135,22 +135,27 @@ test('Loads function paths from the in-source `config` function', async () => {
135
135
  },
136
136
  });
137
137
  const generatedFiles = await fs.readdir(distPath);
138
- expect(result.functions.length).toBe(6);
138
+ expect(result.functions.length).toBe(7);
139
139
  expect(generatedFiles.length).toBe(2);
140
140
  const manifestFile = await fs.readFile(resolve(distPath, 'manifest.json'), 'utf8');
141
141
  const manifest = JSON.parse(manifestFile);
142
- const { bundles, routes, post_cache_routes: postCacheRoutes } = manifest;
142
+ const { bundles, routes, post_cache_routes: postCacheRoutes, function_config: functionConfig } = manifest;
143
143
  expect(bundles.length).toBe(1);
144
144
  expect(bundles[0].format).toBe('eszip2');
145
145
  expect(generatedFiles.includes(bundles[0].asset)).toBe(true);
146
- expect(routes.length).toBe(5);
146
+ expect(routes.length).toBe(6);
147
147
  expect(routes[0]).toEqual({ function: 'framework-func2', pattern: '^/framework-func2/?$' });
148
148
  expect(routes[1]).toEqual({ function: 'user-func2', pattern: '^/user-func2/?$' });
149
149
  expect(routes[2]).toEqual({ function: 'framework-func1', pattern: '^/framework-func1/?$' });
150
150
  expect(routes[3]).toEqual({ function: 'user-func1', pattern: '^/user-func1/?$' });
151
151
  expect(routes[4]).toEqual({ function: 'user-func3', pattern: '^/user-func3/?$' });
152
+ expect(routes[5]).toEqual({ function: 'user-func5', pattern: '^/user-func5/.*/?$' });
152
153
  expect(postCacheRoutes.length).toBe(1);
153
154
  expect(postCacheRoutes[0]).toEqual({ function: 'user-func4', pattern: '^/user-func4/?$' });
155
+ expect(Object.keys(functionConfig)).toHaveLength(1);
156
+ expect(functionConfig['user-func5']).toEqual({
157
+ excluded_patterns: ['^/user-func5/excluded/?$'],
158
+ });
154
159
  await cleanup();
155
160
  });
156
161
  test('Passes validation if default export exists and is a function', async () => {
@@ -17,13 +17,13 @@ export const getDeclarationsFromConfig = (tomlDeclarations, functionsConfig, dep
17
17
  else if ((_a = config.path) === null || _a === void 0 ? void 0 : _a.length) {
18
18
  const paths = Array.isArray(config.path) ? config.path : [config.path];
19
19
  paths.forEach((path) => {
20
- declarations.push({ ...declaration, ...config, path });
20
+ declarations.push({ ...declaration, cache: config.cache, path });
21
21
  });
22
22
  // With an in-source config without a path, add the config to the declaration
23
23
  }
24
24
  else {
25
25
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
26
- const { path, ...rest } = config;
26
+ const { path, excludedPath, ...rest } = config;
27
27
  declarations.push({ ...declaration, ...rest });
28
28
  }
29
29
  functionsVisited.add(declaration.function);
@@ -31,13 +31,12 @@ export const getDeclarationsFromConfig = (tomlDeclarations, functionsConfig, dep
31
31
  // Finally, we must create declarations for functions that are not declared
32
32
  // in the TOML at all.
33
33
  for (const name in functionsConfig) {
34
- const { ...config } = functionsConfig[name];
35
- const { path } = functionsConfig[name];
34
+ const { cache, path } = functionsConfig[name];
36
35
  // If we have path specified create a declaration for each path
37
36
  if (!functionsVisited.has(name) && path) {
38
37
  const paths = Array.isArray(path) ? path : [path];
39
38
  paths.forEach((singlePath) => {
40
- declarations.push({ ...config, function: name, path: singlePath });
39
+ declarations.push({ cache, function: name, path: singlePath });
41
40
  });
42
41
  }
43
42
  }
@@ -1,19 +1,15 @@
1
1
  import type { Bundle } from './bundle.js';
2
+ import { FunctionConfig } from './config.js';
2
3
  import type { Declaration } from './declaration.js';
3
4
  import { EdgeFunction } from './edge_function.js';
4
5
  import { Layer } from './layer.js';
5
- interface GenerateManifestOptions {
6
- bundles?: Bundle[];
7
- declarations?: Declaration[];
8
- functions: EdgeFunction[];
9
- importMap?: string;
10
- layers?: Layer[];
11
- }
12
6
  interface Route {
13
7
  function: string;
14
8
  name?: string;
15
9
  pattern: string;
16
- excluded_pattern?: string;
10
+ }
11
+ interface EdgeFunctionConfig {
12
+ excluded_patterns: string[];
17
13
  }
18
14
  interface Manifest {
19
15
  bundler_version: string;
@@ -28,20 +24,24 @@ interface Manifest {
28
24
  }[];
29
25
  routes: Route[];
30
26
  post_cache_routes: Route[];
27
+ function_config: Record<string, EdgeFunctionConfig>;
28
+ }
29
+ interface GenerateManifestOptions {
30
+ bundles?: Bundle[];
31
+ declarations?: Declaration[];
32
+ functions: EdgeFunction[];
33
+ functionConfig?: Record<string, FunctionConfig>;
34
+ importMap?: string;
35
+ layers?: Layer[];
31
36
  }
32
37
  interface Route {
33
38
  function: string;
34
39
  name?: string;
35
40
  pattern: string;
36
41
  }
37
- declare const generateManifest: ({ bundles, declarations, functions, importMap, layers, }: GenerateManifestOptions) => Manifest;
38
- interface WriteManifestOptions {
39
- bundles: Bundle[];
40
- declarations: Declaration[];
42
+ declare const generateManifest: ({ bundles, declarations, functions, functionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest;
43
+ interface WriteManifestOptions extends GenerateManifestOptions {
41
44
  distDirectory: string;
42
- functions: EdgeFunction[];
43
- importMap?: string;
44
- layers?: Layer[];
45
45
  }
46
- declare const writeManifest: ({ bundles, declarations, distDirectory, functions, importMap, layers, }: WriteManifestOptions) => Promise<Manifest>;
46
+ declare const writeManifest: ({ distDirectory, ...rest }: WriteManifestOptions) => Promise<Manifest>;
47
47
  export { generateManifest, Manifest, writeManifest };
@@ -4,9 +4,26 @@ import globToRegExp from 'glob-to-regexp';
4
4
  import { getPackageVersion } from './package_json.js';
5
5
  import { nonNullable } from './utils/non_nullable.js';
6
6
  const serializePattern = (regex) => regex.source.replace(/\\\//g, '/');
7
- const generateManifest = ({ bundles = [], declarations = [], functions, importMap, layers = [], }) => {
7
+ const sanitizeEdgeFunctionConfig = (config) => {
8
+ const newConfig = {};
9
+ for (const [name, functionConfig] of Object.entries(config)) {
10
+ if (functionConfig.excluded_patterns.length !== 0) {
11
+ newConfig[name] = functionConfig;
12
+ }
13
+ }
14
+ return newConfig;
15
+ };
16
+ const generateManifest = ({ bundles = [], declarations = [], functions, functionConfig = {}, importMap, layers = [], }) => {
8
17
  const preCacheRoutes = [];
9
18
  const postCacheRoutes = [];
19
+ const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
20
+ for (const [name, { excludedPath }] of Object.entries(functionConfig)) {
21
+ if (excludedPath) {
22
+ const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
23
+ const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern);
24
+ manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
25
+ }
26
+ }
10
27
  declarations.forEach((declaration) => {
11
28
  const func = functions.find(({ name }) => declaration.function === name);
12
29
  if (func === undefined) {
@@ -20,7 +37,7 @@ const generateManifest = ({ bundles = [], declarations = [], functions, importMa
20
37
  };
21
38
  const excludedPattern = getExcludedRegularExpression(declaration);
22
39
  if (excludedPattern) {
23
- route.excluded_pattern = serializePattern(excludedPattern);
40
+ manifestFunctionConfig[func.name].excluded_patterns.push(serializePattern(excludedPattern));
24
41
  }
25
42
  if (declaration.cache === "manual" /* Cache.Manual */) {
26
43
  postCacheRoutes.push(route);
@@ -40,6 +57,7 @@ const generateManifest = ({ bundles = [], declarations = [], functions, importMa
40
57
  bundler_version: getPackageVersion(),
41
58
  layers,
42
59
  import_map: importMap,
60
+ function_config: sanitizeEdgeFunctionConfig(manifestFunctionConfig),
43
61
  };
44
62
  return manifest;
45
63
  };
@@ -67,8 +85,8 @@ const getExcludedRegularExpression = (declaration) => {
67
85
  return pathToRegularExpression(declaration.excludedPath);
68
86
  }
69
87
  };
70
- const writeManifest = async ({ bundles, declarations = [], distDirectory, functions, importMap, layers, }) => {
71
- const manifest = generateManifest({ bundles, declarations, functions, importMap, layers });
88
+ const writeManifest = async ({ distDirectory, ...rest }) => {
89
+ const manifest = generateManifest(rest);
72
90
  const manifestPath = join(distDirectory, 'manifest.json');
73
91
  await fs.writeFile(manifestPath, JSON.stringify(manifest));
74
92
  return manifest;
@@ -53,10 +53,14 @@ test('Generates a manifest with excluded paths and patterns', () => {
53
53
  ];
54
54
  const manifest = generateManifest({ bundles: [], declarations, functions });
55
55
  const expectedRoutes = [
56
- { function: 'func-1', name: 'Display Name', pattern: '^/f1/.*/?$', excluded_pattern: '^/f1/exclude/?$' },
57
- { function: 'func-2', pattern: '^/f2/.*/?$', excluded_pattern: '^/f2/exclude$' },
56
+ { function: 'func-1', name: 'Display Name', pattern: '^/f1/.*/?$' },
57
+ { function: 'func-2', pattern: '^/f2/.*/?$' },
58
58
  ];
59
59
  expect(manifest.routes).toEqual(expectedRoutes);
60
+ expect(manifest.function_config).toEqual({
61
+ 'func-1': { excluded_patterns: ['^/f1/exclude/?$'] },
62
+ 'func-2': { excluded_patterns: ['^/f2/exclude$'] },
63
+ });
60
64
  expect(manifest.bundler_version).toBe(env.npm_package_version);
61
65
  });
62
66
  test('Excludes functions for which there are function files but no matching config declarations', () => {
@@ -86,6 +86,23 @@ declare const edgeManifestSchema: {
86
86
  bundler_version: {
87
87
  type: string;
88
88
  };
89
+ function_config: {
90
+ type: string;
91
+ items: {
92
+ type: string;
93
+ required: never[];
94
+ properties: {
95
+ excluded_patterns: {
96
+ type: string;
97
+ items: {
98
+ type: string;
99
+ format: string;
100
+ errorMessage: string;
101
+ };
102
+ };
103
+ };
104
+ };
105
+ };
89
106
  };
90
107
  additionalProperties: boolean;
91
108
  };
@@ -21,6 +21,20 @@ const routesSchema = {
21
21
  },
22
22
  additionalProperties: false,
23
23
  };
24
+ const functionConfigSchema = {
25
+ type: 'object',
26
+ required: [],
27
+ properties: {
28
+ excluded_patterns: {
29
+ type: 'array',
30
+ items: {
31
+ type: 'string',
32
+ format: 'regexPattern',
33
+ errorMessage: 'excluded_patterns needs to be an array of regex that starts with ^ and ends with $ without any additional slashes before and afterwards',
34
+ },
35
+ },
36
+ },
37
+ };
24
38
  const layersSchema = {
25
39
  type: 'object',
26
40
  required: ['flag', 'name'],
@@ -53,6 +67,7 @@ const edgeManifestSchema = {
53
67
  },
54
68
  import_map: { type: 'string' },
55
69
  bundler_version: { type: 'string' },
70
+ function_config: { type: 'object', items: functionConfigSchema },
56
71
  },
57
72
  additionalProperties: false,
58
73
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "8.0.0",
3
+ "version": "8.1.1",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",