ipx 0.9.4 → 0.9.8

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/LICENSE CHANGED
File without changes
@@ -5,7 +5,6 @@ const imageMeta = require('image-meta');
5
5
  const ufo = require('ufo');
6
6
  const fs = require('fs');
7
7
  const pathe = require('pathe');
8
- const isValidPath = require('is-valid-path');
9
8
  const http = require('http');
10
9
  const https = require('https');
11
10
  const ohmyfetch = require('ohmyfetch');
@@ -16,7 +15,6 @@ const xss = require('xss');
16
15
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
17
16
 
18
17
  const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
19
- const isValidPath__default = /*#__PURE__*/_interopDefaultLegacy(isValidPath);
20
18
  const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
21
19
  const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
22
20
  const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
@@ -74,9 +72,9 @@ function cachedPromise(fn) {
74
72
  }
75
73
  class IPXError extends Error {
76
74
  }
77
- function createError(message, statusCode) {
78
- const err = new IPXError(message);
79
- err.statusMessage = "IPX: " + message;
75
+ function createError(statusMessage, statusCode, trace) {
76
+ const err = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
77
+ err.statusMessage = "IPX: " + statusMessage;
80
78
  err.statusCode = statusCode;
81
79
  return err;
82
80
  }
@@ -85,53 +83,64 @@ const createFilesystemSource = (options) => {
85
83
  const rootDir = pathe.resolve(options.dir);
86
84
  return async (id) => {
87
85
  const fsPath = pathe.resolve(pathe.join(rootDir, id));
88
- if (!isValidPath__default(id) || id.includes("..") || !fsPath.startsWith(rootDir)) {
89
- throw createError("Forbidden path:" + id, 403);
86
+ if (!isValidPath(fsPath) || !fsPath.startsWith(rootDir)) {
87
+ throw createError("Forbidden path", 403, id);
90
88
  }
91
89
  let stats;
92
90
  try {
93
91
  stats = await fs.promises.stat(fsPath);
94
92
  } catch (err) {
95
93
  if (err.code === "ENOENT") {
96
- throw createError("File not found: " + fsPath, 404);
94
+ throw createError("File not found", 404, fsPath);
97
95
  } else {
98
- throw createError("File access error for " + fsPath + ":" + err.code, 403);
96
+ throw createError("File access error " + err.code, 403, fsPath);
99
97
  }
100
98
  }
101
99
  if (!stats.isFile()) {
102
- throw createError("Path should be a file: " + fsPath, 400);
100
+ throw createError("Path should be a file", 400, fsPath);
103
101
  }
104
102
  return {
105
103
  mtime: stats.mtime,
106
- maxAge: options.maxAge || 300,
104
+ maxAge: options.maxAge,
107
105
  getData: cachedPromise(() => fs.promises.readFile(fsPath))
108
106
  };
109
107
  };
110
108
  };
109
+ const isWindows = process.platform === "win32";
110
+ function isValidPath(fp) {
111
+ if (isWindows) {
112
+ fp = fp.slice(pathe.parse(fp).root.length);
113
+ }
114
+ if (/[<>:"|?*]/.test(fp)) {
115
+ return false;
116
+ }
117
+ return true;
118
+ }
111
119
 
112
120
  const createHTTPSource = (options) => {
113
121
  const httpsAgent = new https__default.Agent({ keepAlive: true });
114
122
  const httpAgent = new http__default.Agent({ keepAlive: true });
115
- let domains = options.domains || [];
116
- if (typeof domains === "string") {
117
- domains = domains.split(",").map((s) => s.trim());
123
+ let _domains = options.domains || [];
124
+ if (typeof _domains === "string") {
125
+ _domains = _domains.split(",").map((s) => s.trim());
118
126
  }
119
- const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
127
+ const domains = _domains.map((d) => new URL(d).hostname || new URL("http://" + d).hostname).filter(Boolean);
120
128
  return async (id, reqOptions) => {
121
- const url = new URL(id);
122
- if (!url.hostname) {
123
- throw createError("Hostname is missing: " + id, 403);
129
+ const hostname = new URL(id).hostname;
130
+ if (!hostname) {
131
+ throw createError("Hostname is missing", 403, id);
124
132
  }
125
- if (!reqOptions?.bypassDomain && !hosts.find((host) => url.hostname === host)) {
126
- throw createError("Forbidden host: " + url.hostname, 403);
133
+ if (!reqOptions?.bypassDomain && !domains.find((domain) => hostname === domain)) {
134
+ throw createError("Forbidden host", 403, hostname);
127
135
  }
128
136
  const response = await ohmyfetch.fetch(id, {
129
- agent: id.startsWith("https") ? httpsAgent : httpAgent
137
+ agent: id.startsWith("https") ? httpsAgent : httpAgent,
138
+ ...options.fetchOptions
130
139
  });
131
140
  if (!response.ok) {
132
- throw createError(response.statusText || "fetch error", response.status || 500);
141
+ throw createError("Fetch error", response.status || 500, response.statusText);
133
142
  }
134
- let maxAge = options.maxAge || 300;
143
+ let maxAge = options.maxAge;
135
144
  const _cacheControl = response.headers.get("cache-control");
136
145
  if (_cacheControl) {
137
146
  const m = _cacheControl.match(/max-age=(\d+)/);
@@ -147,7 +156,7 @@ const createHTTPSource = (options) => {
147
156
  return {
148
157
  mtime,
149
158
  maxAge,
150
- getData: cachedPromise(() => response.buffer())
159
+ getData: cachedPromise(() => response.arrayBuffer().then((ab) => Buffer.from(ab)))
151
160
  };
152
161
  };
153
162
  };
@@ -384,12 +393,14 @@ const h = height;
384
393
  const s = resize;
385
394
  const pos = position;
386
395
 
387
- const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff"];
396
+ const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
388
397
  function createIPX(userOptions) {
389
398
  const defaults = {
390
399
  dir: getEnv("IPX_DIR", "."),
391
400
  domains: getEnv("IPX_DOMAINS", []),
392
401
  alias: getEnv("IPX_ALIAS", {}),
402
+ fetchOptions: getEnv("IPX_FETCH_OPTIONS", {}),
403
+ maxAge: getEnv("IPX_MAX_AGE", 300),
393
404
  sharp: {}
394
405
  };
395
406
  const options = defu__default(userOptions, defaults);
@@ -399,12 +410,15 @@ function createIPX(userOptions) {
399
410
  };
400
411
  if (options.dir) {
401
412
  ctx.sources.filesystem = createFilesystemSource({
402
- dir: options.dir
413
+ dir: options.dir,
414
+ maxAge: options.maxAge
403
415
  });
404
416
  }
405
417
  if (options.domains) {
406
418
  ctx.sources.http = createHTTPSource({
407
- domains: options.domains
419
+ domains: options.domains,
420
+ fetchOptions: options.fetchOptions,
421
+ maxAge: options.maxAge
408
422
  });
409
423
  }
410
424
  return function ipx(id, modifiers = {}, reqOptions = {}) {
@@ -420,7 +434,7 @@ function createIPX(userOptions) {
420
434
  const getSrc = cachedPromise(() => {
421
435
  const source = ufo.hasProtocol(id) ? "http" : "filesystem";
422
436
  if (!ctx.sources[source]) {
423
- throw createError("Unknown source: " + source, 400);
437
+ throw createError("Unknown source", 400, source);
424
438
  }
425
439
  return ctx.sources[source](id, reqOptions);
426
440
  });
@@ -440,10 +454,7 @@ function createIPX(userOptions) {
440
454
  meta
441
455
  };
442
456
  }
443
- const animated = modifiers.animated !== void 0 || modifiers.a !== void 0;
444
- if (animated) {
445
- format = "webp";
446
- }
457
+ const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
447
458
  const Sharp = await import('sharp').then((r) => r.default || r);
448
459
  let sharp = Sharp(data, { animated });
449
460
  Object.assign(sharp.options, options.sharp);
@@ -476,6 +487,8 @@ function createIPX(userOptions) {
476
487
  };
477
488
  }
478
489
 
490
+ const MODIFIER_SEP = /[,&]/g;
491
+ const MODIFIER_VAL_SEP = /[_=:]/g;
479
492
  async function _handleRequest(req, ipx) {
480
493
  const res = {
481
494
  statusCode: 200,
@@ -483,19 +496,19 @@ async function _handleRequest(req, ipx) {
483
496
  headers: {},
484
497
  body: ""
485
498
  };
486
- const [modifiersStr = "", ...idSegments] = req.url.substr(1).split("/");
487
- const id = ufo.decode(idSegments.join("/"));
499
+ const [modifiersStr = "", ...idSegments] = req.url.substring(1).split("/");
500
+ const id = safeString(ufo.decode(idSegments.join("/")));
488
501
  if (!modifiersStr) {
489
- throw createError("Modifiers is missing in path: " + req.url, 400);
502
+ throw createError("Modifiers are missing", 400, req.url);
490
503
  }
491
504
  if (!id || id === "/") {
492
- throw createError("Resource id is missing: " + req.url, 400);
505
+ throw createError("Resource id is missing", 400, req.url);
493
506
  }
494
507
  const modifiers = /* @__PURE__ */ Object.create(null);
495
508
  if (modifiersStr !== "_") {
496
- for (const p of modifiersStr.split(",")) {
497
- const [key, value = ""] = p.split("_");
498
- modifiers[key] = ufo.decode(value);
509
+ for (const p of modifiersStr.split(MODIFIER_SEP)) {
510
+ const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
511
+ modifiers[safeString(key)] = safeString(ufo.decode(value));
499
512
  }
500
513
  }
501
514
  const img = ipx(id, modifiers, req.options);
@@ -509,7 +522,7 @@ async function _handleRequest(req, ipx) {
509
522
  }
510
523
  res.headers["Last-Modified"] = +src.mtime + "";
511
524
  }
512
- if (src.maxAge !== void 0) {
525
+ if (typeof src.maxAge === "number") {
513
526
  res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
514
527
  }
515
528
  const { data, format } = await img.data();
@@ -523,21 +536,21 @@ async function _handleRequest(req, ipx) {
523
536
  res.headers["Content-Type"] = `image/${format}`;
524
537
  }
525
538
  res.body = data;
526
- return res;
539
+ return sanetizeReponse(res);
527
540
  }
528
541
  function handleRequest(req, ipx) {
529
542
  return _handleRequest(req, ipx).catch((err) => {
530
543
  const statusCode = parseInt(err.statusCode) || 500;
531
- const statusMessage = err.statusMessage ? xss__default(err.statusMessage) : `IPX Error (${statusCode})`;
544
+ const statusMessage = err.statusMessage ? err.statusMessage : `IPX Error (${statusCode})`;
532
545
  if (process.env.NODE_ENV !== "production" && statusCode === 500) {
533
546
  console.error(err);
534
547
  }
535
- return {
548
+ return sanetizeReponse({
536
549
  statusCode,
537
550
  statusMessage,
538
- body: statusMessage,
551
+ body: "IPX Error: " + err,
539
552
  headers: {}
540
- };
553
+ });
541
554
  });
542
555
  }
543
556
  function createIPXMiddleware(ipx) {
@@ -552,6 +565,24 @@ function createIPXMiddleware(ipx) {
552
565
  });
553
566
  };
554
567
  }
568
+ function sanetizeReponse(res) {
569
+ return {
570
+ statusCode: res.statusCode || 200,
571
+ statusMessage: res.statusMessage ? safeString(res.statusMessage) : "OK",
572
+ headers: safeStringObject(res.headers || {}),
573
+ body: typeof res.body === "string" ? xss__default(safeString(res.body)) : res.body || ""
574
+ };
575
+ }
576
+ function safeString(input) {
577
+ return JSON.stringify(input).replace(/^"|"$/g, "");
578
+ }
579
+ function safeStringObject(input) {
580
+ const dst = {};
581
+ for (const key in input) {
582
+ dst[key] = safeString(input[key]);
583
+ }
584
+ return dst;
585
+ }
555
586
 
556
587
  exports.createIPX = createIPX;
557
588
  exports.createIPXMiddleware = createIPXMiddleware;
@@ -1,9 +1,8 @@
1
1
  import defu from 'defu';
2
2
  import { imageMeta } from 'image-meta';
3
- import { parseURL, withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
3
+ import { withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
4
4
  import { promises } from 'fs';
5
- import { resolve, join } from 'pathe';
6
- import isValidPath from 'is-valid-path';
5
+ import { resolve, join, parse } from 'pathe';
7
6
  import http from 'http';
8
7
  import https from 'https';
9
8
  import { fetch } from 'ohmyfetch';
@@ -62,9 +61,9 @@ function cachedPromise(fn) {
62
61
  }
63
62
  class IPXError extends Error {
64
63
  }
65
- function createError(message, statusCode) {
66
- const err = new IPXError(message);
67
- err.statusMessage = "IPX: " + message;
64
+ function createError(statusMessage, statusCode, trace) {
65
+ const err = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
66
+ err.statusMessage = "IPX: " + statusMessage;
68
67
  err.statusCode = statusCode;
69
68
  return err;
70
69
  }
@@ -73,53 +72,64 @@ const createFilesystemSource = (options) => {
73
72
  const rootDir = resolve(options.dir);
74
73
  return async (id) => {
75
74
  const fsPath = resolve(join(rootDir, id));
76
- if (!isValidPath(id) || id.includes("..") || !fsPath.startsWith(rootDir)) {
77
- throw createError("Forbidden path:" + id, 403);
75
+ if (!isValidPath(fsPath) || !fsPath.startsWith(rootDir)) {
76
+ throw createError("Forbidden path", 403, id);
78
77
  }
79
78
  let stats;
80
79
  try {
81
80
  stats = await promises.stat(fsPath);
82
81
  } catch (err) {
83
82
  if (err.code === "ENOENT") {
84
- throw createError("File not found: " + fsPath, 404);
83
+ throw createError("File not found", 404, fsPath);
85
84
  } else {
86
- throw createError("File access error for " + fsPath + ":" + err.code, 403);
85
+ throw createError("File access error " + err.code, 403, fsPath);
87
86
  }
88
87
  }
89
88
  if (!stats.isFile()) {
90
- throw createError("Path should be a file: " + fsPath, 400);
89
+ throw createError("Path should be a file", 400, fsPath);
91
90
  }
92
91
  return {
93
92
  mtime: stats.mtime,
94
- maxAge: options.maxAge || 300,
93
+ maxAge: options.maxAge,
95
94
  getData: cachedPromise(() => promises.readFile(fsPath))
96
95
  };
97
96
  };
98
97
  };
98
+ const isWindows = process.platform === "win32";
99
+ function isValidPath(fp) {
100
+ if (isWindows) {
101
+ fp = fp.slice(parse(fp).root.length);
102
+ }
103
+ if (/[<>:"|?*]/.test(fp)) {
104
+ return false;
105
+ }
106
+ return true;
107
+ }
99
108
 
100
109
  const createHTTPSource = (options) => {
101
110
  const httpsAgent = new https.Agent({ keepAlive: true });
102
111
  const httpAgent = new http.Agent({ keepAlive: true });
103
- let domains = options.domains || [];
104
- if (typeof domains === "string") {
105
- domains = domains.split(",").map((s) => s.trim());
112
+ let _domains = options.domains || [];
113
+ if (typeof _domains === "string") {
114
+ _domains = _domains.split(",").map((s) => s.trim());
106
115
  }
107
- const hosts = domains.map((domain) => parseURL(domain, "https://").host);
116
+ const domains = _domains.map((d) => new URL(d).hostname || new URL("http://" + d).hostname).filter(Boolean);
108
117
  return async (id, reqOptions) => {
109
- const url = new URL(id);
110
- if (!url.hostname) {
111
- throw createError("Hostname is missing: " + id, 403);
118
+ const hostname = new URL(id).hostname;
119
+ if (!hostname) {
120
+ throw createError("Hostname is missing", 403, id);
112
121
  }
113
- if (!reqOptions?.bypassDomain && !hosts.find((host) => url.hostname === host)) {
114
- throw createError("Forbidden host: " + url.hostname, 403);
122
+ if (!reqOptions?.bypassDomain && !domains.find((domain) => hostname === domain)) {
123
+ throw createError("Forbidden host", 403, hostname);
115
124
  }
116
125
  const response = await fetch(id, {
117
- agent: id.startsWith("https") ? httpsAgent : httpAgent
126
+ agent: id.startsWith("https") ? httpsAgent : httpAgent,
127
+ ...options.fetchOptions
118
128
  });
119
129
  if (!response.ok) {
120
- throw createError(response.statusText || "fetch error", response.status || 500);
130
+ throw createError("Fetch error", response.status || 500, response.statusText);
121
131
  }
122
- let maxAge = options.maxAge || 300;
132
+ let maxAge = options.maxAge;
123
133
  const _cacheControl = response.headers.get("cache-control");
124
134
  if (_cacheControl) {
125
135
  const m = _cacheControl.match(/max-age=(\d+)/);
@@ -135,7 +145,7 @@ const createHTTPSource = (options) => {
135
145
  return {
136
146
  mtime,
137
147
  maxAge,
138
- getData: cachedPromise(() => response.buffer())
148
+ getData: cachedPromise(() => response.arrayBuffer().then((ab) => Buffer.from(ab)))
139
149
  };
140
150
  };
141
151
  };
@@ -372,12 +382,14 @@ const h = height;
372
382
  const s = resize;
373
383
  const pos = position;
374
384
 
375
- const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff"];
385
+ const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
376
386
  function createIPX(userOptions) {
377
387
  const defaults = {
378
388
  dir: getEnv("IPX_DIR", "."),
379
389
  domains: getEnv("IPX_DOMAINS", []),
380
390
  alias: getEnv("IPX_ALIAS", {}),
391
+ fetchOptions: getEnv("IPX_FETCH_OPTIONS", {}),
392
+ maxAge: getEnv("IPX_MAX_AGE", 300),
381
393
  sharp: {}
382
394
  };
383
395
  const options = defu(userOptions, defaults);
@@ -387,12 +399,15 @@ function createIPX(userOptions) {
387
399
  };
388
400
  if (options.dir) {
389
401
  ctx.sources.filesystem = createFilesystemSource({
390
- dir: options.dir
402
+ dir: options.dir,
403
+ maxAge: options.maxAge
391
404
  });
392
405
  }
393
406
  if (options.domains) {
394
407
  ctx.sources.http = createHTTPSource({
395
- domains: options.domains
408
+ domains: options.domains,
409
+ fetchOptions: options.fetchOptions,
410
+ maxAge: options.maxAge
396
411
  });
397
412
  }
398
413
  return function ipx(id, modifiers = {}, reqOptions = {}) {
@@ -408,7 +423,7 @@ function createIPX(userOptions) {
408
423
  const getSrc = cachedPromise(() => {
409
424
  const source = hasProtocol(id) ? "http" : "filesystem";
410
425
  if (!ctx.sources[source]) {
411
- throw createError("Unknown source: " + source, 400);
426
+ throw createError("Unknown source", 400, source);
412
427
  }
413
428
  return ctx.sources[source](id, reqOptions);
414
429
  });
@@ -428,10 +443,7 @@ function createIPX(userOptions) {
428
443
  meta
429
444
  };
430
445
  }
431
- const animated = modifiers.animated !== void 0 || modifiers.a !== void 0;
432
- if (animated) {
433
- format = "webp";
434
- }
446
+ const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
435
447
  const Sharp = await import('sharp').then((r) => r.default || r);
436
448
  let sharp = Sharp(data, { animated });
437
449
  Object.assign(sharp.options, options.sharp);
@@ -464,6 +476,8 @@ function createIPX(userOptions) {
464
476
  };
465
477
  }
466
478
 
479
+ const MODIFIER_SEP = /[,&]/g;
480
+ const MODIFIER_VAL_SEP = /[_=:]/g;
467
481
  async function _handleRequest(req, ipx) {
468
482
  const res = {
469
483
  statusCode: 200,
@@ -471,19 +485,19 @@ async function _handleRequest(req, ipx) {
471
485
  headers: {},
472
486
  body: ""
473
487
  };
474
- const [modifiersStr = "", ...idSegments] = req.url.substr(1).split("/");
475
- const id = decode(idSegments.join("/"));
488
+ const [modifiersStr = "", ...idSegments] = req.url.substring(1).split("/");
489
+ const id = safeString(decode(idSegments.join("/")));
476
490
  if (!modifiersStr) {
477
- throw createError("Modifiers is missing in path: " + req.url, 400);
491
+ throw createError("Modifiers are missing", 400, req.url);
478
492
  }
479
493
  if (!id || id === "/") {
480
- throw createError("Resource id is missing: " + req.url, 400);
494
+ throw createError("Resource id is missing", 400, req.url);
481
495
  }
482
496
  const modifiers = /* @__PURE__ */ Object.create(null);
483
497
  if (modifiersStr !== "_") {
484
- for (const p of modifiersStr.split(",")) {
485
- const [key, value = ""] = p.split("_");
486
- modifiers[key] = decode(value);
498
+ for (const p of modifiersStr.split(MODIFIER_SEP)) {
499
+ const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
500
+ modifiers[safeString(key)] = safeString(decode(value));
487
501
  }
488
502
  }
489
503
  const img = ipx(id, modifiers, req.options);
@@ -497,7 +511,7 @@ async function _handleRequest(req, ipx) {
497
511
  }
498
512
  res.headers["Last-Modified"] = +src.mtime + "";
499
513
  }
500
- if (src.maxAge !== void 0) {
514
+ if (typeof src.maxAge === "number") {
501
515
  res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
502
516
  }
503
517
  const { data, format } = await img.data();
@@ -511,21 +525,21 @@ async function _handleRequest(req, ipx) {
511
525
  res.headers["Content-Type"] = `image/${format}`;
512
526
  }
513
527
  res.body = data;
514
- return res;
528
+ return sanetizeReponse(res);
515
529
  }
516
530
  function handleRequest(req, ipx) {
517
531
  return _handleRequest(req, ipx).catch((err) => {
518
532
  const statusCode = parseInt(err.statusCode) || 500;
519
- const statusMessage = err.statusMessage ? xss(err.statusMessage) : `IPX Error (${statusCode})`;
533
+ const statusMessage = err.statusMessage ? err.statusMessage : `IPX Error (${statusCode})`;
520
534
  if (process.env.NODE_ENV !== "production" && statusCode === 500) {
521
535
  console.error(err);
522
536
  }
523
- return {
537
+ return sanetizeReponse({
524
538
  statusCode,
525
539
  statusMessage,
526
- body: statusMessage,
540
+ body: "IPX Error: " + err,
527
541
  headers: {}
528
- };
542
+ });
529
543
  });
530
544
  }
531
545
  function createIPXMiddleware(ipx) {
@@ -540,5 +554,23 @@ function createIPXMiddleware(ipx) {
540
554
  });
541
555
  };
542
556
  }
557
+ function sanetizeReponse(res) {
558
+ return {
559
+ statusCode: res.statusCode || 200,
560
+ statusMessage: res.statusMessage ? safeString(res.statusMessage) : "OK",
561
+ headers: safeStringObject(res.headers || {}),
562
+ body: typeof res.body === "string" ? xss(safeString(res.body)) : res.body || ""
563
+ };
564
+ }
565
+ function safeString(input) {
566
+ return JSON.stringify(input).replace(/^"|"$/g, "");
567
+ }
568
+ function safeStringObject(input) {
569
+ const dst = {};
570
+ for (const key in input) {
571
+ dst[key] = safeString(input[key]);
572
+ }
573
+ return dst;
574
+ }
543
575
 
544
576
  export { createIPXMiddleware as a, createIPX as c, handleRequest as h };
package/dist/cli.cjs CHANGED
@@ -8,7 +8,6 @@ require('image-meta');
8
8
  require('ufo');
9
9
  require('fs');
10
10
  require('pathe');
11
- require('is-valid-path');
12
11
  require('http');
13
12
  require('https');
14
13
  require('ohmyfetch');
package/dist/cli.mjs CHANGED
@@ -6,7 +6,6 @@ import 'image-meta';
6
6
  import 'ufo';
7
7
  import 'fs';
8
8
  import 'pathe';
9
- import 'is-valid-path';
10
9
  import 'http';
11
10
  import 'https';
12
11
  import 'ohmyfetch';
package/dist/index.cjs CHANGED
@@ -8,7 +8,6 @@ require('image-meta');
8
8
  require('ufo');
9
9
  require('fs');
10
10
  require('pathe');
11
- require('is-valid-path');
12
11
  require('http');
13
12
  require('https');
14
13
  require('ohmyfetch');
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ interface SourceData {
6
6
  getData: () => Promise<Buffer>;
7
7
  }
8
8
  declare type Source = (src: string, reqOptions?: any) => Promise<SourceData>;
9
- declare type SourceFactory = (options?: any) => Source;
9
+ declare type SourceFactory<T = Record<string, any>> = (options: T) => Source;
10
10
 
11
11
  interface ImageMeta {
12
12
  width: number;
@@ -27,8 +27,10 @@ declare type IPX = (id: string, modifiers?: Record<string, string>, reqOptions?:
27
27
  };
28
28
  interface IPXOptions {
29
29
  dir?: false | string;
30
+ maxAge?: number;
30
31
  domains?: false | string[];
31
32
  alias: Record<string, string>;
33
+ fetchOptions: RequestInit;
32
34
  sharp?: {
33
35
  [key: string]: any;
34
36
  };
package/dist/index.mjs CHANGED
@@ -4,7 +4,6 @@ import 'image-meta';
4
4
  import 'ufo';
5
5
  import 'fs';
6
6
  import 'pathe';
7
- import 'is-valid-path';
8
7
  import 'http';
9
8
  import 'https';
10
9
  import 'ohmyfetch';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipx",
3
- "version": "0.9.4",
3
+ "version": "0.9.8",
4
4
  "repository": "unjs/ipx",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -17,43 +17,42 @@
17
17
  "dist",
18
18
  "bin"
19
19
  ],
20
- "scripts": {
21
- "build": "unbuild",
22
- "dev": "nodemon",
23
- "lint": "eslint --ext .ts .",
24
- "prepack": "yarn build",
25
- "release": "yarn test && standard-version && git push --follow-tags && npm publish",
26
- "start": "node bin/ipx.js",
27
- "test": "yarn lint && jest"
28
- },
29
20
  "dependencies": {
30
21
  "consola": "^2.15.3",
31
- "defu": "^5.0.1",
32
- "destr": "^1.1.0",
22
+ "defu": "^6.0.0",
23
+ "destr": "^1.1.1",
33
24
  "etag": "^1.8.1",
34
25
  "image-meta": "^0.1.1",
35
- "is-valid-path": "^0.1.1",
36
- "listhen": "^0.2.6",
37
- "ohmyfetch": "^0.4.15",
38
- "pathe": "^0.2.0",
39
- "sharp": "^0.30.1",
40
- "ufo": "^0.7.10",
41
- "xss": "^1.0.10"
26
+ "listhen": "^0.2.13",
27
+ "ohmyfetch": "^0.4.18",
28
+ "pathe": "^0.3.0",
29
+ "sharp": "^0.30.7",
30
+ "ufo": "^0.8.4",
31
+ "xss": "^1.0.13"
42
32
  },
43
33
  "devDependencies": {
44
34
  "@nuxtjs/eslint-config-typescript": "latest",
45
35
  "@types/etag": "latest",
46
36
  "@types/is-valid-path": "latest",
47
- "@types/jest": "latest",
48
37
  "@types/node-fetch": "latest",
49
38
  "@types/sharp": "latest",
39
+ "c8": "latest",
50
40
  "eslint": "latest",
51
- "jest": "latest",
52
41
  "jiti": "latest",
53
42
  "nodemon": "latest",
43
+ "serve-handler": "^6.1.3",
54
44
  "standard-version": "latest",
55
- "ts-jest": "latest",
56
45
  "typescript": "latest",
57
- "unbuild": "latest"
46
+ "unbuild": "latest",
47
+ "vitest": "latest"
48
+ },
49
+ "packageManager": "pnpm@7.3.0",
50
+ "scripts": {
51
+ "build": "unbuild",
52
+ "dev": "nodemon",
53
+ "lint": "eslint --ext .ts .",
54
+ "release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
55
+ "start": "node bin/ipx.js",
56
+ "test": "pnpm lint && vitest run --coverage"
58
57
  }
59
- }
58
+ }