@netlify/edge-bundler 8.10.0 → 8.11.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.
@@ -7,9 +7,12 @@ export declare const enum Cache {
7
7
  Off = "off",
8
8
  Manual = "manual"
9
9
  }
10
+ export type OnError = 'fail' | 'bypass' | `/${string}`;
11
+ export declare const isValidOnError: (value: unknown) => value is OnError;
10
12
  export interface FunctionConfig {
11
13
  cache?: Cache;
12
14
  path?: string | string[];
13
15
  excludedPath?: string | string[];
16
+ onError?: OnError;
14
17
  }
15
18
  export declare const getFunctionConfig: (func: EdgeFunction, importMap: ImportMap, deno: DenoBridge, log: Logger, featureFlags: FeatureFlags) => Promise<FunctionConfig>;
@@ -14,6 +14,13 @@ var ConfigExitCode;
14
14
  ConfigExitCode[ConfigExitCode["SerializationError"] = 5] = "SerializationError";
15
15
  ConfigExitCode[ConfigExitCode["InvalidDefaultExport"] = 6] = "InvalidDefaultExport";
16
16
  })(ConfigExitCode || (ConfigExitCode = {}));
17
+ export const isValidOnError = (value) => {
18
+ if (typeof value === 'undefined')
19
+ return true;
20
+ if (typeof value !== 'string')
21
+ return false;
22
+ return value === 'fail' || value === 'bypass' || value.startsWith('/');
23
+ };
17
24
  const getConfigExtractor = () => {
18
25
  const packagePath = getPackagePath();
19
26
  const configExtractorPath = join(packagePath, 'deno', 'config.ts');
@@ -52,17 +59,21 @@ export const getFunctionConfig = async (func, importMap, deno, log, featureFlags
52
59
  if (stdout !== '') {
53
60
  log.user(stdout);
54
61
  }
62
+ let collectorData = {};
55
63
  try {
56
- const collectorData = await fs.readFile(collector.path, 'utf8');
57
- return JSON.parse(collectorData);
64
+ const collectorDataJSON = await fs.readFile(collector.path, 'utf8');
65
+ collectorData = JSON.parse(collectorDataJSON);
58
66
  }
59
67
  catch {
60
68
  handleConfigError(func, ConfigExitCode.UnhandledError, stderr, log, featureFlags);
61
- return {};
62
69
  }
63
70
  finally {
64
71
  await collector.cleanup();
65
72
  }
73
+ if (!isValidOnError(collectorData.onError)) {
74
+ throw new BundleError(new Error(`The 'onError' configuration property in edge function at '${func.path}' must be one of 'fail', 'bypass', or a path starting with '/'. Got '${collectorData.onError}'. More on the Edge Functions API at https://ntl.fyi/edge-api.`));
75
+ }
76
+ return collectorData;
66
77
  };
67
78
  const handleConfigError = (func, exitCode, stderr, log, featureFlags) => {
68
79
  switch (exitCode) {
@@ -89,6 +89,24 @@ const functions = [
89
89
  edge_functions_invalid_config_throw: false,
90
90
  },
91
91
  },
92
+ {
93
+ testName: 'config with correct onError',
94
+ expectedConfig: { onError: 'bypass' },
95
+ name: 'func5',
96
+ source: `
97
+ export default async () => new Response("Hello from function two")
98
+ export const config = { onError: "bypass" }
99
+ `,
100
+ },
101
+ {
102
+ testName: 'config with wrong onError',
103
+ name: 'func7',
104
+ source: `
105
+ export default async () => new Response("Hello from function two")
106
+ export const config = { onError: "foo" }
107
+ `,
108
+ error: /The 'onError' configuration property in edge function at .*/,
109
+ },
92
110
  {
93
111
  testName: 'config with `path`',
94
112
  expectedConfig: { path: '/home' },
@@ -12,6 +12,7 @@ interface Route {
12
12
  }
13
13
  interface EdgeFunctionConfig {
14
14
  excluded_patterns: string[];
15
+ on_error?: string;
15
16
  }
16
17
  interface Manifest {
17
18
  bundler_version: string;
@@ -37,12 +38,6 @@ interface GenerateManifestOptions {
37
38
  importMap?: string;
38
39
  layers?: Layer[];
39
40
  }
40
- interface Route {
41
- function: string;
42
- name?: string;
43
- pattern: string;
44
- generator?: string;
45
- }
46
41
  declare const generateManifest: ({ bundles, declarations, featureFlags, functions, functionConfig, importMap, layers, }: GenerateManifestOptions) => Manifest;
47
42
  interface WriteManifestOptions extends GenerateManifestOptions {
48
43
  distDirectory: string;
@@ -12,7 +12,7 @@ const serializePattern = (pattern) => pattern.replace(/\\\//g, '/');
12
12
  const sanitizeEdgeFunctionConfig = (config) => {
13
13
  const newConfig = {};
14
14
  for (const [name, functionConfig] of Object.entries(config)) {
15
- if (functionConfig.excluded_patterns.length !== 0) {
15
+ if (functionConfig.excluded_patterns.length !== 0 || functionConfig.on_error) {
16
16
  newConfig[name] = functionConfig;
17
17
  }
18
18
  }
@@ -22,12 +22,15 @@ const generateManifest = ({ bundles = [], declarations = [], featureFlags, funct
22
22
  const preCacheRoutes = [];
23
23
  const postCacheRoutes = [];
24
24
  const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
25
- for (const [name, { excludedPath }] of Object.entries(functionConfig)) {
25
+ for (const [name, { excludedPath, onError }] of Object.entries(functionConfig)) {
26
26
  if (excludedPath) {
27
27
  const paths = Array.isArray(excludedPath) ? excludedPath : [excludedPath];
28
28
  const excludedPatterns = paths.map(pathToRegularExpression).map(serializePattern);
29
29
  manifestFunctionConfig[name].excluded_patterns.push(...excludedPatterns);
30
30
  }
31
+ if (onError) {
32
+ manifestFunctionConfig[name].on_error = onError;
33
+ }
31
34
  }
32
35
  declarations.forEach((declaration) => {
33
36
  const func = functions.find(({ name }) => declaration.function === name);
@@ -84,6 +84,25 @@ test('Generates a manifest with excluded paths and patterns', () => {
84
84
  });
85
85
  expect(manifest.bundler_version).toBe(env.npm_package_version);
86
86
  });
87
+ test('Includes failure modes in manifest', () => {
88
+ const functions = [
89
+ { name: 'func-1', path: '/path/to/func-1.ts' },
90
+ { name: 'func-2', path: '/path/to/func-2.ts' },
91
+ ];
92
+ const declarations = [
93
+ { function: 'func-1', name: 'Display Name', path: '/f1/*' },
94
+ { function: 'func-2', pattern: '^/f2/.*/?$' },
95
+ ];
96
+ const functionConfig = {
97
+ 'func-1': {
98
+ onError: '/custom-error',
99
+ },
100
+ };
101
+ const manifest = generateManifest({ bundles: [], declarations, functions, functionConfig });
102
+ expect(manifest.function_config).toEqual({
103
+ 'func-1': { excluded_patterns: [], on_error: '/custom-error' },
104
+ });
105
+ });
87
106
  test('Excludes functions for which there are function files but no matching config declarations', () => {
88
107
  const bundle1 = {
89
108
  extension: '.ext2',
@@ -106,6 +106,9 @@ declare const edgeManifestSchema: {
106
106
  errorMessage: string;
107
107
  };
108
108
  };
109
+ on_error: {
110
+ type: string;
111
+ };
109
112
  };
110
113
  };
111
114
  };
@@ -34,6 +34,7 @@ const functionConfigSchema = {
34
34
  errorMessage: 'excluded_patterns needs to be an array of regex that starts with ^ and ends with $ without any additional slashes before and afterwards',
35
35
  },
36
36
  },
37
+ on_error: { type: 'string' },
37
38
  },
38
39
  };
39
40
  const layersSchema = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "8.10.0",
3
+ "version": "8.11.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",