@netlify/edge-bundler 14.0.7 → 14.1.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.
@@ -11,8 +11,10 @@ export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'
11
11
  export type Path = `/${string}`;
12
12
  export type OnError = 'fail' | 'bypass' | Path;
13
13
  export declare const isValidOnError: (value: unknown) => value is OnError;
14
+ export type HeadersConfig = Record<string, boolean | string>;
14
15
  interface BaseFunctionConfig {
15
16
  cache?: Cache;
17
+ header?: HeadersConfig;
16
18
  onError?: OnError;
17
19
  name?: string;
18
20
  generator?: string;
@@ -263,6 +263,18 @@ test('Loads function paths from the in-source `config` function', async () => {
263
263
  pattern: '^/user-func1/?$',
264
264
  excluded_patterns: [],
265
265
  path: '/user-func1',
266
+ headers: {
267
+ 'x-must-be-there': {
268
+ style: 'exists',
269
+ },
270
+ 'x-must-match': {
271
+ pattern: '^(foo|bar)$',
272
+ style: 'regex',
273
+ },
274
+ 'x-must-not-be-there': {
275
+ style: 'missing',
276
+ },
277
+ },
266
278
  });
267
279
  expect(routes[7]).toEqual({
268
280
  function: 'user-func3',
@@ -1,8 +1,16 @@
1
- import { FunctionConfig, HTTPMethod, Path } from './config.js';
1
+ import { FunctionConfig, HeadersConfig, HTTPMethod, Path } from './config.js';
2
2
  import { FeatureFlags } from './feature_flags.js';
3
+ export type HeaderMatch = {
4
+ pattern: string;
5
+ style: 'regex';
6
+ } | {
7
+ style: 'exists' | 'missing';
8
+ };
9
+ type HeaderMatchers = Record<string, HeaderMatch>;
3
10
  interface BaseDeclaration {
4
11
  cache?: string;
5
12
  function: string;
13
+ header?: HeadersConfig;
6
14
  method?: HTTPMethod | HTTPMethod[];
7
15
  name?: string;
8
16
  generator?: string;
@@ -22,4 +30,5 @@ export declare const mergeDeclarations: (tomlDeclarations: Declaration[], userFu
22
30
  * `$` characters.
23
31
  */
24
32
  export declare const normalizePattern: (pattern: string) => string;
33
+ export declare const getHeaderMatchers: (headers?: HeadersConfig) => HeaderMatchers;
25
34
  export {};
@@ -1,3 +1,4 @@
1
+ import { BundleError } from './bundle_error.js';
1
2
  export const mergeDeclarations = (tomlDeclarations, userFunctionsConfig, internalFunctionsConfig, deployConfigDeclarations, _featureFlags = {}) => {
2
3
  const functionsVisited = new Set();
3
4
  const declarations = [
@@ -58,7 +59,7 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
58
59
  if (!functionsVisited.has(name)) {
59
60
  // If we have a pattern specified, create a declaration for each pattern.
60
61
  if ('pattern' in functionConfig && functionConfig.pattern) {
61
- const { pattern, excludedPattern } = functionConfig;
62
+ const { header, pattern, excludedPattern } = functionConfig;
62
63
  const patterns = Array.isArray(pattern) ? pattern : [pattern];
63
64
  patterns.forEach((singlePattern) => {
64
65
  const declaration = { function: name, pattern: singlePattern };
@@ -71,12 +72,15 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
71
72
  if (excludedPattern) {
72
73
  declaration.excludedPattern = excludedPattern;
73
74
  }
75
+ if (header) {
76
+ declaration.header = header;
77
+ }
74
78
  declarations.push(declaration);
75
79
  });
76
80
  }
77
81
  // If we don't have a pattern but we have a path specified, create a declaration for each path.
78
82
  else if ('path' in functionConfig && functionConfig.path) {
79
- const { path, excludedPath } = functionConfig;
83
+ const { header, path, excludedPath } = functionConfig;
80
84
  const paths = Array.isArray(path) ? path : [path];
81
85
  paths.forEach((singlePath) => {
82
86
  const declaration = { function: name, path: singlePath };
@@ -89,6 +93,9 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
89
93
  if (excludedPath) {
90
94
  declaration.excludedPath = excludedPath;
91
95
  }
96
+ if (header) {
97
+ declaration.header = header;
98
+ }
92
99
  declarations.push(declaration);
93
100
  });
94
101
  }
@@ -112,3 +119,25 @@ export const normalizePattern = (pattern) => {
112
119
  // Strip leading and forward slashes.
113
120
  return regexp.toString().slice(1, -1);
114
121
  };
122
+ const headerConfigError = `The 'header' configuration property must be an object where keys are strings and values are either booleans or strings (e.g. { "x-header-present": true, "x-header-absent": false, "x-header-matching": "^prefix" }).`;
123
+ export const getHeaderMatchers = (headers) => {
124
+ const matchers = {};
125
+ if (!headers) {
126
+ return matchers;
127
+ }
128
+ if (Object.getPrototypeOf(headers) !== Object.prototype) {
129
+ throw new BundleError(new Error(headerConfigError));
130
+ }
131
+ for (const header in headers) {
132
+ if (typeof headers[header] === 'boolean') {
133
+ matchers[header] = { style: headers[header] ? 'exists' : 'missing' };
134
+ }
135
+ else if (typeof headers[header] === 'string') {
136
+ matchers[header] = { style: 'regex', pattern: normalizePattern(headers[header]) };
137
+ }
138
+ else {
139
+ throw new BundleError(new Error(headerConfigError));
140
+ }
141
+ }
142
+ return matchers;
143
+ };
@@ -1,11 +1,12 @@
1
1
  import type { Bundle } from './bundle.js';
2
2
  import { FunctionConfig } from './config.js';
3
- import { Declaration } from './declaration.js';
3
+ import { Declaration, type HeaderMatch } from './declaration.js';
4
4
  import { EdgeFunction } from './edge_function.js';
5
5
  import { FeatureFlags } from './feature_flags.js';
6
6
  import { Layer } from './layer.js';
7
7
  interface Route {
8
8
  function: string;
9
+ headers?: Record<string, HeaderMatch>;
9
10
  pattern: string;
10
11
  excluded_patterns: string[];
11
12
  path?: string;
@@ -1,7 +1,7 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { wrapBundleError } from './bundle_error.js';
4
- import { normalizePattern } from './declaration.js';
4
+ import { getHeaderMatchers, normalizePattern } from './declaration.js';
5
5
  import { getPackageVersion } from './package_json.js';
6
6
  import { RateLimitAction, RateLimitAlgorithm, RateLimitAggregator } from './rate_limit.js';
7
7
  import { nonNullable } from './utils/non_nullable.js';
@@ -119,6 +119,9 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
119
119
  if ('method' in declaration) {
120
120
  route.methods = normalizeMethods(declaration.method, func.name);
121
121
  }
122
+ if ('header' in declaration) {
123
+ route.headers = getHeaderMatchers(declaration.header);
124
+ }
122
125
  if ('path' in declaration) {
123
126
  route.path = declaration.path;
124
127
  }
@@ -1,4 +1,4 @@
1
- import { test, expect } from 'vitest';
1
+ import { describe, test, expect } from 'vitest';
2
2
  // @ts-expect-error current tsconfig.json doesn't allow this, but I don't want to change it
3
3
  import { version } from '../package.json' assert { type: 'json' };
4
4
  import { getRouteMatcher } from '../test/util.js';
@@ -514,3 +514,85 @@ test('Generates a manifest with rewrite config', () => {
514
514
  expect(manifest.routes).toEqual(expectedRoutes);
515
515
  expect(manifest.function_config).toEqual(expectedFunctionConfig);
516
516
  });
517
+ describe('Header matching', () => {
518
+ test('Throws a bundling error if the type is incorrect', () => {
519
+ const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
520
+ expect(() => generateManifest({
521
+ bundles: [],
522
+ // @ts-expect-error Incorrect type
523
+ declarations: [{ function: 'func-1', path: '/f1/*', header: 'foo' }],
524
+ functions,
525
+ })).toThrowError(BundleError);
526
+ expect(() => generateManifest({
527
+ bundles: [],
528
+ declarations: [
529
+ {
530
+ function: 'func-1',
531
+ path: '/f1/*',
532
+ header: {
533
+ 'x-correct': true,
534
+ // @ts-expect-error Incorrect type
535
+ 'x-not-correct': {
536
+ problem: true,
537
+ },
538
+ },
539
+ },
540
+ ],
541
+ functions,
542
+ })).toThrowError(BundleError);
543
+ });
544
+ test('Writes header matching rules to the manifest', () => {
545
+ const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
546
+ const declarations = [
547
+ {
548
+ function: 'func-1',
549
+ path: '/f1/*',
550
+ header: {
551
+ 'x-present': true,
552
+ 'x-also-present': true,
553
+ 'x-absent': false,
554
+ 'x-match-prefix': '^prefix(.*)',
555
+ 'x-match-exact': 'exact',
556
+ 'x-match-suffix': '(.*)suffix$',
557
+ },
558
+ },
559
+ ];
560
+ const { manifest } = generateManifest({
561
+ bundles: [],
562
+ declarations,
563
+ functions,
564
+ });
565
+ const expectedRoutes = [
566
+ {
567
+ function: 'func-1',
568
+ pattern: '^/f1(?:/(.*))/?$',
569
+ excluded_patterns: [],
570
+ path: '/f1/*',
571
+ headers: {
572
+ 'x-absent': {
573
+ style: 'missing',
574
+ },
575
+ 'x-also-present': {
576
+ style: 'exists',
577
+ },
578
+ 'x-match-exact': {
579
+ pattern: '^exact$',
580
+ style: 'regex',
581
+ },
582
+ 'x-match-prefix': {
583
+ pattern: '^prefix(.*)$',
584
+ style: 'regex',
585
+ },
586
+ 'x-match-suffix': {
587
+ pattern: '^(.*)suffix$',
588
+ style: 'regex',
589
+ },
590
+ 'x-present': {
591
+ style: 'exists',
592
+ },
593
+ },
594
+ },
595
+ ];
596
+ expect(manifest.routes).toEqual(expectedRoutes);
597
+ });
598
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "14.0.7",
3
+ "version": "14.1.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -81,5 +81,5 @@
81
81
  "urlpattern-polyfill": "8.0.2",
82
82
  "uuid": "^11.0.0"
83
83
  },
84
- "gitHead": "e471abe7b07fb8a34110d06bd8857c89f1d84142"
84
+ "gitHead": "ea236c6df25a511cc0f0412759610da917c10b20"
85
85
  }