ipx 2.0.2 → 3.0.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/dist/cli.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  const listhen = require('listhen');
4
4
  const citty = require('citty');
5
5
  const cli = require('listhen/cli');
6
- const nodeFs = require('./shared/ipx.cf456174.cjs');
6
+ const nodeFs = require('./shared/ipx.7601f01b.cjs');
7
7
  require('defu');
8
8
  require('ufo');
9
9
  require('h3');
@@ -15,7 +15,7 @@ require('ofetch');
15
15
  require('pathe');
16
16
 
17
17
  const name = "ipx";
18
- const version = "2.0.2";
18
+ const version = "3.0.0";
19
19
  const description = "High performance, secure and easy-to-use image optimizer.";
20
20
 
21
21
  const serve = citty.defineCommand({
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { listen } from 'listhen';
2
2
  import { defineCommand, runMain } from 'citty';
3
3
  import { getArgs, parseArgs } from 'listhen/cli';
4
- import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.8dfec2f9.mjs';
4
+ import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.b027cc1c.mjs';
5
5
  import 'defu';
6
6
  import 'ufo';
7
7
  import 'h3';
@@ -13,7 +13,7 @@ import 'ofetch';
13
13
  import 'pathe';
14
14
 
15
15
  const name = "ipx";
16
- const version = "2.0.2";
16
+ const version = "3.0.0";
17
17
  const description = "High performance, secure and easy-to-use image optimizer.";
18
18
 
19
19
  const serve = defineCommand({
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- const nodeFs = require('./shared/ipx.cf456174.cjs');
3
+ const nodeFs = require('./shared/ipx.7601f01b.cjs');
4
+ const h3 = require('h3');
4
5
  require('defu');
5
6
  require('ufo');
6
- require('h3');
7
7
  require('image-meta');
8
8
  require('destr');
9
9
  require('@fastify/accept-negotiator');
@@ -11,8 +11,9 @@ require('etag');
11
11
  require('ofetch');
12
12
  require('pathe');
13
13
 
14
- function unstorageToIPXStorage(storage, prefix) {
15
- const resolveKey = (id) => prefix + ":" + id;
14
+ function unstorageToIPXStorage(storage, _options = {}) {
15
+ const options = typeof _options === "string" ? { prefix: _options } : _options;
16
+ const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
16
17
  return {
17
18
  name: "ipx:" + (storage.name || "unstorage"),
18
19
  async getMeta(id, opts = {}) {
@@ -28,8 +29,24 @@ function unstorageToIPXStorage(storage, prefix) {
28
29
  return;
29
30
  }
30
31
  const storageKey = resolveKey(id);
31
- const data = await storage.getItemRaw(storageKey, opts);
32
- return data;
32
+ let data = await storage.getItemRaw(storageKey, opts);
33
+ if (!data) {
34
+ return;
35
+ }
36
+ if (data instanceof Blob) {
37
+ data = await data.arrayBuffer();
38
+ }
39
+ try {
40
+ return Buffer.from(data);
41
+ } catch (error) {
42
+ throw h3.createError({
43
+ statusCode: 500,
44
+ statusText: `IPX_STORAGE_ERROR`,
45
+ message: `Failed to parse storage data to Buffer:
46
+ ${error.message}`,
47
+ cause: error
48
+ });
49
+ }
33
50
  }
34
51
  };
35
52
  }
package/dist/index.d.cts CHANGED
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
145
145
  declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
146
146
 
147
147
  type NodeFSSOptions = {
148
- dir?: string;
148
+ dir?: string | string[];
149
149
  maxAge?: number;
150
150
  };
151
151
  declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
152
152
 
153
- declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
153
+ type UnstorageIPXStorageOptions = {
154
+ prefix?: string;
155
+ };
156
+ declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
154
157
 
155
- export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
158
+ export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
package/dist/index.d.mts CHANGED
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
145
145
  declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
146
146
 
147
147
  type NodeFSSOptions = {
148
- dir?: string;
148
+ dir?: string | string[];
149
149
  maxAge?: number;
150
150
  };
151
151
  declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
152
152
 
153
- declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
153
+ type UnstorageIPXStorageOptions = {
154
+ prefix?: string;
155
+ };
156
+ declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
154
157
 
155
- export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
158
+ export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
package/dist/index.d.ts CHANGED
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
145
145
  declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
146
146
 
147
147
  type NodeFSSOptions = {
148
- dir?: string;
148
+ dir?: string | string[];
149
149
  maxAge?: number;
150
150
  };
151
151
  declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
152
152
 
153
- declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
153
+ type UnstorageIPXStorageOptions = {
154
+ prefix?: string;
155
+ };
156
+ declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
154
157
 
155
- export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
158
+ export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.8dfec2f9.mjs';
1
+ export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.b027cc1c.mjs';
2
+ import { createError } from 'h3';
2
3
  import 'defu';
3
4
  import 'ufo';
4
- import 'h3';
5
5
  import 'image-meta';
6
6
  import 'destr';
7
7
  import '@fastify/accept-negotiator';
@@ -9,8 +9,9 @@ import 'etag';
9
9
  import 'ofetch';
10
10
  import 'pathe';
11
11
 
12
- function unstorageToIPXStorage(storage, prefix) {
13
- const resolveKey = (id) => prefix + ":" + id;
12
+ function unstorageToIPXStorage(storage, _options = {}) {
13
+ const options = typeof _options === "string" ? { prefix: _options } : _options;
14
+ const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
14
15
  return {
15
16
  name: "ipx:" + (storage.name || "unstorage"),
16
17
  async getMeta(id, opts = {}) {
@@ -26,8 +27,24 @@ function unstorageToIPXStorage(storage, prefix) {
26
27
  return;
27
28
  }
28
29
  const storageKey = resolveKey(id);
29
- const data = await storage.getItemRaw(storageKey, opts);
30
- return data;
30
+ let data = await storage.getItemRaw(storageKey, opts);
31
+ if (!data) {
32
+ return;
33
+ }
34
+ if (data instanceof Blob) {
35
+ data = await data.arrayBuffer();
36
+ }
37
+ try {
38
+ return Buffer.from(data);
39
+ } catch (error) {
40
+ throw createError({
41
+ statusCode: 500,
42
+ statusText: `IPX_STORAGE_ERROR`,
43
+ message: `Failed to parse storage data to Buffer:
44
+ ${error.message}`,
45
+ cause: error
46
+ });
47
+ }
31
48
  }
32
49
  };
33
50
  }
@@ -325,7 +325,9 @@ function createIPX(userOptions) {
325
325
  const options = defu.defu(userOptions, {
326
326
  alias: getEnv("IPX_ALIAS") || {},
327
327
  maxAge: getEnv("IPX_MAX_AGE") ?? 60,
328
- sharpOptions: {}
328
+ sharpOptions: {
329
+ jpegProgressive: true
330
+ }
329
331
  });
330
332
  options.alias = Object.fromEntries(
331
333
  Object.entries(options.alias || {}).map((e) => [
@@ -449,8 +451,7 @@ function createIPX(userOptions) {
449
451
  }
450
452
  if (SUPPORTED_FORMATS.has(format || "")) {
451
453
  sharp = sharp.toFormat(format, {
452
- quality: handlerContext.quality,
453
- progressive: format === "jpeg"
454
+ quality: handlerContext.quality
454
455
  });
455
456
  }
456
457
  const processedImage = await sharp.toBuffer();
@@ -518,13 +519,6 @@ function createIPXH3Handler(ipx) {
518
519
  "content-security-policy",
519
520
  "default-src 'none'"
520
521
  );
521
- if (typeof sourceMeta.maxAge === "number") {
522
- sendResponseHeaderIfNotSet(
523
- event,
524
- "cache-control",
525
- `max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
526
- );
527
- }
528
522
  if (sourceMeta.mtime) {
529
523
  sendResponseHeaderIfNotSet(
530
524
  event,
@@ -538,6 +532,13 @@ function createIPXH3Handler(ipx) {
538
532
  }
539
533
  }
540
534
  const { data, format } = await img.process();
535
+ if (typeof sourceMeta.maxAge === "number") {
536
+ sendResponseHeaderIfNotSet(
537
+ event,
538
+ "cache-control",
539
+ `max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
540
+ );
541
+ }
541
542
  const etag = getEtag__default(data);
542
543
  sendResponseHeaderIfNotSet(event, "etag", etag);
543
544
  if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
@@ -606,7 +607,7 @@ const HTTP_RE = /^https?:\/\//;
606
607
  function ipxHttpStorage(_options = {}) {
607
608
  const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
608
609
  let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
609
- const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE");
610
+ const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
610
611
  const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
611
612
  if (typeof _domains === "string") {
612
613
  _domains = _domains.split(",").map((s) => s.trim());
@@ -683,56 +684,66 @@ function ipxHttpStorage(_options = {}) {
683
684
  }
684
685
 
685
686
  function ipxFSStorage(_options = {}) {
686
- const rootDir = pathe.resolve(_options.dir || getEnv("IPX_FS_DIR") || ".");
687
+ const dirs = resolveDirs(_options.dir);
687
688
  const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
688
- const _resolve = (id) => {
689
- const resolved = pathe.join(rootDir, id);
690
- if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
689
+ const _getFS = cachedPromise(
690
+ () => import('node:fs/promises').catch(() => {
691
691
  throw h3.createError({
692
- statusCode: 403,
693
- statusText: `IPX_FORBIDDEN_PATH`,
694
- message: `Forbidden path: ${id}`
692
+ statusCode: 500,
693
+ statusText: `IPX_FILESYSTEM_ERROR`,
694
+ message: `Failed to resolve filesystem module`
695
695
  });
696
- }
697
- return resolved;
698
- };
699
- const _getFS = cachedPromise(() => import('node:fs/promises'));
700
- return {
701
- name: "ipx:node-fs",
702
- async getMeta(id) {
703
- const fsPath = _resolve(id);
704
- let stats;
705
- try {
706
- const fs = await _getFS();
707
- stats = await fs.stat(fsPath);
708
- } catch (error) {
709
- throw error.code === "ENOENT" ? h3.createError({
710
- statusCode: 404,
711
- statusText: `IPX_FILE_NOT_FOUND`,
712
- message: `File not found: ${id}`
713
- }) : h3.createError({
696
+ })
697
+ );
698
+ const resolveFile = async (id) => {
699
+ const fs = await _getFS();
700
+ for (const dir of dirs) {
701
+ const filePath = pathe.join(dir, id);
702
+ if (!isValidPath(filePath) || !filePath.startsWith(dir)) {
703
+ throw h3.createError({
714
704
  statusCode: 403,
715
- statusText: `IPX_FORBIDDEN_FILE`,
716
- message: `File access forbidden: (${error.code}) ${id}`
705
+ statusText: `IPX_FORBIDDEN_PATH`,
706
+ message: `Forbidden path: ${id}`
717
707
  });
718
708
  }
719
- if (!stats.isFile()) {
709
+ try {
710
+ const stats = await fs.stat(filePath);
711
+ if (!stats.isFile()) {
712
+ continue;
713
+ }
714
+ return {
715
+ stats,
716
+ read: () => fs.readFile(filePath)
717
+ };
718
+ } catch (error) {
719
+ if (error.code === "ENOENT") {
720
+ continue;
721
+ }
720
722
  throw h3.createError({
721
- statusCode: 400,
722
- statusText: `IPX_INVALID_FILE`,
723
- message: `Path should be a file: ${id}`
723
+ statusCode: 403,
724
+ statusText: `IPX_FORBIDDEN_FILE`,
725
+ message: `Cannot access file: ${id}`
724
726
  });
725
727
  }
728
+ }
729
+ throw h3.createError({
730
+ statusCode: 404,
731
+ statusText: `IPX_FILE_NOT_FOUND`,
732
+ message: `File not found: ${id}`
733
+ });
734
+ };
735
+ return {
736
+ name: "ipx:node-fs",
737
+ async getMeta(id) {
738
+ const { stats } = await resolveFile(id);
726
739
  return {
727
740
  mtime: stats.mtime,
728
741
  maxAge
729
742
  };
730
743
  },
731
744
  async getData(id) {
732
- const fsPath = _resolve(id);
733
- const fs = await _getFS();
734
- const contents = await fs.readFile(fsPath);
735
- return contents;
745
+ const { read } = await resolveFile(id);
746
+ return read();
736
747
  }
737
748
  };
738
749
  }
@@ -746,6 +757,13 @@ function isValidPath(fp) {
746
757
  }
747
758
  return true;
748
759
  }
760
+ function resolveDirs(dirs) {
761
+ if (!dirs || !Array.isArray(dirs)) {
762
+ const dir = pathe.resolve(dirs || getEnv("IPX_FS_DIR") || ".");
763
+ return [dir];
764
+ }
765
+ return dirs.map((dirs2) => pathe.resolve(dirs2));
766
+ }
749
767
 
750
768
  exports.createIPX = createIPX;
751
769
  exports.createIPXH3App = createIPXH3App;
@@ -318,7 +318,9 @@ function createIPX(userOptions) {
318
318
  const options = defu(userOptions, {
319
319
  alias: getEnv("IPX_ALIAS") || {},
320
320
  maxAge: getEnv("IPX_MAX_AGE") ?? 60,
321
- sharpOptions: {}
321
+ sharpOptions: {
322
+ jpegProgressive: true
323
+ }
322
324
  });
323
325
  options.alias = Object.fromEntries(
324
326
  Object.entries(options.alias || {}).map((e) => [
@@ -442,8 +444,7 @@ function createIPX(userOptions) {
442
444
  }
443
445
  if (SUPPORTED_FORMATS.has(format || "")) {
444
446
  sharp = sharp.toFormat(format, {
445
- quality: handlerContext.quality,
446
- progressive: format === "jpeg"
447
+ quality: handlerContext.quality
447
448
  });
448
449
  }
449
450
  const processedImage = await sharp.toBuffer();
@@ -511,13 +512,6 @@ function createIPXH3Handler(ipx) {
511
512
  "content-security-policy",
512
513
  "default-src 'none'"
513
514
  );
514
- if (typeof sourceMeta.maxAge === "number") {
515
- sendResponseHeaderIfNotSet(
516
- event,
517
- "cache-control",
518
- `max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
519
- );
520
- }
521
515
  if (sourceMeta.mtime) {
522
516
  sendResponseHeaderIfNotSet(
523
517
  event,
@@ -531,6 +525,13 @@ function createIPXH3Handler(ipx) {
531
525
  }
532
526
  }
533
527
  const { data, format } = await img.process();
528
+ if (typeof sourceMeta.maxAge === "number") {
529
+ sendResponseHeaderIfNotSet(
530
+ event,
531
+ "cache-control",
532
+ `max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
533
+ );
534
+ }
534
535
  const etag = getEtag(data);
535
536
  sendResponseHeaderIfNotSet(event, "etag", etag);
536
537
  if (etag && getRequestHeader(event, "if-none-match") === etag) {
@@ -599,7 +600,7 @@ const HTTP_RE = /^https?:\/\//;
599
600
  function ipxHttpStorage(_options = {}) {
600
601
  const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
601
602
  let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
602
- const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE");
603
+ const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
603
604
  const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
604
605
  if (typeof _domains === "string") {
605
606
  _domains = _domains.split(",").map((s) => s.trim());
@@ -676,56 +677,66 @@ function ipxHttpStorage(_options = {}) {
676
677
  }
677
678
 
678
679
  function ipxFSStorage(_options = {}) {
679
- const rootDir = resolve(_options.dir || getEnv("IPX_FS_DIR") || ".");
680
+ const dirs = resolveDirs(_options.dir);
680
681
  const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
681
- const _resolve = (id) => {
682
- const resolved = join(rootDir, id);
683
- if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
682
+ const _getFS = cachedPromise(
683
+ () => import('node:fs/promises').catch(() => {
684
684
  throw createError({
685
- statusCode: 403,
686
- statusText: `IPX_FORBIDDEN_PATH`,
687
- message: `Forbidden path: ${id}`
685
+ statusCode: 500,
686
+ statusText: `IPX_FILESYSTEM_ERROR`,
687
+ message: `Failed to resolve filesystem module`
688
688
  });
689
- }
690
- return resolved;
691
- };
692
- const _getFS = cachedPromise(() => import('node:fs/promises'));
693
- return {
694
- name: "ipx:node-fs",
695
- async getMeta(id) {
696
- const fsPath = _resolve(id);
697
- let stats;
698
- try {
699
- const fs = await _getFS();
700
- stats = await fs.stat(fsPath);
701
- } catch (error) {
702
- throw error.code === "ENOENT" ? createError({
703
- statusCode: 404,
704
- statusText: `IPX_FILE_NOT_FOUND`,
705
- message: `File not found: ${id}`
706
- }) : createError({
689
+ })
690
+ );
691
+ const resolveFile = async (id) => {
692
+ const fs = await _getFS();
693
+ for (const dir of dirs) {
694
+ const filePath = join(dir, id);
695
+ if (!isValidPath(filePath) || !filePath.startsWith(dir)) {
696
+ throw createError({
707
697
  statusCode: 403,
708
- statusText: `IPX_FORBIDDEN_FILE`,
709
- message: `File access forbidden: (${error.code}) ${id}`
698
+ statusText: `IPX_FORBIDDEN_PATH`,
699
+ message: `Forbidden path: ${id}`
710
700
  });
711
701
  }
712
- if (!stats.isFile()) {
702
+ try {
703
+ const stats = await fs.stat(filePath);
704
+ if (!stats.isFile()) {
705
+ continue;
706
+ }
707
+ return {
708
+ stats,
709
+ read: () => fs.readFile(filePath)
710
+ };
711
+ } catch (error) {
712
+ if (error.code === "ENOENT") {
713
+ continue;
714
+ }
713
715
  throw createError({
714
- statusCode: 400,
715
- statusText: `IPX_INVALID_FILE`,
716
- message: `Path should be a file: ${id}`
716
+ statusCode: 403,
717
+ statusText: `IPX_FORBIDDEN_FILE`,
718
+ message: `Cannot access file: ${id}`
717
719
  });
718
720
  }
721
+ }
722
+ throw createError({
723
+ statusCode: 404,
724
+ statusText: `IPX_FILE_NOT_FOUND`,
725
+ message: `File not found: ${id}`
726
+ });
727
+ };
728
+ return {
729
+ name: "ipx:node-fs",
730
+ async getMeta(id) {
731
+ const { stats } = await resolveFile(id);
719
732
  return {
720
733
  mtime: stats.mtime,
721
734
  maxAge
722
735
  };
723
736
  },
724
737
  async getData(id) {
725
- const fsPath = _resolve(id);
726
- const fs = await _getFS();
727
- const contents = await fs.readFile(fsPath);
728
- return contents;
738
+ const { read } = await resolveFile(id);
739
+ return read();
729
740
  }
730
741
  };
731
742
  }
@@ -739,5 +750,12 @@ function isValidPath(fp) {
739
750
  }
740
751
  return true;
741
752
  }
753
+ function resolveDirs(dirs) {
754
+ if (!dirs || !Array.isArray(dirs)) {
755
+ const dir = resolve(dirs || getEnv("IPX_FS_DIR") || ".");
756
+ return [dir];
757
+ }
758
+ return dirs.map((dirs2) => resolve(dirs2));
759
+ }
742
760
 
743
761
  export { createIPXH3Handler as a, createIPXH3App as b, createIPX as c, createIPXWebServer as d, createIPXNodeServer as e, createIPXPlainServer as f, ipxFSStorage as g, ipxHttpStorage as i };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipx",
3
- "version": "2.0.2",
3
+ "version": "3.0.0",
4
4
  "repository": "unjs/ipx",
5
5
  "description": "High performance, secure and easy-to-use image optimizer.",
6
6
  "license": "MIT",
@@ -32,41 +32,41 @@
32
32
  "lint:fix": "eslint --ext .ts . --fix && prettier -w src test",
33
33
  "prepack": "pnpm build",
34
34
  "release": "pnpm test && changelogen --release --push && npm publish",
35
- "prerelease": "pnpm test && pnpm build && changelogen --release --prerelease --push --publish --publishTag next-2",
35
+ "prerelease": "pnpm test && pnpm build && changelogen --release --prerelease --push --publish --publishTag latest",
36
36
  "start": "node bin/ipx.js",
37
37
  "test": "pnpm lint && vitest run --coverage"
38
38
  },
39
39
  "dependencies": {
40
40
  "@fastify/accept-negotiator": "^1.1.0",
41
- "citty": "^0.1.4",
41
+ "citty": "^0.1.5",
42
42
  "consola": "^3.2.3",
43
- "defu": "^6.1.3",
43
+ "defu": "^6.1.4",
44
44
  "destr": "^2.0.2",
45
45
  "etag": "^1.8.1",
46
- "h3": "^1.8.2",
46
+ "h3": "^1.10.0",
47
47
  "image-meta": "^0.2.0",
48
- "listhen": "^1.5.5",
48
+ "listhen": "^1.5.6",
49
49
  "ofetch": "^1.3.3",
50
- "pathe": "^1.1.1",
51
- "sharp": "^0.32.6",
52
- "svgo": "^3.0.3",
53
- "ufo": "^1.3.1",
54
- "unstorage": "^1.9.0",
50
+ "pathe": "^1.1.2",
51
+ "sharp": "^0.33.1",
52
+ "svgo": "^3.2.0",
53
+ "ufo": "^1.3.2",
54
+ "unstorage": "^1.10.1",
55
55
  "xss": "^1.0.14"
56
56
  },
57
57
  "devDependencies": {
58
- "@types/etag": "^1.8.2",
59
- "@types/is-valid-path": "^0.1.1",
60
- "@vitest/coverage-v8": "^0.34.6",
58
+ "@types/etag": "^1.8.3",
59
+ "@types/is-valid-path": "^0.1.2",
60
+ "@vitest/coverage-v8": "^1.1.3",
61
61
  "changelogen": "^0.5.5",
62
- "eslint": "^8.53.0",
62
+ "eslint": "^8.56.0",
63
63
  "eslint-config-unjs": "^0.2.1",
64
64
  "jiti": "^1.21.0",
65
- "prettier": "^3.0.3",
65
+ "prettier": "^3.1.1",
66
66
  "serve-handler": "^6.1.5",
67
- "typescript": "^5.2.2",
67
+ "typescript": "^5.3.3",
68
68
  "unbuild": "^2.0.0",
69
- "vitest": "^0.34.6"
69
+ "vitest": "^1.1.3"
70
70
  },
71
- "packageManager": "pnpm@8.10.2"
71
+ "packageManager": "pnpm@8.10.5"
72
72
  }