@netlify/edge-bundler 14.0.6 → 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.
@@ -13,7 +13,7 @@ interface TaggedYieldedValue<T> {
13
13
  */
14
14
  export class MuxAsyncIterator<T> implements AsyncIterable<T> {
15
15
  private iteratorCount = 0;
16
- private yields: Array<TaggedYieldedValue<T>> = [];
16
+ private yields: TaggedYieldedValue<T>[] = [];
17
17
  // deno-lint-ignore no-explicit-any
18
18
  private throws: any[] = [];
19
19
  private signal: Deferred<void> = deferred();
@@ -31,7 +31,7 @@ export function pooledMap<T, R>(
31
31
  // Start processing items from the iterator
32
32
  (async () => {
33
33
  const writer = res.writable.getWriter();
34
- const executing: Array<Promise<unknown>> = [];
34
+ const executing: Promise<unknown>[] = [];
35
35
  try {
36
36
  for await (const item of array) {
37
37
  const p = Promise.resolve().then(() => iteratorFn(item));
@@ -703,8 +703,8 @@ const imports = {
703
703
  },
704
704
  };
705
705
 
706
- import { Loader } from "https://deno.land/x/wasmbuild@0.15.1/loader.ts";
707
706
  import { cacheToLocalDir } from "https://deno.land/x/wasmbuild@0.15.1/cache.ts";
707
+ import { Loader } from "https://deno.land/x/wasmbuild@0.15.1/loader.ts";
708
708
 
709
709
  const loader = new Loader({
710
710
  imports,
@@ -1,5 +1,5 @@
1
1
  import { type WriteStream } from 'fs';
2
- import { ExecaChildProcess } from 'execa';
2
+ import { type ExecaChildProcess } from 'execa';
3
3
  import { Logger } from './logger.js';
4
4
  declare const DENO_VERSION_RANGE = "1.39.0 - 2.2.4";
5
5
  type OnBeforeDownloadHook = () => void | Promise<void>;
@@ -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,6 +1,5 @@
1
- export const mergeDeclarations = (tomlDeclarations, userFunctionsConfig, internalFunctionsConfig, deployConfigDeclarations,
2
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3
- _featureFlags = {}) => {
1
+ import { BundleError } from './bundle_error.js';
2
+ export const mergeDeclarations = (tomlDeclarations, userFunctionsConfig, internalFunctionsConfig, deployConfigDeclarations, _featureFlags = {}) => {
4
3
  const functionsVisited = new Set();
5
4
  const declarations = [
6
5
  // INTEGRATIONS
@@ -45,7 +44,6 @@ const getDeclarationsFromInput = (inputDeclarations, functionConfigs, functionsV
45
44
  }
46
45
  else {
47
46
  // With an in-source config without a path, add the config to the declaration.
48
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
49
47
  const { path, excludedPath, pattern, excludedPattern, ...rest } = config;
50
48
  declarations.push({ ...declaration, ...rest });
51
49
  }
@@ -61,7 +59,7 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
61
59
  if (!functionsVisited.has(name)) {
62
60
  // If we have a pattern specified, create a declaration for each pattern.
63
61
  if ('pattern' in functionConfig && functionConfig.pattern) {
64
- const { pattern, excludedPattern } = functionConfig;
62
+ const { header, pattern, excludedPattern } = functionConfig;
65
63
  const patterns = Array.isArray(pattern) ? pattern : [pattern];
66
64
  patterns.forEach((singlePattern) => {
67
65
  const declaration = { function: name, pattern: singlePattern };
@@ -74,12 +72,15 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
74
72
  if (excludedPattern) {
75
73
  declaration.excludedPattern = excludedPattern;
76
74
  }
75
+ if (header) {
76
+ declaration.header = header;
77
+ }
77
78
  declarations.push(declaration);
78
79
  });
79
80
  }
80
81
  // If we don't have a pattern but we have a path specified, create a declaration for each path.
81
82
  else if ('path' in functionConfig && functionConfig.path) {
82
- const { path, excludedPath } = functionConfig;
83
+ const { header, path, excludedPath } = functionConfig;
83
84
  const paths = Array.isArray(path) ? path : [path];
84
85
  paths.forEach((singlePath) => {
85
86
  const declaration = { function: name, path: singlePath };
@@ -92,6 +93,9 @@ const createDeclarationsFromFunctionConfigs = (functionConfigs, functionsVisited
92
93
  if (excludedPath) {
93
94
  declaration.excludedPath = excludedPath;
94
95
  }
96
+ if (header) {
97
+ declaration.header = header;
98
+ }
95
99
  declarations.push(declaration);
96
100
  });
97
101
  }
@@ -115,3 +119,25 @@ export const normalizePattern = (pattern) => {
115
119
  // Strip leading and forward slashes.
116
120
  return regexp.toString().slice(1, -1);
117
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';
@@ -83,7 +83,6 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
83
83
  if (manifestFunctionConfig[name] === undefined) {
84
84
  continue;
85
85
  }
86
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
87
86
  const { onError, rateLimit, path, excludedPath, pattern, excludedPattern, ...rest } = singleInternalFunctionConfig;
88
87
  if (pattern && excludedPattern) {
89
88
  addManifestExcludedPatternsFromConfigExcludedPattern(name, manifestFunctionConfig, excludedPattern);
@@ -120,6 +119,9 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
120
119
  if ('method' in declaration) {
121
120
  route.methods = normalizeMethods(declaration.method, func.name);
122
121
  }
122
+ if ('header' in declaration) {
123
+ route.headers = getHeaderMatchers(declaration.header);
124
+ }
123
125
  if ('path' in declaration) {
124
126
  route.path = declaration.path;
125
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
+ });
@@ -1,4 +1,4 @@
1
- import { ExecaChildProcess } from 'execa';
1
+ import { type ExecaChildProcess } from 'execa';
2
2
  declare const killProcess: (ps: ExecaChildProcess<string>) => Promise<unknown> | undefined;
3
3
  declare const waitForServer: (port: number, ps?: ExecaChildProcess<string>) => Promise<boolean>;
4
4
  export { killProcess, waitForServer };
@@ -19,7 +19,6 @@ const initializeValidator = () => {
19
19
  return manifestValidator;
20
20
  };
21
21
  // throws on validation error
22
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
23
22
  export const validateManifest = (manifestData, _featureFlags = {}) => {
24
23
  const validate = initializeValidator();
25
24
  const valid = validate(manifestData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "14.0.6",
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",
@@ -42,12 +42,13 @@
42
42
  "test": "test/node"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/node": "^18.0.0",
45
+ "@types/node": "^18.19.111",
46
46
  "@types/semver": "^7.3.9",
47
47
  "@types/uuid": "^10.0.0",
48
48
  "@vitest/coverage-v8": "^3.0.0",
49
49
  "archiver": "^7.0.0",
50
50
  "chalk": "^5.4.0",
51
+ "cpy": "^11.1.0",
51
52
  "nock": "^14.0.0",
52
53
  "npm-run-all2": "^6.0.0",
53
54
  "tar": "^7.0.0",
@@ -64,7 +65,7 @@
64
65
  "better-ajv-errors": "^1.2.0",
65
66
  "common-path-prefix": "^3.0.0",
66
67
  "env-paths": "^3.0.0",
67
- "esbuild": "0.25.5",
68
+ "esbuild": "0.25.6",
68
69
  "execa": "^8.0.0",
69
70
  "find-up": "^7.0.0",
70
71
  "get-package-name": "^2.2.0",
@@ -80,5 +81,5 @@
80
81
  "urlpattern-polyfill": "8.0.2",
81
82
  "uuid": "^11.0.0"
82
83
  },
83
- "gitHead": "4a0f587ae4efe1c2e62c25c11374e3fbaa9aebce"
84
+ "gitHead": "ea236c6df25a511cc0f0412759610da917c10b20"
84
85
  }