@netlify/dev 4.1.3 → 4.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.
package/README.md CHANGED
@@ -8,7 +8,7 @@ users, it is primarily designed as a foundational library for higher-level tools
8
8
  [Netlify Vite Plugin](https://docs.netlify.com/integrations/vite/overview/).
9
9
 
10
10
  It provides a local request pipeline that mimics the Netlify platform’s request handling, including support for
11
- Functions, Blobs, Static files, and Redirects.
11
+ Functions, Blobs, Static files, Redirects, and Image CDN.
12
12
 
13
13
  ## 🚧 Feature Support
14
14
 
@@ -21,7 +21,7 @@ Functions, Blobs, Static files, and Redirects.
21
21
  | Redirects and Rewrites | ✅ Yes |
22
22
  | Headers | ✅ Yes |
23
23
  | Environment Variables | ✅ Yes |
24
- | Image CDN | No |
24
+ | Image CDN | Yes |
25
25
 
26
26
  > Note: Missing features will be added incrementally. This module is **not** intended to be a full replacement for the
27
27
  > Netlify CLI.
package/dist/main.cjs CHANGED
@@ -41,6 +41,7 @@ var import_dev_utils = require("@netlify/dev-utils");
41
41
  var import_dev = require("@netlify/edge-functions/dev");
42
42
  var import_dev2 = require("@netlify/functions/dev");
43
43
  var import_headers = require("@netlify/headers");
44
+ var import_images = require("@netlify/images");
44
45
  var import_redirects = require("@netlify/redirects");
45
46
  var import_static = require("@netlify/static");
46
47
 
@@ -314,6 +315,7 @@ var NetlifyDev = class {
314
315
  #config;
315
316
  #features;
316
317
  #headersHandler;
318
+ #imageHandler;
317
319
  #logger;
318
320
  #projectRoot;
319
321
  #redirectsHandler;
@@ -336,6 +338,7 @@ var NetlifyDev = class {
336
338
  environmentVariables: options.environmentVariables?.enabled !== false,
337
339
  functions: options.functions?.enabled !== false,
338
340
  headers: options.headers?.enabled !== false,
341
+ images: options.images?.enabled !== false,
339
342
  redirects: options.redirects?.enabled !== false,
340
343
  static: options.staticFiles?.enabled !== false
341
344
  };
@@ -366,6 +369,11 @@ var NetlifyDev = class {
366
369
  type: "edge-function"
367
370
  };
368
371
  }
372
+ const imageMatch = this.#imageHandler?.match(readRequest);
373
+ if (imageMatch) {
374
+ const response = await imageMatch.handle();
375
+ return { response, type: "image" };
376
+ }
369
377
  const functionMatch = await this.#functionsHandler?.match(readRequest, destPath);
370
378
  if (functionMatch) {
371
379
  if (functionMatch.preferStatic) {
@@ -380,7 +388,13 @@ var NetlifyDev = class {
380
388
  }
381
389
  const redirectMatch = await this.#redirectsHandler?.match(readRequest);
382
390
  if (redirectMatch) {
383
- const functionMatch2 = await this.#functionsHandler?.match(new Request(redirectMatch.target), destPath);
391
+ const redirectRequest = new Request(redirectMatch.target);
392
+ const imageMatch2 = this.#imageHandler?.match(redirectRequest);
393
+ if (imageMatch2) {
394
+ const response2 = await imageMatch2.handle();
395
+ return { response: response2, type: "image" };
396
+ }
397
+ const functionMatch2 = await this.#functionsHandler?.match(redirectRequest, destPath);
384
398
  if (functionMatch2 && !functionMatch2.preferStatic) {
385
399
  return {
386
400
  response: await functionMatch2.handle(getWriteRequest()),
@@ -406,13 +420,6 @@ var NetlifyDev = class {
406
420
  return { response, type: "redirect" };
407
421
  }
408
422
  }
409
- const { pathname } = new URL(readRequest.url);
410
- if (pathname.startsWith("/.netlify/images")) {
411
- this.#logger.error(
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.`
413
- );
414
- return;
415
- }
416
423
  const staticMatch = await this.#staticHandler?.match(readRequest);
417
424
  if (staticMatch) {
418
425
  const response = await staticMatch.handle();
@@ -506,7 +513,7 @@ var NetlifyDev = class {
506
513
  let serverAddress;
507
514
  if (typeof this.#server === "string") {
508
515
  serverAddress = this.#server;
509
- } else if (this.#features.edgeFunctions) {
516
+ } else if (this.#features.edgeFunctions || this.#features.images) {
510
517
  const passthroughServer = new import_dev_utils.HTTPServer(async (req) => {
511
518
  const res = await this.handle(req);
512
519
  return res ?? new Response(null, { status: 404 });
@@ -586,6 +593,13 @@ var NetlifyDev = class {
586
593
  ]
587
594
  });
588
595
  }
596
+ if (this.#features.images) {
597
+ this.#imageHandler = new import_images.ImageHandler({
598
+ imagesConfig: this.#config?.config.images,
599
+ logger: this.#logger,
600
+ originServerAddress: serverAddress
601
+ });
602
+ }
589
603
  return {
590
604
  serverAddress
591
605
  };
package/dist/main.d.cts CHANGED
@@ -43,6 +43,14 @@ interface Features {
43
43
  headers?: {
44
44
  enabled?: boolean;
45
45
  };
46
+ /**
47
+ * Configuration options for Netlify Image CDN.
48
+ *
49
+ * {@link} https://docs.netlify.com/image-cdn/overview/
50
+ */
51
+ images?: {
52
+ enabled?: boolean;
53
+ };
46
54
  /**
47
55
  * Configuration options for Netlify redirects and rewrites.
48
56
  *
@@ -83,7 +91,7 @@ interface HandleOptions {
83
91
  */
84
92
  headersCollector?: HeadersCollector;
85
93
  }
86
- type ResponseType = 'edge-function' | 'function' | 'redirect' | 'static';
94
+ type ResponseType = 'edge-function' | 'function' | 'image' | 'redirect' | 'static';
87
95
  declare class NetlifyDev {
88
96
  #private;
89
97
  constructor(options: NetlifyDevOptions);
package/dist/main.d.ts CHANGED
@@ -43,6 +43,14 @@ interface Features {
43
43
  headers?: {
44
44
  enabled?: boolean;
45
45
  };
46
+ /**
47
+ * Configuration options for Netlify Image CDN.
48
+ *
49
+ * {@link} https://docs.netlify.com/image-cdn/overview/
50
+ */
51
+ images?: {
52
+ enabled?: boolean;
53
+ };
46
54
  /**
47
55
  * Configuration options for Netlify redirects and rewrites.
48
56
  *
@@ -83,7 +91,7 @@ interface HandleOptions {
83
91
  */
84
92
  headersCollector?: HeadersCollector;
85
93
  }
86
- type ResponseType = 'edge-function' | 'function' | 'redirect' | 'static';
94
+ type ResponseType = 'edge-function' | 'function' | 'image' | 'redirect' | 'static';
87
95
  declare class NetlifyDev {
88
96
  #private;
89
97
  constructor(options: NetlifyDevOptions);
package/dist/main.js CHANGED
@@ -3,17 +3,11 @@ 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 {
7
- ensureNetlifyIgnore,
8
- getAPIToken,
9
- mockLocation,
10
- LocalState,
11
- HTTPServer,
12
- netlifyCommand
13
- } from "@netlify/dev-utils";
6
+ import { ensureNetlifyIgnore, getAPIToken, mockLocation, LocalState, HTTPServer } from "@netlify/dev-utils";
14
7
  import { EdgeFunctionsHandler } from "@netlify/edge-functions/dev";
15
8
  import { FunctionsHandler } from "@netlify/functions/dev";
16
9
  import { HeadersHandler } from "@netlify/headers";
10
+ import { ImageHandler } from "@netlify/images";
17
11
  import { RedirectsHandler } from "@netlify/redirects";
18
12
  import { StaticHandler } from "@netlify/static";
19
13
 
@@ -287,6 +281,7 @@ var NetlifyDev = class {
287
281
  #config;
288
282
  #features;
289
283
  #headersHandler;
284
+ #imageHandler;
290
285
  #logger;
291
286
  #projectRoot;
292
287
  #redirectsHandler;
@@ -309,6 +304,7 @@ var NetlifyDev = class {
309
304
  environmentVariables: options.environmentVariables?.enabled !== false,
310
305
  functions: options.functions?.enabled !== false,
311
306
  headers: options.headers?.enabled !== false,
307
+ images: options.images?.enabled !== false,
312
308
  redirects: options.redirects?.enabled !== false,
313
309
  static: options.staticFiles?.enabled !== false
314
310
  };
@@ -339,6 +335,11 @@ var NetlifyDev = class {
339
335
  type: "edge-function"
340
336
  };
341
337
  }
338
+ const imageMatch = this.#imageHandler?.match(readRequest);
339
+ if (imageMatch) {
340
+ const response = await imageMatch.handle();
341
+ return { response, type: "image" };
342
+ }
342
343
  const functionMatch = await this.#functionsHandler?.match(readRequest, destPath);
343
344
  if (functionMatch) {
344
345
  if (functionMatch.preferStatic) {
@@ -353,7 +354,13 @@ var NetlifyDev = class {
353
354
  }
354
355
  const redirectMatch = await this.#redirectsHandler?.match(readRequest);
355
356
  if (redirectMatch) {
356
- const functionMatch2 = await this.#functionsHandler?.match(new Request(redirectMatch.target), destPath);
357
+ const redirectRequest = new Request(redirectMatch.target);
358
+ const imageMatch2 = this.#imageHandler?.match(redirectRequest);
359
+ if (imageMatch2) {
360
+ const response2 = await imageMatch2.handle();
361
+ return { response: response2, type: "image" };
362
+ }
363
+ const functionMatch2 = await this.#functionsHandler?.match(redirectRequest, destPath);
357
364
  if (functionMatch2 && !functionMatch2.preferStatic) {
358
365
  return {
359
366
  response: await functionMatch2.handle(getWriteRequest()),
@@ -379,13 +386,6 @@ var NetlifyDev = class {
379
386
  return { response, type: "redirect" };
380
387
  }
381
388
  }
382
- const { pathname } = new URL(readRequest.url);
383
- if (pathname.startsWith("/.netlify/images")) {
384
- this.#logger.error(
385
- `The Netlify Image CDN is currently only supported in the Netlify CLI. Run ${netlifyCommand("npx netlify dev")} to get started.`
386
- );
387
- return;
388
- }
389
389
  const staticMatch = await this.#staticHandler?.match(readRequest);
390
390
  if (staticMatch) {
391
391
  const response = await staticMatch.handle();
@@ -479,7 +479,7 @@ var NetlifyDev = class {
479
479
  let serverAddress;
480
480
  if (typeof this.#server === "string") {
481
481
  serverAddress = this.#server;
482
- } else if (this.#features.edgeFunctions) {
482
+ } else if (this.#features.edgeFunctions || this.#features.images) {
483
483
  const passthroughServer = new HTTPServer(async (req) => {
484
484
  const res = await this.handle(req);
485
485
  return res ?? new Response(null, { status: 404 });
@@ -559,6 +559,13 @@ var NetlifyDev = class {
559
559
  ]
560
560
  });
561
561
  }
562
+ if (this.#features.images) {
563
+ this.#imageHandler = new ImageHandler({
564
+ imagesConfig: this.#config?.config.images,
565
+ logger: this.#logger,
566
+ originServerAddress: serverAddress
567
+ });
568
+ }
562
569
  return {
563
570
  serverAddress
564
571
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/dev",
3
- "version": "4.1.3",
3
+ "version": "4.2.0",
4
4
  "description": "Emulation of the Netlify environment for local development",
5
5
  "type": "module",
6
6
  "engines": {
@@ -52,15 +52,16 @@
52
52
  "vitest": "^3.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "@netlify/blobs": "9.1.5",
56
- "@netlify/config": "^23.0.8",
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",
55
+ "@netlify/blobs": "9.1.6",
56
+ "@netlify/config": "^23.0.10",
57
+ "@netlify/dev-utils": "3.2.0",
58
+ "@netlify/edge-functions": "2.14.4",
59
+ "@netlify/functions": "4.1.4",
60
+ "@netlify/headers": "2.0.2",
61
+ "@netlify/images": "1.0.0",
62
+ "@netlify/redirects": "3.0.2",
63
+ "@netlify/runtime": "4.0.3",
64
+ "@netlify/static": "3.0.2",
64
65
  "ulid": "^3.0.0"
65
66
  }
66
67
  }