ipx 0.9.11 → 1.0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017-2021
3
+ Copyright (c) Pooya Parsa <pooya@pi0.io>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/cli.cjs CHANGED
@@ -2,23 +2,19 @@
2
2
 
3
3
  const consola = require('consola');
4
4
  const listhen = require('listhen');
5
- const middleware = require('./shared/ipx.eadce322.cjs');
5
+ const middleware = require('./shared/ipx.dd1c1144.cjs');
6
6
  require('defu');
7
7
  require('image-meta');
8
8
  require('ufo');
9
- require('fs');
9
+ require('node:fs');
10
10
  require('pathe');
11
- require('http');
12
- require('https');
11
+ require('node:http');
12
+ require('node:https');
13
13
  require('ohmyfetch');
14
14
  require('destr');
15
15
  require('etag');
16
16
  require('xss');
17
17
 
18
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
19
-
20
- const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
21
-
22
18
  async function main() {
23
19
  const ipx = middleware.createIPX({});
24
20
  const middleware$1 = middleware.createIPXMiddleware(ipx);
@@ -26,7 +22,7 @@ async function main() {
26
22
  clipboard: false
27
23
  });
28
24
  }
29
- main().catch((err) => {
30
- consola__default.error(err);
25
+ main().catch((error) => {
26
+ consola.error(error);
31
27
  process.exit(1);
32
28
  });
package/dist/cli.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  import consola from 'consola';
2
2
  import { listen } from 'listhen';
3
- import { c as createIPX, a as createIPXMiddleware } from './shared/ipx.72b0591f.mjs';
3
+ import { c as createIPX, a as createIPXMiddleware } from './shared/ipx.d659cb77.mjs';
4
4
  import 'defu';
5
5
  import 'image-meta';
6
6
  import 'ufo';
7
- import 'fs';
7
+ import 'node:fs';
8
8
  import 'pathe';
9
- import 'http';
10
- import 'https';
9
+ import 'node:http';
10
+ import 'node:https';
11
11
  import 'ohmyfetch';
12
12
  import 'destr';
13
13
  import 'etag';
@@ -20,7 +20,7 @@ async function main() {
20
20
  clipboard: false
21
21
  });
22
22
  }
23
- main().catch((err) => {
24
- consola.error(err);
23
+ main().catch((error) => {
24
+ consola.error(error);
25
25
  process.exit(1);
26
26
  });
package/dist/index.cjs CHANGED
@@ -1,15 +1,13 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- const middleware = require('./shared/ipx.eadce322.cjs');
3
+ const middleware = require('./shared/ipx.dd1c1144.cjs');
6
4
  require('defu');
7
5
  require('image-meta');
8
6
  require('ufo');
9
- require('fs');
7
+ require('node:fs');
10
8
  require('pathe');
11
- require('http');
12
- require('https');
9
+ require('node:http');
10
+ require('node:https');
13
11
  require('ohmyfetch');
14
12
  require('destr');
15
13
  require('etag');
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { IncomingMessage, ServerResponse } from 'http';
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
2
2
 
3
3
  interface SourceData {
4
4
  mtime?: Date;
5
5
  maxAge?: number;
6
6
  getData: () => Promise<Buffer>;
7
7
  }
8
- declare type Source = (src: string, reqOptions?: any) => Promise<SourceData>;
9
- declare type SourceFactory<T = Record<string, any>> = (options: T) => Source;
8
+ type Source = (source: string, requestOptions?: any) => Promise<SourceData>;
9
+ type SourceFactory<T = Record<string, any>> = (options: T) => Source;
10
10
 
11
11
  interface ImageMeta {
12
12
  width: number;
@@ -17,7 +17,7 @@ interface ImageMeta {
17
17
  interface IPXCTX {
18
18
  sources: Record<string, Source>;
19
19
  }
20
- declare type IPX = (id: string, modifiers?: Record<string, string>, reqOptions?: any) => {
20
+ type IPX = (id: string, modifiers?: Record<string, string>, requestOptions?: any) => {
21
21
  src: () => Promise<SourceData>;
22
22
  data: () => Promise<{
23
23
  data: Buffer;
@@ -48,7 +48,7 @@ interface IPXHResponse {
48
48
  headers: Record<string, string>;
49
49
  body: any;
50
50
  }
51
- declare function handleRequest(req: IPXHRequest, ipx: IPX): Promise<IPXHResponse>;
52
- declare function createIPXMiddleware(ipx: IPX): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
51
+ declare function handleRequest(request: IPXHRequest, ipx: IPX): Promise<IPXHResponse>;
52
+ declare function createIPXMiddleware(ipx: IPX): (request: IncomingMessage, res: ServerResponse) => Promise<void>;
53
53
 
54
54
  export { IPX, IPXCTX, IPXHRequest, IPXHResponse, IPXOptions, ImageMeta, Source, SourceData, SourceFactory, createIPX, createIPXMiddleware, handleRequest };
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- export { c as createIPX, a as createIPXMiddleware, h as handleRequest } from './shared/ipx.72b0591f.mjs';
1
+ export { c as createIPX, a as createIPXMiddleware, h as handleRequest } from './shared/ipx.d659cb77.mjs';
2
2
  import 'defu';
3
3
  import 'image-meta';
4
4
  import 'ufo';
5
- import 'fs';
5
+ import 'node:fs';
6
6
  import 'pathe';
7
- import 'http';
8
- import 'https';
7
+ import 'node:http';
8
+ import 'node:https';
9
9
  import 'ohmyfetch';
10
10
  import 'destr';
11
11
  import 'etag';
@@ -1,10 +1,10 @@
1
1
  import defu from 'defu';
2
2
  import { imageMeta } from 'image-meta';
3
3
  import { withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
4
- import { promises } from 'fs';
4
+ import { promises } from 'node:fs';
5
5
  import { resolve, join, parse } from 'pathe';
6
- import http from 'http';
7
- import https from 'https';
6
+ import http from 'node:http';
7
+ import https from 'node:https';
8
8
  import { fetch } from 'ohmyfetch';
9
9
  import destr from 'destr';
10
10
  import getEtag from 'etag';
@@ -49,23 +49,23 @@ const Handlers = {
49
49
  function getEnv(name, defaultValue) {
50
50
  return destr(process.env[name]) ?? defaultValue;
51
51
  }
52
- function cachedPromise(fn) {
52
+ function cachedPromise(function_) {
53
53
  let p;
54
- return (...args) => {
54
+ return (...arguments_) => {
55
55
  if (p) {
56
56
  return p;
57
57
  }
58
- p = Promise.resolve(fn(...args));
58
+ p = Promise.resolve(function_(...arguments_));
59
59
  return p;
60
60
  };
61
61
  }
62
62
  class IPXError extends Error {
63
63
  }
64
64
  function createError(statusMessage, statusCode, trace) {
65
- const err = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
66
- err.statusMessage = "IPX: " + statusMessage;
67
- err.statusCode = statusCode;
68
- return err;
65
+ const error = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
66
+ error.statusMessage = "IPX: " + statusMessage;
67
+ error.statusCode = statusCode;
68
+ return error;
69
69
  }
70
70
 
71
71
  const createFilesystemSource = (options) => {
@@ -78,12 +78,9 @@ const createFilesystemSource = (options) => {
78
78
  let stats;
79
79
  try {
80
80
  stats = await promises.stat(fsPath);
81
- } catch (err) {
82
- if (err.code === "ENOENT") {
83
- throw createError("File not found", 404, fsPath);
84
- } else {
85
- throw createError("File access error " + err.code, 403, fsPath);
86
- }
81
+ } catch (error_) {
82
+ const error = error_.code === "ENOENT" ? createError("File not found", 404, fsPath) : createError("File access error " + error_.code, 403, fsPath);
83
+ throw error;
87
84
  }
88
85
  if (!stats.isFile()) {
89
86
  throw createError("Path should be a file", 400, fsPath);
@@ -100,7 +97,7 @@ function isValidPath(fp) {
100
97
  if (isWindows) {
101
98
  fp = fp.slice(parse(fp).root.length);
102
99
  }
103
- if (/[<>:"|?*]/.test(fp)) {
100
+ if (/["*:<>?|]/.test(fp)) {
104
101
  return false;
105
102
  }
106
103
  return true;
@@ -114,18 +111,18 @@ const createHTTPSource = (options) => {
114
111
  if (typeof _domains === "string") {
115
112
  _domains = _domains.split(",").map((s) => s.trim());
116
113
  }
117
- const domains = _domains.map((d) => {
114
+ const domains = new Set(_domains.map((d) => {
118
115
  if (!HTTP_RE.test(d)) {
119
116
  d = "http://" + d;
120
117
  }
121
118
  return new URL(d).hostname;
122
- }).filter(Boolean);
123
- return async (id, reqOptions) => {
119
+ }).filter(Boolean));
120
+ return async (id, requestOptions) => {
124
121
  const hostname = new URL(id).hostname;
125
122
  if (!hostname) {
126
123
  throw createError("Hostname is missing", 403, id);
127
124
  }
128
- if (!reqOptions?.bypassDomain && !domains.find((domain) => hostname === domain)) {
125
+ if (!requestOptions?.bypassDomain && !domains.has(hostname)) {
129
126
  throw createError("Forbidden host", 403, hostname);
130
127
  }
131
128
  const response = await fetch(id, {
@@ -140,7 +137,7 @@ const createHTTPSource = (options) => {
140
137
  if (_cacheControl) {
141
138
  const m = _cacheControl.match(/max-age=(\d+)/);
142
139
  if (m && m[1]) {
143
- maxAge = parseInt(m[1]);
140
+ maxAge = Number.parseInt(m[1]);
144
141
  }
145
142
  }
146
143
  let mtime;
@@ -156,19 +153,19 @@ const createHTTPSource = (options) => {
156
153
  };
157
154
  };
158
155
 
159
- function VArg(arg) {
160
- return destr(arg);
156
+ function VArg(argument) {
157
+ return destr(argument);
161
158
  }
162
- function parseArgs(args, mappers) {
163
- const vargs = args.split("_");
164
- return mappers.map((v, i) => v(vargs[i]));
159
+ function parseArgs(arguments_, mappers) {
160
+ const vargs = arguments_.split("_");
161
+ return mappers.map((v, index) => v(vargs[index]));
165
162
  }
166
163
  function getHandler(key) {
167
164
  return Handlers[key];
168
165
  }
169
- function applyHandler(ctx, pipe, handler, argsStr) {
170
- const args = handler.args ? parseArgs(argsStr, handler.args) : [];
171
- return handler.apply(ctx, pipe, ...args);
166
+ function applyHandler(context, pipe, handler, argumentsString) {
167
+ const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
168
+ return handler.apply(context, pipe, ...arguments_);
172
169
  }
173
170
  function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
174
171
  const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
@@ -205,8 +202,8 @@ const position = {
205
202
  context.position = position2;
206
203
  }
207
204
  };
208
- const HEX_RE = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
209
- const SHORTHEX_RE = /^([a-f\d])([a-f\d])([a-f\d])$/i;
205
+ const HEX_RE = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
206
+ const SHORTHEX_RE = /^([\da-f])([\da-f])([\da-f])$/i;
210
207
  const background = {
211
208
  args: [VArg],
212
209
  order: -1,
@@ -227,19 +224,19 @@ const enlarge = {
227
224
  const width = {
228
225
  args: [VArg],
229
226
  apply: (context, pipe, width2) => {
230
- return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
227
+ return pipe.resize(width2, void 0, { withoutEnlargement: !context.enlarge });
231
228
  }
232
229
  };
233
230
  const height = {
234
231
  args: [VArg],
235
232
  apply: (context, pipe, height2) => {
236
- return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
233
+ return pipe.resize(void 0, height2, { withoutEnlargement: !context.enlarge });
237
234
  }
238
235
  };
239
236
  const resize = {
240
237
  args: [VArg, VArg, VArg],
241
238
  apply: (context, pipe, size) => {
242
- let [width2, height2] = String(size).split("x").map((v) => Number(v));
239
+ let [width2, height2] = String(size).split("x").map(Number);
243
240
  if (!width2) {
244
241
  return;
245
242
  }
@@ -388,7 +385,7 @@ const h = height;
388
385
  const s = resize;
389
386
  const pos = position;
390
387
 
391
- const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
388
+ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif", "tiff", "gif"]);
392
389
  function createIPX(userOptions) {
393
390
  const defaults = {
394
391
  dir: getEnv("IPX_DIR", "."),
@@ -400,42 +397,42 @@ function createIPX(userOptions) {
400
397
  };
401
398
  const options = defu(userOptions, defaults);
402
399
  options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [withLeadingSlash(e[0]), e[1]]));
403
- const ctx = {
400
+ const context = {
404
401
  sources: {}
405
402
  };
406
403
  if (options.dir) {
407
- ctx.sources.filesystem = createFilesystemSource({
404
+ context.sources.filesystem = createFilesystemSource({
408
405
  dir: options.dir,
409
406
  maxAge: options.maxAge
410
407
  });
411
408
  }
412
409
  if (options.domains) {
413
- ctx.sources.http = createHTTPSource({
410
+ context.sources.http = createHTTPSource({
414
411
  domains: options.domains,
415
412
  fetchOptions: options.fetchOptions,
416
413
  maxAge: options.maxAge
417
414
  });
418
415
  }
419
- return function ipx(id, modifiers = {}, reqOptions = {}) {
416
+ return function ipx(id, modifiers = {}, requestOptions = {}) {
420
417
  if (!id) {
421
418
  throw createError("resource id is missing", 400);
422
419
  }
423
420
  id = hasProtocol(id) ? id : withLeadingSlash(id);
424
421
  for (const base in options.alias) {
425
422
  if (id.startsWith(base)) {
426
- id = joinURL(options.alias[base], id.substr(base.length));
423
+ id = joinURL(options.alias[base], id.slice(base.length));
427
424
  }
428
425
  }
429
- const getSrc = cachedPromise(() => {
426
+ const getSource = cachedPromise(() => {
430
427
  const source = hasProtocol(id) ? "http" : "filesystem";
431
- if (!ctx.sources[source]) {
428
+ if (!context.sources[source]) {
432
429
  throw createError("Unknown source", 400, source);
433
430
  }
434
- return ctx.sources[source](id, reqOptions);
431
+ return context.sources[source](id, requestOptions);
435
432
  });
436
433
  const getData = cachedPromise(async () => {
437
- const src = await getSrc();
438
- const data = await src.getData();
434
+ const source = await getSource();
435
+ const data = await source.getData();
439
436
  const meta = imageMeta(data);
440
437
  const mFormat = modifiers.f || modifiers.format;
441
438
  let format = mFormat || meta.type;
@@ -453,18 +450,18 @@ function createIPX(userOptions) {
453
450
  const Sharp = await import('sharp').then((r) => r.default || r);
454
451
  let sharp = Sharp(data, { animated });
455
452
  Object.assign(sharp.options, options.sharp);
456
- const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
453
+ const handlers = Object.entries(modifiers).map(([name, arguments_]) => ({ handler: getHandler(name), name, args: arguments_ })).filter((h) => h.handler).sort((a, b) => {
457
454
  const aKey = (a.handler.order || a.name || "").toString();
458
455
  const bKey = (b.handler.order || b.name || "").toString();
459
456
  return aKey.localeCompare(bKey);
460
457
  });
461
- const handlerCtx = { meta };
458
+ const handlerContext = { meta };
462
459
  for (const h of handlers) {
463
- sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
460
+ sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
464
461
  }
465
- if (SUPPORTED_FORMATS.includes(format)) {
462
+ if (SUPPORTED_FORMATS.has(format)) {
466
463
  sharp = sharp.toFormat(format, {
467
- quality: handlerCtx.quality,
464
+ quality: handlerContext.quality,
468
465
  progressive: format === "jpeg"
469
466
  });
470
467
  }
@@ -476,54 +473,52 @@ function createIPX(userOptions) {
476
473
  };
477
474
  });
478
475
  return {
479
- src: getSrc,
476
+ src: getSource,
480
477
  data: getData
481
478
  };
482
479
  };
483
480
  }
484
481
 
485
- const MODIFIER_SEP = /[,&]/g;
486
- const MODIFIER_VAL_SEP = /[_=:]/g;
487
- async function _handleRequest(req, ipx) {
482
+ const MODIFIER_SEP = /[&,]/g;
483
+ const MODIFIER_VAL_SEP = /[:=_]/g;
484
+ async function _handleRequest(request, ipx) {
488
485
  const res = {
489
486
  statusCode: 200,
490
487
  statusMessage: "",
491
488
  headers: {},
492
489
  body: ""
493
490
  };
494
- const [modifiersStr = "", ...idSegments] = req.url.substring(1).split("/");
491
+ const [modifiersString = "", ...idSegments] = request.url.slice(1).split("/");
495
492
  const id = safeString(decode(idSegments.join("/")));
496
- if (!modifiersStr) {
497
- throw createError("Modifiers are missing", 400, req.url);
493
+ if (!modifiersString) {
494
+ throw createError("Modifiers are missing", 400, request.url);
498
495
  }
499
496
  if (!id || id === "/") {
500
- throw createError("Resource id is missing", 400, req.url);
497
+ throw createError("Resource id is missing", 400, request.url);
501
498
  }
502
499
  const modifiers = /* @__PURE__ */ Object.create(null);
503
- if (modifiersStr !== "_") {
504
- for (const p of modifiersStr.split(MODIFIER_SEP)) {
500
+ if (modifiersString !== "_") {
501
+ for (const p of modifiersString.split(MODIFIER_SEP)) {
505
502
  const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
506
503
  modifiers[safeString(key)] = safeString(decode(value));
507
504
  }
508
505
  }
509
- const img = ipx(id, modifiers, req.options);
510
- const src = await img.src();
511
- if (src.mtime) {
512
- if (req.headers["if-modified-since"]) {
513
- if (new Date(req.headers["if-modified-since"]) >= src.mtime) {
514
- res.statusCode = 304;
515
- return res;
516
- }
506
+ const img = ipx(id, modifiers, request.options);
507
+ const source = await img.src();
508
+ if (source.mtime) {
509
+ if (request.headers["if-modified-since"] && new Date(request.headers["if-modified-since"]) >= source.mtime) {
510
+ res.statusCode = 304;
511
+ return res;
517
512
  }
518
- res.headers["Last-Modified"] = +src.mtime + "";
513
+ res.headers["Last-Modified"] = source.mtime.toUTCString();
519
514
  }
520
- if (typeof src.maxAge === "number") {
521
- res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
515
+ if (typeof source.maxAge === "number") {
516
+ res.headers["Cache-Control"] = `max-age=${+source.maxAge}, public, s-maxage=${+source.maxAge}`;
522
517
  }
523
518
  const { data, format } = await img.data();
524
519
  const etag = getEtag(data);
525
520
  res.headers.ETag = etag;
526
- if (etag && req.headers["if-none-match"] === etag) {
521
+ if (etag && request.headers["if-none-match"] === etag) {
527
522
  res.statusCode = 304;
528
523
  return res;
529
524
  }
@@ -534,24 +529,24 @@ async function _handleRequest(req, ipx) {
534
529
  res.body = data;
535
530
  return sanetizeReponse(res);
536
531
  }
537
- function handleRequest(req, ipx) {
538
- return _handleRequest(req, ipx).catch((err) => {
539
- const statusCode = parseInt(err.statusCode) || 500;
540
- const statusMessage = err.statusMessage ? err.statusMessage : `IPX Error (${statusCode})`;
532
+ function handleRequest(request, ipx) {
533
+ return _handleRequest(request, ipx).catch((error) => {
534
+ const statusCode = Number.parseInt(error.statusCode) || 500;
535
+ const statusMessage = error.statusMessage ? error.statusMessage : `IPX Error (${statusCode})`;
541
536
  if (process.env.NODE_ENV !== "production" && statusCode === 500) {
542
- console.error(err);
537
+ console.error(error);
543
538
  }
544
539
  return sanetizeReponse({
545
540
  statusCode,
546
541
  statusMessage,
547
- body: "IPX Error: " + err,
542
+ body: "IPX Error: " + error,
548
543
  headers: {}
549
544
  });
550
545
  });
551
546
  }
552
547
  function createIPXMiddleware(ipx) {
553
- return function IPXMiddleware(req, res) {
554
- return handleRequest({ url: req.url, headers: req.headers }, ipx).then((_res) => {
548
+ return function IPXMiddleware(request, res) {
549
+ return handleRequest({ url: request.url, headers: request.headers }, ipx).then((_res) => {
555
550
  res.statusCode = _res.statusCode;
556
551
  res.statusMessage = _res.statusMessage;
557
552
  for (const name in _res.headers) {
@@ -3,24 +3,15 @@
3
3
  const defu = require('defu');
4
4
  const imageMeta = require('image-meta');
5
5
  const ufo = require('ufo');
6
- const fs = require('fs');
6
+ const node_fs = require('node:fs');
7
7
  const pathe = require('pathe');
8
- const http = require('http');
9
- const https = require('https');
8
+ const http = require('node:http');
9
+ const https = require('node:https');
10
10
  const ohmyfetch = require('ohmyfetch');
11
11
  const destr = require('destr');
12
12
  const getEtag = require('etag');
13
13
  const xss = require('xss');
14
14
 
15
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
16
-
17
- const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
18
- const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
19
- const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
20
- const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
21
- const getEtag__default = /*#__PURE__*/_interopDefaultLegacy(getEtag);
22
- const xss__default = /*#__PURE__*/_interopDefaultLegacy(xss);
23
-
24
15
  const Handlers = {
25
16
  __proto__: null,
26
17
  get quality () { return quality; },
@@ -58,25 +49,25 @@ const Handlers = {
58
49
  };
59
50
 
60
51
  function getEnv(name, defaultValue) {
61
- return destr__default(process.env[name]) ?? defaultValue;
52
+ return destr(process.env[name]) ?? defaultValue;
62
53
  }
63
- function cachedPromise(fn) {
54
+ function cachedPromise(function_) {
64
55
  let p;
65
- return (...args) => {
56
+ return (...arguments_) => {
66
57
  if (p) {
67
58
  return p;
68
59
  }
69
- p = Promise.resolve(fn(...args));
60
+ p = Promise.resolve(function_(...arguments_));
70
61
  return p;
71
62
  };
72
63
  }
73
64
  class IPXError extends Error {
74
65
  }
75
66
  function createError(statusMessage, statusCode, trace) {
76
- const err = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
77
- err.statusMessage = "IPX: " + statusMessage;
78
- err.statusCode = statusCode;
79
- return err;
67
+ const error = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
68
+ error.statusMessage = "IPX: " + statusMessage;
69
+ error.statusCode = statusCode;
70
+ return error;
80
71
  }
81
72
 
82
73
  const createFilesystemSource = (options) => {
@@ -88,13 +79,10 @@ const createFilesystemSource = (options) => {
88
79
  }
89
80
  let stats;
90
81
  try {
91
- stats = await fs.promises.stat(fsPath);
92
- } catch (err) {
93
- if (err.code === "ENOENT") {
94
- throw createError("File not found", 404, fsPath);
95
- } else {
96
- throw createError("File access error " + err.code, 403, fsPath);
97
- }
82
+ stats = await node_fs.promises.stat(fsPath);
83
+ } catch (error_) {
84
+ const error = error_.code === "ENOENT" ? createError("File not found", 404, fsPath) : createError("File access error " + error_.code, 403, fsPath);
85
+ throw error;
98
86
  }
99
87
  if (!stats.isFile()) {
100
88
  throw createError("Path should be a file", 400, fsPath);
@@ -102,7 +90,7 @@ const createFilesystemSource = (options) => {
102
90
  return {
103
91
  mtime: stats.mtime,
104
92
  maxAge: options.maxAge,
105
- getData: cachedPromise(() => fs.promises.readFile(fsPath))
93
+ getData: cachedPromise(() => node_fs.promises.readFile(fsPath))
106
94
  };
107
95
  };
108
96
  };
@@ -111,7 +99,7 @@ function isValidPath(fp) {
111
99
  if (isWindows) {
112
100
  fp = fp.slice(pathe.parse(fp).root.length);
113
101
  }
114
- if (/[<>:"|?*]/.test(fp)) {
102
+ if (/["*:<>?|]/.test(fp)) {
115
103
  return false;
116
104
  }
117
105
  return true;
@@ -119,24 +107,24 @@ function isValidPath(fp) {
119
107
 
120
108
  const HTTP_RE = /^https?:\/\//;
121
109
  const createHTTPSource = (options) => {
122
- const httpsAgent = new https__default.Agent({ keepAlive: true });
123
- const httpAgent = new http__default.Agent({ keepAlive: true });
110
+ const httpsAgent = new https.Agent({ keepAlive: true });
111
+ const httpAgent = new http.Agent({ keepAlive: true });
124
112
  let _domains = options.domains || [];
125
113
  if (typeof _domains === "string") {
126
114
  _domains = _domains.split(",").map((s) => s.trim());
127
115
  }
128
- const domains = _domains.map((d) => {
116
+ const domains = new Set(_domains.map((d) => {
129
117
  if (!HTTP_RE.test(d)) {
130
118
  d = "http://" + d;
131
119
  }
132
120
  return new URL(d).hostname;
133
- }).filter(Boolean);
134
- return async (id, reqOptions) => {
121
+ }).filter(Boolean));
122
+ return async (id, requestOptions) => {
135
123
  const hostname = new URL(id).hostname;
136
124
  if (!hostname) {
137
125
  throw createError("Hostname is missing", 403, id);
138
126
  }
139
- if (!reqOptions?.bypassDomain && !domains.find((domain) => hostname === domain)) {
127
+ if (!requestOptions?.bypassDomain && !domains.has(hostname)) {
140
128
  throw createError("Forbidden host", 403, hostname);
141
129
  }
142
130
  const response = await ohmyfetch.fetch(id, {
@@ -151,7 +139,7 @@ const createHTTPSource = (options) => {
151
139
  if (_cacheControl) {
152
140
  const m = _cacheControl.match(/max-age=(\d+)/);
153
141
  if (m && m[1]) {
154
- maxAge = parseInt(m[1]);
142
+ maxAge = Number.parseInt(m[1]);
155
143
  }
156
144
  }
157
145
  let mtime;
@@ -167,19 +155,19 @@ const createHTTPSource = (options) => {
167
155
  };
168
156
  };
169
157
 
170
- function VArg(arg) {
171
- return destr__default(arg);
158
+ function VArg(argument) {
159
+ return destr(argument);
172
160
  }
173
- function parseArgs(args, mappers) {
174
- const vargs = args.split("_");
175
- return mappers.map((v, i) => v(vargs[i]));
161
+ function parseArgs(arguments_, mappers) {
162
+ const vargs = arguments_.split("_");
163
+ return mappers.map((v, index) => v(vargs[index]));
176
164
  }
177
165
  function getHandler(key) {
178
166
  return Handlers[key];
179
167
  }
180
- function applyHandler(ctx, pipe, handler, argsStr) {
181
- const args = handler.args ? parseArgs(argsStr, handler.args) : [];
182
- return handler.apply(ctx, pipe, ...args);
168
+ function applyHandler(context, pipe, handler, argumentsString) {
169
+ const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
170
+ return handler.apply(context, pipe, ...arguments_);
183
171
  }
184
172
  function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
185
173
  const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
@@ -216,8 +204,8 @@ const position = {
216
204
  context.position = position2;
217
205
  }
218
206
  };
219
- const HEX_RE = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
220
- const SHORTHEX_RE = /^([a-f\d])([a-f\d])([a-f\d])$/i;
207
+ const HEX_RE = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
208
+ const SHORTHEX_RE = /^([\da-f])([\da-f])([\da-f])$/i;
221
209
  const background = {
222
210
  args: [VArg],
223
211
  order: -1,
@@ -238,19 +226,19 @@ const enlarge = {
238
226
  const width = {
239
227
  args: [VArg],
240
228
  apply: (context, pipe, width2) => {
241
- return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
229
+ return pipe.resize(width2, void 0, { withoutEnlargement: !context.enlarge });
242
230
  }
243
231
  };
244
232
  const height = {
245
233
  args: [VArg],
246
234
  apply: (context, pipe, height2) => {
247
- return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
235
+ return pipe.resize(void 0, height2, { withoutEnlargement: !context.enlarge });
248
236
  }
249
237
  };
250
238
  const resize = {
251
239
  args: [VArg, VArg, VArg],
252
240
  apply: (context, pipe, size) => {
253
- let [width2, height2] = String(size).split("x").map((v) => Number(v));
241
+ let [width2, height2] = String(size).split("x").map(Number);
254
242
  if (!width2) {
255
243
  return;
256
244
  }
@@ -399,7 +387,7 @@ const h = height;
399
387
  const s = resize;
400
388
  const pos = position;
401
389
 
402
- const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
390
+ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set(["jpeg", "png", "webp", "avif", "tiff", "gif"]);
403
391
  function createIPX(userOptions) {
404
392
  const defaults = {
405
393
  dir: getEnv("IPX_DIR", "."),
@@ -409,44 +397,44 @@ function createIPX(userOptions) {
409
397
  maxAge: getEnv("IPX_MAX_AGE", 300),
410
398
  sharp: {}
411
399
  };
412
- const options = defu__default(userOptions, defaults);
400
+ const options = defu(userOptions, defaults);
413
401
  options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [ufo.withLeadingSlash(e[0]), e[1]]));
414
- const ctx = {
402
+ const context = {
415
403
  sources: {}
416
404
  };
417
405
  if (options.dir) {
418
- ctx.sources.filesystem = createFilesystemSource({
406
+ context.sources.filesystem = createFilesystemSource({
419
407
  dir: options.dir,
420
408
  maxAge: options.maxAge
421
409
  });
422
410
  }
423
411
  if (options.domains) {
424
- ctx.sources.http = createHTTPSource({
412
+ context.sources.http = createHTTPSource({
425
413
  domains: options.domains,
426
414
  fetchOptions: options.fetchOptions,
427
415
  maxAge: options.maxAge
428
416
  });
429
417
  }
430
- return function ipx(id, modifiers = {}, reqOptions = {}) {
418
+ return function ipx(id, modifiers = {}, requestOptions = {}) {
431
419
  if (!id) {
432
420
  throw createError("resource id is missing", 400);
433
421
  }
434
422
  id = ufo.hasProtocol(id) ? id : ufo.withLeadingSlash(id);
435
423
  for (const base in options.alias) {
436
424
  if (id.startsWith(base)) {
437
- id = ufo.joinURL(options.alias[base], id.substr(base.length));
425
+ id = ufo.joinURL(options.alias[base], id.slice(base.length));
438
426
  }
439
427
  }
440
- const getSrc = cachedPromise(() => {
428
+ const getSource = cachedPromise(() => {
441
429
  const source = ufo.hasProtocol(id) ? "http" : "filesystem";
442
- if (!ctx.sources[source]) {
430
+ if (!context.sources[source]) {
443
431
  throw createError("Unknown source", 400, source);
444
432
  }
445
- return ctx.sources[source](id, reqOptions);
433
+ return context.sources[source](id, requestOptions);
446
434
  });
447
435
  const getData = cachedPromise(async () => {
448
- const src = await getSrc();
449
- const data = await src.getData();
436
+ const source = await getSource();
437
+ const data = await source.getData();
450
438
  const meta = imageMeta.imageMeta(data);
451
439
  const mFormat = modifiers.f || modifiers.format;
452
440
  let format = mFormat || meta.type;
@@ -464,18 +452,18 @@ function createIPX(userOptions) {
464
452
  const Sharp = await import('sharp').then((r) => r.default || r);
465
453
  let sharp = Sharp(data, { animated });
466
454
  Object.assign(sharp.options, options.sharp);
467
- const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
455
+ const handlers = Object.entries(modifiers).map(([name, arguments_]) => ({ handler: getHandler(name), name, args: arguments_ })).filter((h) => h.handler).sort((a, b) => {
468
456
  const aKey = (a.handler.order || a.name || "").toString();
469
457
  const bKey = (b.handler.order || b.name || "").toString();
470
458
  return aKey.localeCompare(bKey);
471
459
  });
472
- const handlerCtx = { meta };
460
+ const handlerContext = { meta };
473
461
  for (const h of handlers) {
474
- sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
462
+ sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
475
463
  }
476
- if (SUPPORTED_FORMATS.includes(format)) {
464
+ if (SUPPORTED_FORMATS.has(format)) {
477
465
  sharp = sharp.toFormat(format, {
478
- quality: handlerCtx.quality,
466
+ quality: handlerContext.quality,
479
467
  progressive: format === "jpeg"
480
468
  });
481
469
  }
@@ -487,54 +475,52 @@ function createIPX(userOptions) {
487
475
  };
488
476
  });
489
477
  return {
490
- src: getSrc,
478
+ src: getSource,
491
479
  data: getData
492
480
  };
493
481
  };
494
482
  }
495
483
 
496
- const MODIFIER_SEP = /[,&]/g;
497
- const MODIFIER_VAL_SEP = /[_=:]/g;
498
- async function _handleRequest(req, ipx) {
484
+ const MODIFIER_SEP = /[&,]/g;
485
+ const MODIFIER_VAL_SEP = /[:=_]/g;
486
+ async function _handleRequest(request, ipx) {
499
487
  const res = {
500
488
  statusCode: 200,
501
489
  statusMessage: "",
502
490
  headers: {},
503
491
  body: ""
504
492
  };
505
- const [modifiersStr = "", ...idSegments] = req.url.substring(1).split("/");
493
+ const [modifiersString = "", ...idSegments] = request.url.slice(1).split("/");
506
494
  const id = safeString(ufo.decode(idSegments.join("/")));
507
- if (!modifiersStr) {
508
- throw createError("Modifiers are missing", 400, req.url);
495
+ if (!modifiersString) {
496
+ throw createError("Modifiers are missing", 400, request.url);
509
497
  }
510
498
  if (!id || id === "/") {
511
- throw createError("Resource id is missing", 400, req.url);
499
+ throw createError("Resource id is missing", 400, request.url);
512
500
  }
513
501
  const modifiers = /* @__PURE__ */ Object.create(null);
514
- if (modifiersStr !== "_") {
515
- for (const p of modifiersStr.split(MODIFIER_SEP)) {
502
+ if (modifiersString !== "_") {
503
+ for (const p of modifiersString.split(MODIFIER_SEP)) {
516
504
  const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
517
505
  modifiers[safeString(key)] = safeString(ufo.decode(value));
518
506
  }
519
507
  }
520
- const img = ipx(id, modifiers, req.options);
521
- const src = await img.src();
522
- if (src.mtime) {
523
- if (req.headers["if-modified-since"]) {
524
- if (new Date(req.headers["if-modified-since"]) >= src.mtime) {
525
- res.statusCode = 304;
526
- return res;
527
- }
508
+ const img = ipx(id, modifiers, request.options);
509
+ const source = await img.src();
510
+ if (source.mtime) {
511
+ if (request.headers["if-modified-since"] && new Date(request.headers["if-modified-since"]) >= source.mtime) {
512
+ res.statusCode = 304;
513
+ return res;
528
514
  }
529
- res.headers["Last-Modified"] = +src.mtime + "";
515
+ res.headers["Last-Modified"] = source.mtime.toUTCString();
530
516
  }
531
- if (typeof src.maxAge === "number") {
532
- res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
517
+ if (typeof source.maxAge === "number") {
518
+ res.headers["Cache-Control"] = `max-age=${+source.maxAge}, public, s-maxage=${+source.maxAge}`;
533
519
  }
534
520
  const { data, format } = await img.data();
535
- const etag = getEtag__default(data);
521
+ const etag = getEtag(data);
536
522
  res.headers.ETag = etag;
537
- if (etag && req.headers["if-none-match"] === etag) {
523
+ if (etag && request.headers["if-none-match"] === etag) {
538
524
  res.statusCode = 304;
539
525
  return res;
540
526
  }
@@ -545,24 +531,24 @@ async function _handleRequest(req, ipx) {
545
531
  res.body = data;
546
532
  return sanetizeReponse(res);
547
533
  }
548
- function handleRequest(req, ipx) {
549
- return _handleRequest(req, ipx).catch((err) => {
550
- const statusCode = parseInt(err.statusCode) || 500;
551
- const statusMessage = err.statusMessage ? err.statusMessage : `IPX Error (${statusCode})`;
534
+ function handleRequest(request, ipx) {
535
+ return _handleRequest(request, ipx).catch((error) => {
536
+ const statusCode = Number.parseInt(error.statusCode) || 500;
537
+ const statusMessage = error.statusMessage ? error.statusMessage : `IPX Error (${statusCode})`;
552
538
  if (process.env.NODE_ENV !== "production" && statusCode === 500) {
553
- console.error(err);
539
+ console.error(error);
554
540
  }
555
541
  return sanetizeReponse({
556
542
  statusCode,
557
543
  statusMessage,
558
- body: "IPX Error: " + err,
544
+ body: "IPX Error: " + error,
559
545
  headers: {}
560
546
  });
561
547
  });
562
548
  }
563
549
  function createIPXMiddleware(ipx) {
564
- return function IPXMiddleware(req, res) {
565
- return handleRequest({ url: req.url, headers: req.headers }, ipx).then((_res) => {
550
+ return function IPXMiddleware(request, res) {
551
+ return handleRequest({ url: request.url, headers: request.headers }, ipx).then((_res) => {
566
552
  res.statusCode = _res.statusCode;
567
553
  res.statusMessage = _res.statusMessage;
568
554
  for (const name in _res.headers) {
@@ -577,7 +563,7 @@ function sanetizeReponse(res) {
577
563
  statusCode: res.statusCode || 200,
578
564
  statusMessage: res.statusMessage ? safeString(res.statusMessage) : "OK",
579
565
  headers: safeStringObject(res.headers || {}),
580
- body: typeof res.body === "string" ? xss__default(safeString(res.body)) : res.body || ""
566
+ body: typeof res.body === "string" ? xss(safeString(res.body)) : res.body || ""
581
567
  };
582
568
  }
583
569
  function safeString(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipx",
3
- "version": "0.9.11",
3
+ "version": "1.0.0-0",
4
4
  "repository": "unjs/ipx",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -17,43 +17,45 @@
17
17
  "dist",
18
18
  "bin"
19
19
  ],
20
+ "scripts": {
21
+ "build": "unbuild",
22
+ "dev": "nodemon",
23
+ "lint": "eslint --ext .ts .",
24
+ "prepack": "pnpm build",
25
+ "release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
26
+ "start": "node bin/ipx.js",
27
+ "test": "pnpm lint && vitest run --coverage"
28
+ },
20
29
  "dependencies": {
21
30
  "consola": "^2.15.3",
22
- "defu": "^6.1.0",
23
- "destr": "^1.1.1",
31
+ "defu": "^6.1.1",
32
+ "destr": "^1.2.1",
24
33
  "etag": "^1.8.1",
25
34
  "image-meta": "^0.1.1",
26
- "listhen": "^0.2.15",
27
- "ohmyfetch": "^0.4.18",
28
- "pathe": "^0.3.5",
29
- "sharp": "^0.30.7",
30
- "ufo": "^0.8.5",
35
+ "listhen": "^1.0.0",
36
+ "ohmyfetch": "^0.4.21",
37
+ "pathe": "^1.0.0",
38
+ "sharp": "^0.31.2",
39
+ "ufo": "^1.0.0",
31
40
  "xss": "^1.0.14"
32
41
  },
33
42
  "devDependencies": {
34
- "@nuxtjs/eslint-config-typescript": "^11.0.0",
43
+ "@nuxtjs/eslint-config-typescript": "^12.0.0",
35
44
  "@types/etag": "^1.8.1",
36
45
  "@types/is-valid-path": "^0.1.0",
37
46
  "@types/node-fetch": "^2.6.2",
38
- "@types/sharp": "^0.30.5",
39
- "@vitest/coverage-c8": "^0.22.1",
40
- "changelogen": "^0.3.0",
41
- "eslint": "^8.23.0",
42
- "jiti": "^1.14.0",
43
- "nodemon": "^2.0.19",
44
- "serve-handler": "^6.1.3",
47
+ "@types/sharp": "^0.31.0",
48
+ "@vitest/coverage-c8": "^0.25.3",
49
+ "changelogen": "^0.4.0",
50
+ "eslint": "^8.28.0",
51
+ "eslint-config-unjs": "^0.0.2",
52
+ "jiti": "^1.16.0",
53
+ "nodemon": "^2.0.20",
54
+ "serve-handler": "^6.1.5",
45
55
  "standard-version": "^9.5.0",
46
- "typescript": "^4.8.2",
47
- "unbuild": "^0.8.10",
48
- "vitest": "^0.22.1"
56
+ "typescript": "^4.9.3",
57
+ "unbuild": "^1.0.1",
58
+ "vitest": "^0.25.3"
49
59
  },
50
- "packageManager": "pnpm@7.9.5",
51
- "scripts": {
52
- "build": "unbuild",
53
- "dev": "nodemon",
54
- "lint": "eslint --ext .ts .",
55
- "release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
56
- "start": "node bin/ipx.js",
57
- "test": "pnpm lint && vitest run --coverage"
58
- }
59
- }
60
+ "packageManager": "pnpm@7.17.0"
61
+ }