@netlify/edge-bundler 11.0.0 → 11.2.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.
@@ -1,4 +1,6 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import { type WriteStream } from 'fs';
2
4
  import { ExecaChildProcess } from 'execa';
3
5
  import { Logger } from './logger.js';
4
6
  declare const DENO_VERSION_RANGE = "^1.37.0";
@@ -18,9 +20,11 @@ interface ProcessRef {
18
20
  ps?: ExecaChildProcess<string>;
19
21
  }
20
22
  interface RunOptions {
21
- pipeOutput?: boolean;
22
23
  env?: NodeJS.ProcessEnv;
23
24
  extendEnv?: boolean;
25
+ pipeOutput?: boolean;
26
+ stderr?: WriteStream;
27
+ stdout?: WriteStream;
24
28
  rejectOnExitCode?: boolean;
25
29
  }
26
30
  declare class DenoBridge {
@@ -47,8 +51,8 @@ declare class DenoBridge {
47
51
  path: string;
48
52
  }>;
49
53
  getEnvironmentVariables(inputEnv?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
50
- run(args: string[], { pipeOutput, env: inputEnv, extendEnv, rejectOnExitCode }?: RunOptions): Promise<import("execa").ExecaReturnValue<string>>;
51
- runInBackground(args: string[], ref?: ProcessRef, { pipeOutput, env: inputEnv, extendEnv }?: RunOptions): Promise<void>;
54
+ run(args: string[], { env: inputEnv, extendEnv, rejectOnExitCode, stderr, stdout }?: RunOptions): Promise<import("execa").ExecaReturnValue<string>>;
55
+ runInBackground(args: string[], ref?: ProcessRef, { env: inputEnv, extendEnv, stderr, stdout }?: RunOptions): Promise<void>;
52
56
  }
53
57
  export { DENO_VERSION_RANGE, DenoBridge };
54
58
  export type { DenoOptions, OnAfterDownloadHook, OnBeforeDownloadHook, ProcessRef };
@@ -93,13 +93,21 @@ class DenoBridge {
93
93
  }
94
94
  return this.currentDownload;
95
95
  }
96
- static runWithBinary(binaryPath, args, options, pipeOutput) {
97
- var _a, _b;
96
+ static runWithBinary(binaryPath, args, { options, pipeOutput, stderr, stdout, }) {
97
+ var _a, _b, _c, _d;
98
98
  const runDeno = execa(binaryPath, args, options);
99
- if (pipeOutput) {
100
- (_a = runDeno.stdout) === null || _a === void 0 ? void 0 : _a.pipe(process.stdout);
99
+ if (stderr) {
100
+ (_a = runDeno.stderr) === null || _a === void 0 ? void 0 : _a.pipe(stderr);
101
+ }
102
+ else if (pipeOutput) {
101
103
  (_b = runDeno.stderr) === null || _b === void 0 ? void 0 : _b.pipe(process.stderr);
102
104
  }
105
+ if (stdout) {
106
+ (_c = runDeno.stdout) === null || _c === void 0 ? void 0 : _c.pipe(stdout);
107
+ }
108
+ else if (pipeOutput) {
109
+ (_d = runDeno.stdout) === null || _d === void 0 ? void 0 : _d.pipe(process.stdout);
110
+ }
103
111
  return runDeno;
104
112
  }
105
113
  async writeVersionFile(version) {
@@ -135,19 +143,19 @@ class DenoBridge {
135
143
  }
136
144
  // Runs the Deno CLI in the background and returns a reference to the child
137
145
  // process, awaiting its execution.
138
- async run(args, { pipeOutput, env: inputEnv, extendEnv = true, rejectOnExitCode = true } = {}) {
146
+ async run(args, { env: inputEnv, extendEnv = true, rejectOnExitCode = true, stderr, stdout } = {}) {
139
147
  const { path: binaryPath } = await this.getBinaryPath();
140
148
  const env = this.getEnvironmentVariables(inputEnv);
141
149
  const options = { env, extendEnv, reject: rejectOnExitCode };
142
- return DenoBridge.runWithBinary(binaryPath, args, options, pipeOutput);
150
+ return DenoBridge.runWithBinary(binaryPath, args, { options, stderr, stdout });
143
151
  }
144
152
  // Runs the Deno CLI in the background, assigning a reference of the child
145
153
  // process to a `ps` property in the `ref` argument, if one is supplied.
146
- async runInBackground(args, ref, { pipeOutput, env: inputEnv, extendEnv = true } = {}) {
154
+ async runInBackground(args, ref, { env: inputEnv, extendEnv = true, stderr, stdout } = {}) {
147
155
  const { path: binaryPath } = await this.getBinaryPath();
148
156
  const env = this.getEnvironmentVariables(inputEnv);
149
157
  const options = { env, extendEnv };
150
- const ps = DenoBridge.runWithBinary(binaryPath, args, options, pipeOutput);
158
+ const ps = DenoBridge.runWithBinary(binaryPath, args, { options, stderr, stdout });
151
159
  if (ref !== undefined) {
152
160
  // eslint-disable-next-line no-param-reassign
153
161
  ref.ps = ps;
@@ -1,6 +1,10 @@
1
- declare const defaultFlags: {};
1
+ declare const defaultFlags: {
2
+ edge_bundler_pcre_regexp: boolean;
3
+ };
2
4
  type FeatureFlag = keyof typeof defaultFlags;
3
5
  type FeatureFlags = Partial<Record<FeatureFlag, boolean>>;
4
- declare const getFlags: (input?: Record<string, boolean>, flags?: {}) => FeatureFlags;
6
+ declare const getFlags: (input?: Record<string, boolean>, flags?: {
7
+ edge_bundler_pcre_regexp: boolean;
8
+ }) => FeatureFlags;
5
9
  export { defaultFlags, getFlags };
6
10
  export type { FeatureFlag, FeatureFlags };
@@ -1,4 +1,6 @@
1
- const defaultFlags = {};
1
+ const defaultFlags = {
2
+ edge_bundler_pcre_regexp: false,
3
+ };
2
4
  const getFlags = (input = {}, flags = defaultFlags) => Object.entries(flags).reduce((result, [key, defaultValue]) => ({
3
5
  ...result,
4
6
  [key]: input[key] === undefined ? defaultValue : input[key],
@@ -42,7 +42,7 @@ interface GenerateManifestOptions {
42
42
  layers?: Layer[];
43
43
  userFunctionConfig?: Record<string, FunctionConfig>;
44
44
  }
45
- declare const generateManifest: ({ bundles, declarations, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => {
45
+ declare const generateManifest: ({ bundles, declarations, featureFlags, functions, userFunctionConfig, internalFunctionConfig, importMap, layers, }: GenerateManifestOptions) => {
46
46
  declarationsWithoutFunction: string[];
47
47
  manifest: Manifest;
48
48
  unroutedFunctions: string[];
@@ -46,7 +46,7 @@ const normalizeMethods = (method, name) => {
46
46
  return method.toUpperCase();
47
47
  });
48
48
  };
49
- const generateManifest = ({ bundles = [], declarations = [], functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => {
49
+ const generateManifest = ({ bundles = [], declarations = [], featureFlags, functions, userFunctionConfig = {}, internalFunctionConfig = {}, importMap, layers = [], }) => {
50
50
  const preCacheRoutes = [];
51
51
  const postCacheRoutes = [];
52
52
  const manifestFunctionConfig = Object.fromEntries(functions.map(({ name }) => [name, { excluded_patterns: [] }]));
@@ -74,14 +74,14 @@ const generateManifest = ({ bundles = [], declarations = [], functions, userFunc
74
74
  declarationsWithoutFunction.add(declaration.function);
75
75
  return;
76
76
  }
77
- const pattern = getRegularExpression(declaration);
77
+ const pattern = getRegularExpression(declaration, Boolean(featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_bundler_pcre_regexp));
78
78
  // If there is no `pattern`, the declaration will never be triggered, so we
79
79
  // can discard it.
80
80
  if (!pattern) {
81
81
  return;
82
82
  }
83
83
  routedFunctions.add(declaration.function);
84
- const excludedPattern = getExcludedRegularExpressions(declaration);
84
+ const excludedPattern = getExcludedRegularExpressions(declaration, Boolean(featureFlags === null || featureFlags === void 0 ? void 0 : featureFlags.edge_bundler_pcre_regexp));
85
85
  const route = {
86
86
  function: func.name,
87
87
  pattern: serializePattern(pattern),
@@ -135,8 +135,11 @@ const pathToRegularExpression = (path) => {
135
135
  throw wrapBundleError(error);
136
136
  }
137
137
  };
138
- const getRegularExpression = (declaration) => {
138
+ const getRegularExpression = (declaration, pcreRegexpEngine) => {
139
139
  if ('pattern' in declaration) {
140
+ if (pcreRegexpEngine) {
141
+ return declaration.pattern;
142
+ }
140
143
  try {
141
144
  return parsePattern(declaration.pattern);
142
145
  }
@@ -146,11 +149,14 @@ const getRegularExpression = (declaration) => {
146
149
  }
147
150
  return pathToRegularExpression(declaration.path);
148
151
  };
149
- const getExcludedRegularExpressions = (declaration) => {
152
+ const getExcludedRegularExpressions = (declaration, pcreRegexpEngine) => {
150
153
  if ('excludedPattern' in declaration && declaration.excludedPattern) {
151
154
  const excludedPatterns = Array.isArray(declaration.excludedPattern)
152
155
  ? declaration.excludedPattern
153
156
  : [declaration.excludedPattern];
157
+ if (pcreRegexpEngine) {
158
+ return excludedPatterns;
159
+ }
154
160
  return excludedPatterns.map((excludedPattern) => {
155
161
  try {
156
162
  return parsePattern(excludedPattern);
@@ -386,15 +386,29 @@ test('Generates a manifest with layers', () => {
386
386
  expect(manifest2.routes).toEqual(expectedRoutes);
387
387
  expect(manifest2.layers).toEqual(layers);
388
388
  });
389
- test('Throws an error if the regular expression contains a negative lookahead', () => {
389
+ test('Throws an error if the regular expression contains a negative lookahead and support for the PCRE engine is disabled', () => {
390
390
  const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
391
- const declarations = [{ function: 'func-1', pattern: '^/\\w+(?=\\d)$' }];
391
+ const declarations = [{ function: 'func-1', pattern: '^\\/((?!api|_next\\/static|_next\\/image|favicon.ico).*)$' }];
392
392
  expect(() => generateManifest({
393
393
  bundles: [],
394
394
  declarations,
395
395
  functions,
396
396
  })).toThrowError(/^Could not parse path declaration of function 'func-1': Regular expressions with lookaheads are not supported$/);
397
397
  });
398
+ test('Accepts regular expressions with lookaheads if support for the PCRE engine is enabled', () => {
399
+ const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
400
+ const declarations = [{ function: 'func-1', pattern: '^\\/((?!api|_next\\/static|_next\\/image|favicon.ico).*)$' }];
401
+ const { manifest } = generateManifest({
402
+ bundles: [],
403
+ declarations,
404
+ featureFlags: { edge_bundler_pcre_regexp: true },
405
+ functions,
406
+ });
407
+ const [route] = manifest.routes;
408
+ const regexp = new RegExp(route.pattern);
409
+ expect(regexp.test('/foo')).toBeTruthy();
410
+ expect(regexp.test('/_next/static/foo')).toBeFalsy();
411
+ });
398
412
  test('Converts named capture groups to unnamed capture groups in regular expressions', () => {
399
413
  const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }];
400
414
  const declarations = [{ function: 'func-1', pattern: '^/(?<name>\\w+)$' }];
@@ -3,6 +3,8 @@
3
3
  /// <reference types="node" />
4
4
  /// <reference types="node" />
5
5
  /// <reference types="node" />
6
+ /// <reference types="node" />
7
+ import type { WriteStream } from 'fs';
6
8
  import { OnAfterDownloadHook, OnBeforeDownloadHook } from '../bridge.js';
7
9
  import { FunctionConfig } from '../config.js';
8
10
  import type { EdgeFunction } from '../edge_function.js';
@@ -34,10 +36,12 @@ interface ServeOptions {
34
36
  port: number;
35
37
  rootPath?: string;
36
38
  servePath: string;
39
+ stderr?: WriteStream;
40
+ stdout?: WriteStream;
37
41
  userLogger?: LogFunction;
38
42
  systemLogger?: LogFunction;
39
43
  }
40
- export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, onAfterDownload, onBeforeDownload, port, rootPath, servePath, userLogger, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
44
+ export declare const serve: ({ basePath, bootstrapURL, certificatePath, debug, distImportMapPath, inspectSettings, featureFlags, formatExportTypeError, formatImportError, onAfterDownload, onBeforeDownload, port, rootPath, servePath, stderr, stdout, userLogger, systemLogger, }: ServeOptions) => Promise<(functions: EdgeFunction[], env?: NodeJS.ProcessEnv, options?: StartServerOptions) => Promise<{
41
45
  features: Record<string, boolean>;
42
46
  functionsConfig: FunctionConfig[];
43
47
  graph: ModuleGraphJson;
@@ -18,7 +18,7 @@ const cleanDirectory = async (directory, except) => {
18
18
  const toBeDeleted = files.filter((file) => !except.includes(join(directory, file)));
19
19
  await Promise.all(toBeDeleted.map((file) => unlink(join(directory, file))));
20
20
  };
21
- const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, logger, port, rootPath, }) => {
21
+ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImportMapPath, flags: denoFlags, formatExportTypeError, formatImportError, logger, port, rootPath, stderr, stdout, }) => {
22
22
  const processRef = {};
23
23
  const startServer = async (functions, env = {}, options = {}) => {
24
24
  var _a;
@@ -77,9 +77,11 @@ const prepareServer = ({ basePath, bootstrapURL, deno, distDirectory, distImport
77
77
  // with variables from the user's system, since those will not be available
78
78
  // in the production environment.
79
79
  await deno.runInBackground(['run', ...denoFlags, ...extraDenoFlags, stage2Path, ...applicationFlags], processRef, {
80
- pipeOutput: true,
81
80
  env,
82
81
  extendEnv: false,
82
+ pipeOutput: true,
83
+ stderr,
84
+ stdout,
83
85
  });
84
86
  let functionsConfig = [];
85
87
  if (options.getFunctionsConfig) {
@@ -165,6 +167,16 @@ rootPath,
165
167
  * operate.
166
168
  */
167
169
  servePath,
170
+ /**
171
+ * Writable stream to receive the stderr of the server process. If not set,
172
+ * the stderr of the parent process will be used.
173
+ */
174
+ stderr,
175
+ /**
176
+ * Writable stream to receive the stdout of the server process. If not set,
177
+ * the stdout of the parent process will be used.
178
+ */
179
+ stdout,
168
180
  /**
169
181
  * Custom logging function to be used for user-facing messages. Defaults to
170
182
  * `console.log`.
@@ -214,6 +226,8 @@ systemLogger, }) => {
214
226
  formatExportTypeError,
215
227
  formatImportError,
216
228
  logger,
229
+ stderr,
230
+ stdout,
217
231
  port,
218
232
  rootPath,
219
233
  });
@@ -1,8 +1,10 @@
1
+ import { createWriteStream } from 'fs';
1
2
  import { readFile } from 'fs/promises';
2
3
  import { join } from 'path';
3
4
  import process from 'process';
4
5
  import getPort from 'get-port';
5
6
  import fetch from 'node-fetch';
7
+ import tmp from 'tmp-promise';
6
8
  import { v4 as uuidv4 } from 'uuid';
7
9
  import { test, expect } from 'vitest';
8
10
  import { fixturesDir } from '../../test/util.js';
@@ -86,6 +88,8 @@ test('Starts a server and serves requests for edge functions', async () => {
86
88
  expect(identidadeBarrelFile).toContain(`/// <reference types="${join('..', '..', 'node_modules', '@types', 'pt-committee__identidade', 'index.d.ts')}" />`);
87
89
  });
88
90
  test('Serves edge functions in a monorepo setup', async () => {
91
+ const tmpFile = await tmp.file();
92
+ const stderr = createWriteStream(tmpFile.path);
89
93
  const rootPath = join(fixturesDir, 'monorepo_npm_module');
90
94
  const basePath = join(rootPath, 'packages', 'frontend');
91
95
  const paths = {
@@ -100,6 +104,7 @@ test('Serves edge functions in a monorepo setup', async () => {
100
104
  port,
101
105
  rootPath,
102
106
  servePath,
107
+ stderr,
103
108
  });
104
109
  const functions = [
105
110
  {
@@ -131,4 +136,6 @@ test('Serves edge functions in a monorepo setup', async () => {
131
136
  });
132
137
  expect(response1.status).toBe(200);
133
138
  expect(await response1.text()).toBe(`<parent-1><child-1>JavaScript</child-1></parent-1>, <parent-2><child-2><grandchild-1>APIs<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-2>, <parent-3><child-2><grandchild-1>Markup<cwd>${process.cwd()}</cwd></grandchild-1></child-2></parent-3>`);
139
+ expect(await readFile(tmpFile.path, 'utf8')).toContain('[func1] Something is on fire');
140
+ await tmpFile.cleanup();
134
141
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/edge-bundler",
3
- "version": "11.0.0",
3
+ "version": "11.2.0",
4
4
  "description": "Intelligently prepare Netlify Edge Functions for deployment",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.js",
@@ -75,13 +75,13 @@
75
75
  },
76
76
  "dependencies": {
77
77
  "@import-maps/resolve": "^1.0.1",
78
- "@vercel/nft": "^0.24.3",
78
+ "@vercel/nft": "^0.26.0",
79
79
  "ajv": "^8.11.2",
80
80
  "ajv-errors": "^3.0.0",
81
81
  "better-ajv-errors": "^1.2.0",
82
82
  "common-path-prefix": "^3.0.0",
83
83
  "env-paths": "^3.0.0",
84
- "esbuild": "0.19.9",
84
+ "esbuild": "0.19.11",
85
85
  "execa": "^6.0.0",
86
86
  "find-up": "^6.3.0",
87
87
  "get-package-name": "^2.2.0",