@netlify/dev 4.1.1 → 4.1.3

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.
package/dist/main.cjs CHANGED
@@ -345,37 +345,50 @@ var NetlifyDev = class {
345
345
  this.#projectRoot = projectRoot;
346
346
  this.#staticHandlerAdditionalDirectories = options.staticFiles?.directories ?? [];
347
347
  }
348
- async handleInEphemeralDirectory(matchRequest, getHandleRequest, destPath, options = {}) {
349
- const edgeFunctionMatch = await this.#edgeFunctionsHandler?.match(matchRequest);
348
+ /**
349
+ * Runs a request through the Netlify request chain and returns a `Response`
350
+ * if there's a match. We must not disturb the incoming request unless we
351
+ * know we will be returning a response, so this method takes a read-only
352
+ * request that is safe to access (used for matching) and a getter for the
353
+ * actual request (used for handling matches).
354
+ *
355
+ * @param readRequest Read-only version of the request (without a body)
356
+ * @param getWriteRequest Getter for the actual request (with a body)
357
+ * @param destPath Destination directory for compiled files
358
+ * @param options Options object
359
+ * @returns
360
+ */
361
+ async handleInEphemeralDirectory(readRequest, getWriteRequest, destPath, options = {}) {
362
+ const edgeFunctionMatch = await this.#edgeFunctionsHandler?.match(readRequest);
350
363
  if (edgeFunctionMatch) {
351
364
  return {
352
- response: await edgeFunctionMatch.handle(getHandleRequest()),
365
+ response: await edgeFunctionMatch.handle(getWriteRequest()),
353
366
  type: "edge-function"
354
367
  };
355
368
  }
356
- const functionMatch = await this.#functionsHandler?.match(matchRequest, destPath);
369
+ const functionMatch = await this.#functionsHandler?.match(readRequest, destPath);
357
370
  if (functionMatch) {
358
371
  if (functionMatch.preferStatic) {
359
- const staticMatch2 = await this.#staticHandler?.match(matchRequest);
372
+ const staticMatch2 = await this.#staticHandler?.match(readRequest);
360
373
  if (staticMatch2) {
361
374
  const response = await staticMatch2.handle();
362
- await this.#headersHandler?.apply(matchRequest, response, options.headersCollector);
375
+ await this.#headersHandler?.apply(readRequest, response, options.headersCollector);
363
376
  return { response, type: "static" };
364
377
  }
365
378
  }
366
- return { response: await functionMatch.handle(getHandleRequest()), type: "function" };
379
+ return { response: await functionMatch.handle(getWriteRequest()), type: "function" };
367
380
  }
368
- const redirectMatch = await this.#redirectsHandler?.match(matchRequest);
381
+ const redirectMatch = await this.#redirectsHandler?.match(readRequest);
369
382
  if (redirectMatch) {
370
383
  const functionMatch2 = await this.#functionsHandler?.match(new Request(redirectMatch.target), destPath);
371
384
  if (functionMatch2 && !functionMatch2.preferStatic) {
372
385
  return {
373
- response: await functionMatch2.handle(getHandleRequest()),
386
+ response: await functionMatch2.handle(getWriteRequest()),
374
387
  type: "function"
375
388
  };
376
389
  }
377
390
  const response = await this.#redirectsHandler?.handle(
378
- getHandleRequest(),
391
+ getWriteRequest(),
379
392
  redirectMatch,
380
393
  async (maybeStaticFile) => {
381
394
  const staticMatch2 = await this.#staticHandler?.match(maybeStaticFile);
@@ -393,17 +406,17 @@ var NetlifyDev = class {
393
406
  return { response, type: "redirect" };
394
407
  }
395
408
  }
396
- const { pathname } = new URL(matchRequest.url);
409
+ const { pathname } = new URL(readRequest.url);
397
410
  if (pathname.startsWith("/.netlify/images")) {
398
411
  this.#logger.error(
399
- "The Netlify Image CDN is currently only supported in the Netlify CLI. Run `npx netlify dev` to get started."
412
+ `The Netlify Image CDN is currently only supported in the Netlify CLI. Run ${(0, import_dev_utils.netlifyCommand)("npx netlify dev")} to get started.`
400
413
  );
401
414
  return;
402
415
  }
403
- const staticMatch = await this.#staticHandler?.match(matchRequest);
416
+ const staticMatch = await this.#staticHandler?.match(readRequest);
404
417
  if (staticMatch) {
405
418
  const response = await staticMatch.handle();
406
- await this.#headersHandler?.apply(matchRequest, response, options.headersCollector);
419
+ await this.#headersHandler?.apply(readRequest, response, options.headersCollector);
407
420
  return { response, type: "static" };
408
421
  }
409
422
  }
@@ -424,10 +437,19 @@ var NetlifyDev = class {
424
437
  });
425
438
  return config;
426
439
  }
440
+ /**
441
+ * Runs a `Request` through the Netlify request chain. If there is a match,
442
+ * it returns the resulting `Response` object; if not, it returns `undefined`.
443
+ */
427
444
  async handle(request, options = {}) {
428
445
  const result = await this.handleAndIntrospect(request, options);
429
446
  return result?.response;
430
447
  }
448
+ /**
449
+ * Runs a `Request` through the Netlify request chain. If there is a match,
450
+ * it returns an object with the resulting `Response` object and information
451
+ * about the match; if not, it returns `undefined`.
452
+ */
431
453
  async handleAndIntrospect(request, options = {}) {
432
454
  await import_node_fs2.promises.mkdir(this.#functionsServePath, { recursive: true });
433
455
  const destPath = await import_node_fs2.promises.mkdtemp(import_node_path2.default.join(this.#functionsServePath, `_`));
@@ -443,6 +465,11 @@ var NetlifyDev = class {
443
465
  }
444
466
  }
445
467
  }
468
+ /**
469
+ * Runs a Node.js `IncomingMessage` through the Netlify request chain. If
470
+ * there is a match, it returns an object with the resulting `Response`
471
+ * object and information about the match; if not, it returns `undefined`.
472
+ */
446
473
  async handleAndIntrospectNodeRequest(request, options = {}) {
447
474
  await import_node_fs2.promises.mkdir(this.#functionsServePath, { recursive: true });
448
475
  const destPath = await import_node_fs2.promises.mkdtemp(import_node_path2.default.join(this.#functionsServePath, `_`));
@@ -566,6 +593,9 @@ var NetlifyDev = class {
566
593
  async stop() {
567
594
  await Promise.allSettled(this.#cleanupJobs.map((task) => task()));
568
595
  }
596
+ getEnabledFeatures() {
597
+ return Object.entries(this.#features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
598
+ }
569
599
  };
570
600
  // Annotate the CommonJS export names for ESM import in node:
571
601
  0 && (module.exports = {
package/dist/main.d.cts CHANGED
@@ -87,13 +87,40 @@ type ResponseType = 'edge-function' | 'function' | 'redirect' | 'static';
87
87
  declare class NetlifyDev {
88
88
  #private;
89
89
  constructor(options: NetlifyDevOptions);
90
+ /**
91
+ * Runs a request through the Netlify request chain and returns a `Response`
92
+ * if there's a match. We must not disturb the incoming request unless we
93
+ * know we will be returning a response, so this method takes a read-only
94
+ * request that is safe to access (used for matching) and a getter for the
95
+ * actual request (used for handling matches).
96
+ *
97
+ * @param readRequest Read-only version of the request (without a body)
98
+ * @param getWriteRequest Getter for the actual request (with a body)
99
+ * @param destPath Destination directory for compiled files
100
+ * @param options Options object
101
+ * @returns
102
+ */
90
103
  private handleInEphemeralDirectory;
91
104
  private getConfig;
105
+ /**
106
+ * Runs a `Request` through the Netlify request chain. If there is a match,
107
+ * it returns the resulting `Response` object; if not, it returns `undefined`.
108
+ */
92
109
  handle(request: Request, options?: HandleOptions): Promise<Response | undefined>;
110
+ /**
111
+ * Runs a `Request` through the Netlify request chain. If there is a match,
112
+ * it returns an object with the resulting `Response` object and information
113
+ * about the match; if not, it returns `undefined`.
114
+ */
93
115
  handleAndIntrospect(request: Request, options?: HandleOptions): Promise<{
94
116
  response: Response;
95
117
  type: ResponseType;
96
118
  } | undefined>;
119
+ /**
120
+ * Runs a Node.js `IncomingMessage` through the Netlify request chain. If
121
+ * there is a match, it returns an object with the resulting `Response`
122
+ * object and information about the match; if not, it returns `undefined`.
123
+ */
97
124
  handleAndIntrospectNodeRequest(request: IncomingMessage, options?: HandleOptions): Promise<{
98
125
  response: Response;
99
126
  type: ResponseType;
@@ -103,6 +130,7 @@ declare class NetlifyDev {
103
130
  serverAddress: string | undefined;
104
131
  }>;
105
132
  stop(): Promise<void>;
133
+ getEnabledFeatures(): string[];
106
134
  }
107
135
 
108
136
  export { type Features, NetlifyDev, type ResponseType };
package/dist/main.d.ts CHANGED
@@ -87,13 +87,40 @@ type ResponseType = 'edge-function' | 'function' | 'redirect' | 'static';
87
87
  declare class NetlifyDev {
88
88
  #private;
89
89
  constructor(options: NetlifyDevOptions);
90
+ /**
91
+ * Runs a request through the Netlify request chain and returns a `Response`
92
+ * if there's a match. We must not disturb the incoming request unless we
93
+ * know we will be returning a response, so this method takes a read-only
94
+ * request that is safe to access (used for matching) and a getter for the
95
+ * actual request (used for handling matches).
96
+ *
97
+ * @param readRequest Read-only version of the request (without a body)
98
+ * @param getWriteRequest Getter for the actual request (with a body)
99
+ * @param destPath Destination directory for compiled files
100
+ * @param options Options object
101
+ * @returns
102
+ */
90
103
  private handleInEphemeralDirectory;
91
104
  private getConfig;
105
+ /**
106
+ * Runs a `Request` through the Netlify request chain. If there is a match,
107
+ * it returns the resulting `Response` object; if not, it returns `undefined`.
108
+ */
92
109
  handle(request: Request, options?: HandleOptions): Promise<Response | undefined>;
110
+ /**
111
+ * Runs a `Request` through the Netlify request chain. If there is a match,
112
+ * it returns an object with the resulting `Response` object and information
113
+ * about the match; if not, it returns `undefined`.
114
+ */
93
115
  handleAndIntrospect(request: Request, options?: HandleOptions): Promise<{
94
116
  response: Response;
95
117
  type: ResponseType;
96
118
  } | undefined>;
119
+ /**
120
+ * Runs a Node.js `IncomingMessage` through the Netlify request chain. If
121
+ * there is a match, it returns an object with the resulting `Response`
122
+ * object and information about the match; if not, it returns `undefined`.
123
+ */
97
124
  handleAndIntrospectNodeRequest(request: IncomingMessage, options?: HandleOptions): Promise<{
98
125
  response: Response;
99
126
  type: ResponseType;
@@ -103,6 +130,7 @@ declare class NetlifyDev {
103
130
  serverAddress: string | undefined;
104
131
  }>;
105
132
  stop(): Promise<void>;
133
+ getEnabledFeatures(): string[];
106
134
  }
107
135
 
108
136
  export { type Features, NetlifyDev, type ResponseType };
package/dist/main.js CHANGED
@@ -3,7 +3,14 @@ import { promises as fs2 } from "fs";
3
3
  import path2 from "path";
4
4
  import process2 from "process";
5
5
  import { resolveConfig } from "@netlify/config";
6
- import { ensureNetlifyIgnore, getAPIToken, mockLocation, LocalState, HTTPServer } from "@netlify/dev-utils";
6
+ import {
7
+ ensureNetlifyIgnore,
8
+ getAPIToken,
9
+ mockLocation,
10
+ LocalState,
11
+ HTTPServer,
12
+ netlifyCommand
13
+ } from "@netlify/dev-utils";
7
14
  import { EdgeFunctionsHandler } from "@netlify/edge-functions/dev";
8
15
  import { FunctionsHandler } from "@netlify/functions/dev";
9
16
  import { HeadersHandler } from "@netlify/headers";
@@ -311,37 +318,50 @@ var NetlifyDev = class {
311
318
  this.#projectRoot = projectRoot;
312
319
  this.#staticHandlerAdditionalDirectories = options.staticFiles?.directories ?? [];
313
320
  }
314
- async handleInEphemeralDirectory(matchRequest, getHandleRequest, destPath, options = {}) {
315
- const edgeFunctionMatch = await this.#edgeFunctionsHandler?.match(matchRequest);
321
+ /**
322
+ * Runs a request through the Netlify request chain and returns a `Response`
323
+ * if there's a match. We must not disturb the incoming request unless we
324
+ * know we will be returning a response, so this method takes a read-only
325
+ * request that is safe to access (used for matching) and a getter for the
326
+ * actual request (used for handling matches).
327
+ *
328
+ * @param readRequest Read-only version of the request (without a body)
329
+ * @param getWriteRequest Getter for the actual request (with a body)
330
+ * @param destPath Destination directory for compiled files
331
+ * @param options Options object
332
+ * @returns
333
+ */
334
+ async handleInEphemeralDirectory(readRequest, getWriteRequest, destPath, options = {}) {
335
+ const edgeFunctionMatch = await this.#edgeFunctionsHandler?.match(readRequest);
316
336
  if (edgeFunctionMatch) {
317
337
  return {
318
- response: await edgeFunctionMatch.handle(getHandleRequest()),
338
+ response: await edgeFunctionMatch.handle(getWriteRequest()),
319
339
  type: "edge-function"
320
340
  };
321
341
  }
322
- const functionMatch = await this.#functionsHandler?.match(matchRequest, destPath);
342
+ const functionMatch = await this.#functionsHandler?.match(readRequest, destPath);
323
343
  if (functionMatch) {
324
344
  if (functionMatch.preferStatic) {
325
- const staticMatch2 = await this.#staticHandler?.match(matchRequest);
345
+ const staticMatch2 = await this.#staticHandler?.match(readRequest);
326
346
  if (staticMatch2) {
327
347
  const response = await staticMatch2.handle();
328
- await this.#headersHandler?.apply(matchRequest, response, options.headersCollector);
348
+ await this.#headersHandler?.apply(readRequest, response, options.headersCollector);
329
349
  return { response, type: "static" };
330
350
  }
331
351
  }
332
- return { response: await functionMatch.handle(getHandleRequest()), type: "function" };
352
+ return { response: await functionMatch.handle(getWriteRequest()), type: "function" };
333
353
  }
334
- const redirectMatch = await this.#redirectsHandler?.match(matchRequest);
354
+ const redirectMatch = await this.#redirectsHandler?.match(readRequest);
335
355
  if (redirectMatch) {
336
356
  const functionMatch2 = await this.#functionsHandler?.match(new Request(redirectMatch.target), destPath);
337
357
  if (functionMatch2 && !functionMatch2.preferStatic) {
338
358
  return {
339
- response: await functionMatch2.handle(getHandleRequest()),
359
+ response: await functionMatch2.handle(getWriteRequest()),
340
360
  type: "function"
341
361
  };
342
362
  }
343
363
  const response = await this.#redirectsHandler?.handle(
344
- getHandleRequest(),
364
+ getWriteRequest(),
345
365
  redirectMatch,
346
366
  async (maybeStaticFile) => {
347
367
  const staticMatch2 = await this.#staticHandler?.match(maybeStaticFile);
@@ -359,17 +379,17 @@ var NetlifyDev = class {
359
379
  return { response, type: "redirect" };
360
380
  }
361
381
  }
362
- const { pathname } = new URL(matchRequest.url);
382
+ const { pathname } = new URL(readRequest.url);
363
383
  if (pathname.startsWith("/.netlify/images")) {
364
384
  this.#logger.error(
365
- "The Netlify Image CDN is currently only supported in the Netlify CLI. Run `npx netlify dev` to get started."
385
+ `The Netlify Image CDN is currently only supported in the Netlify CLI. Run ${netlifyCommand("npx netlify dev")} to get started.`
366
386
  );
367
387
  return;
368
388
  }
369
- const staticMatch = await this.#staticHandler?.match(matchRequest);
389
+ const staticMatch = await this.#staticHandler?.match(readRequest);
370
390
  if (staticMatch) {
371
391
  const response = await staticMatch.handle();
372
- await this.#headersHandler?.apply(matchRequest, response, options.headersCollector);
392
+ await this.#headersHandler?.apply(readRequest, response, options.headersCollector);
373
393
  return { response, type: "static" };
374
394
  }
375
395
  }
@@ -390,10 +410,19 @@ var NetlifyDev = class {
390
410
  });
391
411
  return config;
392
412
  }
413
+ /**
414
+ * Runs a `Request` through the Netlify request chain. If there is a match,
415
+ * it returns the resulting `Response` object; if not, it returns `undefined`.
416
+ */
393
417
  async handle(request, options = {}) {
394
418
  const result = await this.handleAndIntrospect(request, options);
395
419
  return result?.response;
396
420
  }
421
+ /**
422
+ * Runs a `Request` through the Netlify request chain. If there is a match,
423
+ * it returns an object with the resulting `Response` object and information
424
+ * about the match; if not, it returns `undefined`.
425
+ */
397
426
  async handleAndIntrospect(request, options = {}) {
398
427
  await fs2.mkdir(this.#functionsServePath, { recursive: true });
399
428
  const destPath = await fs2.mkdtemp(path2.join(this.#functionsServePath, `_`));
@@ -409,6 +438,11 @@ var NetlifyDev = class {
409
438
  }
410
439
  }
411
440
  }
441
+ /**
442
+ * Runs a Node.js `IncomingMessage` through the Netlify request chain. If
443
+ * there is a match, it returns an object with the resulting `Response`
444
+ * object and information about the match; if not, it returns `undefined`.
445
+ */
412
446
  async handleAndIntrospectNodeRequest(request, options = {}) {
413
447
  await fs2.mkdir(this.#functionsServePath, { recursive: true });
414
448
  const destPath = await fs2.mkdtemp(path2.join(this.#functionsServePath, `_`));
@@ -532,6 +566,9 @@ var NetlifyDev = class {
532
566
  async stop() {
533
567
  await Promise.allSettled(this.#cleanupJobs.map((task) => task()));
534
568
  }
569
+ getEnabledFeatures() {
570
+ return Object.entries(this.#features).filter(([_, enabled]) => enabled).map(([feature]) => feature);
571
+ }
535
572
  };
536
573
  export {
537
574
  NetlifyDev
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/dev",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
4
4
  "description": "Emulation of the Netlify environment for local development",
5
5
  "type": "module",
6
6
  "engines": {
@@ -47,20 +47,20 @@
47
47
  "author": "Netlify Inc.",
48
48
  "devDependencies": {
49
49
  "@netlify/api": "^14.0.3",
50
- "@netlify/types": "2.0.1",
50
+ "@netlify/types": "2.0.2",
51
51
  "tsup": "^8.0.0",
52
52
  "vitest": "^3.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "@netlify/blobs": "9.1.4",
55
+ "@netlify/blobs": "9.1.5",
56
56
  "@netlify/config": "^23.0.8",
57
- "@netlify/dev-utils": "3.1.0",
58
- "@netlify/edge-functions": "2.14.0",
59
- "@netlify/functions": "4.1.1",
60
- "@netlify/headers": "2.0.0",
61
- "@netlify/redirects": "3.0.0",
62
- "@netlify/runtime": "4.0.0",
63
- "@netlify/static": "3.0.0",
57
+ "@netlify/dev-utils": "3.1.1",
58
+ "@netlify/edge-functions": "2.14.2",
59
+ "@netlify/functions": "4.1.2",
60
+ "@netlify/headers": "2.0.1",
61
+ "@netlify/redirects": "3.0.1",
62
+ "@netlify/runtime": "4.0.2",
63
+ "@netlify/static": "3.0.1",
64
64
  "ulid": "^3.0.0"
65
65
  }
66
66
  }