ipx 0.8.0 → 0.9.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
@@ -6,7 +6,7 @@
6
6
 
7
7
  High performance, secure and easy to use image proxy based on [sharp](https://github.com/lovell/sharp) and [libvips](https://github.com/libvips/libvips).
8
8
 
9
- <h2 align="center">Usage</h2>
9
+ ## Usage
10
10
 
11
11
  ### Quick Start
12
12
 
@@ -85,6 +85,6 @@ Config can be customized using `IPX_*` environment variables.
85
85
  - `IPX_DOMAINS`
86
86
  - Default: `[]`
87
87
 
88
- <h2 align="center">License</h2>
88
+ ## License
89
89
 
90
90
  MIT
package/dist/index.cjs CHANGED
@@ -2,34 +2,30 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- const Sharp = require('sharp');
6
5
  const defu = require('defu');
7
6
  const imageMeta = require('image-meta');
8
7
  const ufo = require('ufo');
9
- const path = require('path');
8
+ const fs = require('fs');
9
+ const pathe = require('pathe');
10
10
  const isValidPath = require('is-valid-path');
11
- const fsExtra = require('fs-extra');
12
11
  const destr = require('destr');
13
12
  const http = require('http');
14
13
  const https = require('https');
15
- const fetch = require('node-fetch');
14
+ const ohmyfetch = require('ohmyfetch');
16
15
  const getEtag = require('etag');
17
16
  const xss = require('xss');
18
17
 
19
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
20
19
 
21
- const Sharp__default = /*#__PURE__*/_interopDefaultLegacy(Sharp);
22
20
  const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
23
- const imageMeta__default = /*#__PURE__*/_interopDefaultLegacy(imageMeta);
24
21
  const isValidPath__default = /*#__PURE__*/_interopDefaultLegacy(isValidPath);
25
22
  const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
26
23
  const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
27
24
  const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
28
- const fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
29
25
  const getEtag__default = /*#__PURE__*/_interopDefaultLegacy(getEtag);
30
26
  const xss__default = /*#__PURE__*/_interopDefaultLegacy(xss);
31
27
 
32
- const Handlers = /*#__PURE__*/Object.freeze({
28
+ const Handlers = {
33
29
  __proto__: null,
34
30
  get quality () { return quality; },
35
31
  get fit () { return fit; },
@@ -61,11 +57,10 @@ const Handlers = /*#__PURE__*/Object.freeze({
61
57
  get w () { return w; },
62
58
  get h () { return h; },
63
59
  get s () { return s; }
64
- });
60
+ };
65
61
 
66
62
  function getEnv(name, defaultValue) {
67
- var _a;
68
- return (_a = destr__default['default'](process.env[name])) != null ? _a : defaultValue;
63
+ return destr__default(process.env[name]) ?? defaultValue;
69
64
  }
70
65
  function cachedPromise(fn) {
71
66
  let p;
@@ -87,15 +82,15 @@ function createError(message, statusCode) {
87
82
  }
88
83
 
89
84
  const createFilesystemSource = (options) => {
90
- const rootDir = path.resolve(options.dir);
85
+ const rootDir = pathe.resolve(options.dir);
91
86
  return async (id) => {
92
- const fsPath = path.resolve(path.join(rootDir, id));
93
- if (!isValidPath__default['default'](id) || id.includes("..") || !fsPath.startsWith(rootDir)) {
87
+ const fsPath = pathe.resolve(pathe.join(rootDir, id));
88
+ if (!isValidPath__default(id) || id.includes("..") || !fsPath.startsWith(rootDir)) {
94
89
  throw createError("Forbidden path:" + id, 403);
95
90
  }
96
91
  let stats;
97
92
  try {
98
- stats = await fsExtra.stat(fsPath);
93
+ stats = await fs.promises.stat(fsPath);
99
94
  } catch (err) {
100
95
  if (err.code === "ENOENT") {
101
96
  throw createError("File not found: " + fsPath, 404);
@@ -109,14 +104,14 @@ const createFilesystemSource = (options) => {
109
104
  return {
110
105
  mtime: stats.mtime,
111
106
  maxAge: options.maxAge || 300,
112
- getData: cachedPromise(() => fsExtra.readFile(fsPath))
107
+ getData: cachedPromise(() => fs.promises.readFile(fsPath))
113
108
  };
114
109
  };
115
110
  };
116
111
 
117
112
  const createHTTPSource = (options) => {
118
- const httpsAgent = new https__default['default'].Agent({ keepAlive: true });
119
- const httpAgent = new http__default['default'].Agent({ keepAlive: true });
113
+ const httpsAgent = new https__default.Agent({ keepAlive: true });
114
+ const httpAgent = new http__default.Agent({ keepAlive: true });
120
115
  let domains = options.domains || [];
121
116
  if (typeof domains === "string") {
122
117
  domains = domains.split(",").map((s) => s.trim());
@@ -127,10 +122,10 @@ const createHTTPSource = (options) => {
127
122
  if (!parsedUrl.host) {
128
123
  throw createError("Hostname is missing: " + id, 403);
129
124
  }
130
- if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => parsedUrl.host === host)) {
125
+ if (!reqOptions?.bypassDomain && !hosts.find((host) => parsedUrl.host === host)) {
131
126
  throw createError("Forbidden host: " + parsedUrl.host, 403);
132
127
  }
133
- const response = await fetch__default['default'](id, {
128
+ const response = await ohmyfetch.fetch(id, {
134
129
  agent: id.startsWith("https") ? httpsAgent : httpAgent
135
130
  });
136
131
  if (!response.ok) {
@@ -158,7 +153,7 @@ const createHTTPSource = (options) => {
158
153
  };
159
154
 
160
155
  function VArg(arg) {
161
- return destr__default['default'](arg);
156
+ return destr__default(arg);
162
157
  }
163
158
  function parseArgs(args, mappers) {
164
159
  const vargs = args.split("_");
@@ -382,7 +377,7 @@ function createIPX(userOptions) {
382
377
  alias: getEnv("IPX_ALIAS", {}),
383
378
  sharp: {}
384
379
  };
385
- const options = defu__default['default'](userOptions, defaults);
380
+ const options = defu__default(userOptions, defaults);
386
381
  options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [ufo.withLeadingSlash(e[0]), e[1]]));
387
382
  const ctx = {
388
383
  sources: {}
@@ -417,7 +412,7 @@ function createIPX(userOptions) {
417
412
  const getData = cachedPromise(async () => {
418
413
  const src = await getSrc();
419
414
  const data = await src.getData();
420
- const meta = imageMeta__default['default'](data);
415
+ const meta = imageMeta.imageMeta(data);
421
416
  const mFormat = modifiers.f || modifiers.format;
422
417
  let format = mFormat || meta.type;
423
418
  if (format === "jpg") {
@@ -434,7 +429,8 @@ function createIPX(userOptions) {
434
429
  if (animated) {
435
430
  format = "webp";
436
431
  }
437
- let sharp = Sharp__default['default'](data, { animated });
432
+ const Sharp = await import('sharp').then((r) => r.default || r);
433
+ let sharp = Sharp(data, { animated });
438
434
  Object.assign(sharp.options, options.sharp);
439
435
  const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
440
436
  const aKey = (a.handler.order || a.name || "").toString();
@@ -502,7 +498,7 @@ async function _handleRequest(req, ipx) {
502
498
  res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
503
499
  }
504
500
  const { data, format } = await img.data();
505
- const etag = getEtag__default['default'](data);
501
+ const etag = getEtag__default(data);
506
502
  res.headers.ETag = etag;
507
503
  if (etag && req.headers["if-none-match"] === etag) {
508
504
  res.statusCode = 304;
@@ -517,7 +513,7 @@ async function _handleRequest(req, ipx) {
517
513
  function handleRequest(req, ipx) {
518
514
  return _handleRequest(req, ipx).catch((err) => {
519
515
  const statusCode = parseInt(err.statusCode) || 500;
520
- const statusMessage = err.statusMessage ? xss__default['default'](err.statusMessage) : `IPX Error (${statusCode})`;
516
+ const statusMessage = err.statusMessage ? xss__default(err.statusMessage) : `IPX Error (${statusCode})`;
521
517
  if (process.env.NODE_ENV !== "production" && statusCode === 500) {
522
518
  console.error(err);
523
519
  }
package/dist/index.mjs CHANGED
@@ -1,18 +1,17 @@
1
- import Sharp from 'sharp';
2
1
  import defu from 'defu';
3
- import imageMeta from 'image-meta';
2
+ import { imageMeta } from 'image-meta';
4
3
  import { parseURL, withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
5
- import { resolve, join } from 'path';
4
+ import { promises } from 'fs';
5
+ import { resolve, join } from 'pathe';
6
6
  import isValidPath from 'is-valid-path';
7
- import { stat, readFile } from 'fs-extra';
8
7
  import destr from 'destr';
9
8
  import http from 'http';
10
9
  import https from 'https';
11
- import fetch from 'node-fetch';
10
+ import { fetch } from 'ohmyfetch';
12
11
  import getEtag from 'etag';
13
12
  import xss from 'xss';
14
13
 
15
- var Handlers = /*#__PURE__*/Object.freeze({
14
+ const Handlers = {
16
15
  __proto__: null,
17
16
  get quality () { return quality; },
18
17
  get fit () { return fit; },
@@ -44,11 +43,10 @@ var Handlers = /*#__PURE__*/Object.freeze({
44
43
  get w () { return w; },
45
44
  get h () { return h; },
46
45
  get s () { return s; }
47
- });
46
+ };
48
47
 
49
48
  function getEnv(name, defaultValue) {
50
- var _a;
51
- return (_a = destr(process.env[name])) != null ? _a : defaultValue;
49
+ return destr(process.env[name]) ?? defaultValue;
52
50
  }
53
51
  function cachedPromise(fn) {
54
52
  let p;
@@ -78,7 +76,7 @@ const createFilesystemSource = (options) => {
78
76
  }
79
77
  let stats;
80
78
  try {
81
- stats = await stat(fsPath);
79
+ stats = await promises.stat(fsPath);
82
80
  } catch (err) {
83
81
  if (err.code === "ENOENT") {
84
82
  throw createError("File not found: " + fsPath, 404);
@@ -92,7 +90,7 @@ const createFilesystemSource = (options) => {
92
90
  return {
93
91
  mtime: stats.mtime,
94
92
  maxAge: options.maxAge || 300,
95
- getData: cachedPromise(() => readFile(fsPath))
93
+ getData: cachedPromise(() => promises.readFile(fsPath))
96
94
  };
97
95
  };
98
96
  };
@@ -110,7 +108,7 @@ const createHTTPSource = (options) => {
110
108
  if (!parsedUrl.host) {
111
109
  throw createError("Hostname is missing: " + id, 403);
112
110
  }
113
- if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => parsedUrl.host === host)) {
111
+ if (!reqOptions?.bypassDomain && !hosts.find((host) => parsedUrl.host === host)) {
114
112
  throw createError("Forbidden host: " + parsedUrl.host, 403);
115
113
  }
116
114
  const response = await fetch(id, {
@@ -417,6 +415,7 @@ function createIPX(userOptions) {
417
415
  if (animated) {
418
416
  format = "webp";
419
417
  }
418
+ const Sharp = await import('sharp').then((r) => r.default || r);
420
419
  let sharp = Sharp(data, { animated });
421
420
  Object.assign(sharp.options, options.sharp);
422
421
  const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ipx",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "repository": "unjs/ipx",
5
5
  "license": "MIT",
6
6
  "exports": {
@@ -17,10 +17,10 @@
17
17
  "dist"
18
18
  ],
19
19
  "scripts": {
20
- "build": "siroc build",
20
+ "build": "unbuild",
21
21
  "dev": "nodemon",
22
22
  "lint": "eslint --ext .ts .",
23
- "prepublishOnly": "yarn build",
23
+ "prepack": "yarn build",
24
24
  "release": "yarn test && standard-version && git push --follow-tags && npm publish",
25
25
  "start": "node bin/ipx.js",
26
26
  "test": "yarn lint && jest"
@@ -30,19 +30,18 @@
30
30
  "defu": "^5.0.0",
31
31
  "destr": "^1.1.0",
32
32
  "etag": "^1.8.1",
33
- "fs-extra": "^10.0.0",
34
- "image-meta": "^0.0.1",
33
+ "image-meta": "^0.1.1",
35
34
  "is-valid-path": "^0.1.1",
36
- "listhen": "^0.2.4",
37
- "node-fetch": "^2.6.1",
35
+ "listhen": "^0.2.5",
36
+ "ohmyfetch": "^0.4.2",
37
+ "pathe": "^0.2.0",
38
38
  "sharp": "^0.29.0",
39
39
  "ufo": "^0.7.9",
40
- "xss": "^1.0.9"
40
+ "xss": "^1.0.10"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@nuxtjs/eslint-config-typescript": "latest",
44
44
  "@types/etag": "latest",
45
- "@types/fs-extra": "latest",
46
45
  "@types/is-valid-path": "latest",
47
46
  "@types/jest": "latest",
48
47
  "@types/node-fetch": "latest",
@@ -51,9 +50,9 @@
51
50
  "jest": "latest",
52
51
  "jiti": "latest",
53
52
  "nodemon": "latest",
54
- "siroc": "latest",
55
53
  "standard-version": "latest",
56
54
  "ts-jest": "latest",
57
- "typescript": "latest"
55
+ "typescript": "latest",
56
+ "unbuild": "latest"
58
57
  }
59
58
  }
package/CHANGELOG.md DELETED
@@ -1,220 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
-
5
- ## [0.8.0](https://github.com/unjs/ipx/compare/v0.7.2...v0.8.0) (2021-09-05)
6
-
7
-
8
- ### ⚠ BREAKING CHANGES
9
-
10
- * update sharp to 0.29.0
11
-
12
- * update sharp to 0.29.0 ([b5a06fb](https://github.com/unjs/ipx/commit/b5a06fbdd2d5e0caf12f8c8a3d389ebed2744425)), closes [/github.com/lovell/sharp/blob/master/docs/changelog.md#v0290---17th-august-2021](https://github.com/unjs//github.com/lovell/sharp/blob/master/docs/changelog.md/issues/v0290---17th-august-2021)
13
-
14
- ### [0.7.2](https://github.com/unjs/ipx/compare/v0.7.1...v0.7.2) (2021-07-26)
15
-
16
-
17
- ### Features
18
-
19
- * default to not upscaling images ([#41](https://github.com/unjs/ipx/issues/41)) ([162f730](https://github.com/unjs/ipx/commit/162f7308650416905b33ab2c031c5fc7b82ef13b))
20
-
21
- ### [0.7.1](https://github.com/unjs/ipx/compare/v0.7.0...v0.7.1) (2021-07-02)
22
-
23
-
24
- ### Bug Fixes
25
-
26
- * handle background number ([2f82a56](https://github.com/unjs/ipx/commit/2f82a56893004e6797b23ef40c2940155fde63f4))
27
- * resize with width and hight ([3ca70a0](https://github.com/unjs/ipx/commit/3ca70a017d86a2d907a935eaf6ffab901424bffb))
28
- * support background with rotate ([b6c8f8c](https://github.com/unjs/ipx/commit/b6c8f8cb1310d18134d0ade2e4c023d3d7a1936c))
29
-
30
- ## [0.7.0](https://github.com/unjs/ipx/compare/v0.6.7...v0.7.0) (2021-07-01)
31
-
32
-
33
- ### ⚠ BREAKING CHANGES
34
-
35
- * **pkg:** add exports field
36
- * move modifiers to path from query
37
-
38
- ### Features
39
-
40
- * `reqOptions` and `bypassDomain` ([fc8c7b5](https://github.com/unjs/ipx/commit/fc8c7b5b655d61e23f6f63af82669ed23e48eec5))
41
- * **pkg:** add exports field ([394384f](https://github.com/unjs/ipx/commit/394384f19364845e228aedeee598d8960d263c7e))
42
- * move modifiers to path from query ([b7570d9](https://github.com/unjs/ipx/commit/b7570d942bf282da38acdc79b34c6e33177611c0))
43
-
44
-
45
- ### Bug Fixes
46
-
47
- * don't prepend trailing slash to external id ([01e151a](https://github.com/unjs/ipx/commit/01e151a90c0601802bf197cf28542d24fae1c3b4))
48
-
49
- ### [0.6.7](https://github.com/unjs/ipx/compare/v0.6.6...v0.6.7) (2021-07-01)
50
-
51
- ### Bug Fixes
52
-
53
- - **middleware:** set res.body ([d7dc146](https://github.com/unjs/ipx/commit/d7dc1466224310e583d6c595a3c1e67b00f4a13f))
54
-
55
- ### [0.6.6](https://github.com/unjs/ipx/compare/v0.6.5...v0.6.6) (2021-07-01)
56
-
57
- ### [0.6.5](https://github.com/unjs/ipx/compare/v0.6.4...v0.6.5) (2021-07-01)
58
-
59
- ### Features
60
-
61
- - expose handleRequest ([7c8c857](https://github.com/unjs/ipx/commit/7c8c857fc4a84d57ee8c2a5919f0b397c2e1b220))
62
-
63
- ### Bug Fixes
64
-
65
- - **filesystem:** handle when input is not a file ([9e1f7bf](https://github.com/unjs/ipx/commit/9e1f7bf73463b0958362bfa1443f1db24058410a))
66
-
67
- ### [0.6.4](https://github.com/unjs/ipx/compare/v0.6.3...v0.6.4) (2021-07-01)
68
-
69
- ### Bug Fixes
70
-
71
- - enforce leadingSlash for alias resolution ([3092e00](https://github.com/unjs/ipx/commit/3092e00870a29cb797c1c3b6cb921497523800fa))
72
-
73
- ### [0.6.3](https://github.com/unjs/ipx/compare/v0.6.2...v0.6.3) (2021-06-30)
74
-
75
- ### Bug Fixes
76
-
77
- - content-type of svg files ([#38](https://github.com/unjs/ipx/issues/38)) ([a7a1b3b](https://github.com/unjs/ipx/commit/a7a1b3b8fb3c1b996ec823d80d029a11a19b9311))
78
-
79
- ### [0.6.2](https://github.com/unjs/ipx/compare/v0.6.1...v0.6.2) (2021-06-29)
80
-
81
- ### Features
82
-
83
- - experimental animated support (ref [#35](https://github.com/unjs/ipx/issues/35)) ([d93fdfa](https://github.com/unjs/ipx/commit/d93fdfa1d591e70b89084a7f50d37343a7d68df8))
84
- - support id alias ([#32](https://github.com/unjs/ipx/issues/32)) ([d4356cf](https://github.com/unjs/ipx/commit/d4356cfc28f23000e3e25f597d49eb164da580b3))
85
- - **http:** use hostname for domain validation ([da5ca74](https://github.com/unjs/ipx/commit/da5ca74b0a57f5e47b1927f282fdda7228e54f58)), closes [nuxt/image#343](https://github.com/nuxt/image/issues/343)
86
-
87
- ### Bug Fixes
88
-
89
- - apply context modifiers first (resolves [#33](https://github.com/unjs/ipx/issues/33)) ([cf9effd](https://github.com/unjs/ipx/commit/cf9effd1f8b390c51507f2b18d2a69de921017fd))
90
- - default modifiers to empty object ([00d5c1d](https://github.com/unjs/ipx/commit/00d5c1d262a300469d24dc5a92c4a9940f2f0483))
91
-
92
- ### [0.6.1](https://github.com/unjs/ipx/compare/v0.6.0...v0.6.1) (2021-05-26)
93
-
94
- ## [0.6.0](https://github.com/unjs/ipx/compare/v0.5.8...v0.6.0) (2021-02-15)
95
-
96
- ### ⚠ BREAKING CHANGES
97
-
98
- - improved handlers and format support
99
-
100
- ### Features
101
-
102
- - improved handlers and format support ([f4f6e58](https://github.com/unjs/ipx/commit/f4f6e586119e5c9c7c81354277b42e2d3406bb96))
103
-
104
- ### [0.5.8](https://github.com/unjs/ipx/compare/v0.5.7...v0.5.8) (2021-02-08)
105
-
106
- ### Bug Fixes
107
-
108
- - **ipx:** handle when modifiers not provided ([4efebd8](https://github.com/unjs/ipx/commit/4efebd88963cfd054004810207874553e89e5d61))
109
-
110
- ### [0.5.7](https://github.com/unjs/ipx/compare/v0.5.6...v0.5.7) (2021-02-08)
111
-
112
- ### Bug Fixes
113
-
114
- - override meta.type and meta.mimeType if format modifier used ([820f1e2](https://github.com/unjs/ipx/commit/820f1e253dcbd0fe1122a742bb75dcfc364b868b))
115
-
116
- ### [0.5.6](https://github.com/unjs/ipx/compare/v0.5.5...v0.5.6) (2021-02-04)
117
-
118
- ### Bug Fixes
119
-
120
- - remove extra space ([6df3504](https://github.com/unjs/ipx/commit/6df350413d2cab1b4d4a4c9f8b8a92bd906cc8f5))
121
-
122
- ### [0.5.5](https://github.com/unjs/ipx/compare/v0.5.4...v0.5.5) (2021-02-04)
123
-
124
- ### Bug Fixes
125
-
126
- - add public and s-maxage ([bfd9727](https://github.com/unjs/ipx/commit/bfd9727ac867d0af390f56dd939347f5183d1763))
127
-
128
- ### [0.5.4](https://github.com/unjs/ipx/compare/v0.5.3...v0.5.4) (2021-02-04)
129
-
130
- ### Bug Fixes
131
-
132
- - **http:** user headers.get ([9cf5aeb](https://github.com/unjs/ipx/commit/9cf5aebaff8f8fe86014993ac4c91590bc5a6134))
133
-
134
- ### [0.5.3](https://github.com/unjs/ipx/compare/v0.5.2...v0.5.3) (2021-02-04)
135
-
136
- ### Bug Fixes
137
-
138
- - fix max-age cache header name ([833272b](https://github.com/unjs/ipx/commit/833272b6a4c63c388e941c8f037118c204a8dac4))
139
- - **types:** optional ipxOptions ([692ab1f](https://github.com/unjs/ipx/commit/692ab1f6c64fa86d77581bebdcabf0ba707b9469))
140
-
141
- ### [0.5.2](https://github.com/unjs/ipx/compare/v0.5.1...v0.5.2) (2021-02-03)
142
-
143
- ### Features
144
-
145
- - support meta, content-type and svg handling ([37592e7](https://github.com/unjs/ipx/commit/37592e711d166df41c29f1b1117adb186d42ce5d))
146
-
147
- ### Bug Fixes
148
-
149
- - only giveup svg if no format modifier set ([f5ce8b7](https://github.com/unjs/ipx/commit/f5ce8b7aecd18629b7a116dc6aecd5096d4573aa))
150
-
151
- ### [0.5.1](https://github.com/unjs/ipx/compare/v0.5.0...v0.5.1) (2021-02-03)
152
-
153
- ### Bug Fixes
154
-
155
- - **pkg:** export index.ts ([6125bbb](https://github.com/unjs/ipx/commit/6125bbb79ad430294f5d371d9a08f8ecca5c8372))
156
-
157
- ## [0.5.0](https://github.com/unjs/ipx/compare/v0.4.8...v0.5.0) (2021-02-03)
158
-
159
- ### ⚠ BREAKING CHANGES
160
-
161
- - rewrite ipx
162
-
163
- ### Features
164
-
165
- - rewrite ipx ([a60fb0d](https://github.com/unjs/ipx/commit/a60fb0d44b96c9f135af3295730c3da13fbc3e6c))
166
-
167
- ### [0.4.8](https://github.com/unjs/ipx/compare/v0.4.7...v0.4.8) (2020-12-23)
168
-
169
- ### Bug Fixes
170
-
171
- - update allowList import ([a26cae0](https://github.com/unjs/ipx/commit/a26cae00faa4fea7c190e3fb4efdf5fa1d137095))
172
-
173
- ### [0.4.7](https://github.com/unjs/ipx/compare/v0.4.6...v0.4.7) (2020-12-23)
174
-
175
- ### Bug Fixes
176
-
177
- - **pkg:** update exports ([584cfe4](https://github.com/unjs/ipx/commit/584cfe4c341da6e10a7da28a20afe6b4d9aeff0a))
178
-
179
- ### [0.4.6](https://github.com/unjs/ipx/compare/v0.4.5...v0.4.6) (2020-11-30)
180
-
181
- ### [0.4.5](https://github.com/unjs/ipx/compare/v0.4.4...v0.4.5) (2020-11-30)
182
-
183
- ### Bug Fixes
184
-
185
- - prevent unknow format error ([#18](https://github.com/unjs/ipx/issues/18)) ([3f338be](https://github.com/unjs/ipx/commit/3f338be630c76fd2d91901462cc3d5b495719882))
186
-
187
- ### [0.4.4](https://github.com/unjs/ipx/compare/v0.4.3...v0.4.4) (2020-11-27)
188
-
189
- ### Features
190
-
191
- - add background operation ([#16](https://github.com/unjs/ipx/issues/16)) ([b1a0178](https://github.com/unjs/ipx/commit/b1a0178c2522bba1361a8973bf338fe0ae1cab86))
192
-
193
- ### [0.4.3](https://github.com/unjs/ipx/compare/v0.4.2...v0.4.3) (2020-11-25)
194
-
195
- ### Features
196
-
197
- - allow gif images ([#15](https://github.com/unjs/ipx/issues/15)) ([51dcfc1](https://github.com/unjs/ipx/commit/51dcfc1dc0a076eca2c33ce5fcaf37b970964bca))
198
-
199
- ### [0.4.2](https://github.com/unjs/ipx/compare/v0.4.1...v0.4.2) (2020-11-18)
200
-
201
- ### Bug Fixes
202
-
203
- - support `HttpAgent` with `remote` input ([#14](https://github.com/unjs/ipx/issues/14)) ([699b671](https://github.com/unjs/ipx/commit/699b6717d1b6f817edb784d50cd5f2ce8da5d21a))
204
-
205
- ### [0.4.1](https://github.com/unjs/ipx/compare/v0.4.0...v0.4.1) (2020-11-12)
206
-
207
- ### Features
208
-
209
- - allow overiding `sharp.options` ([#13](https://github.com/unjs/ipx/issues/13)) ([ae7244d](https://github.com/unjs/ipx/commit/ae7244d83712d352e4fd08fa2106122aac6f2689))
210
-
211
- ## [0.4.0](https://github.com/unjs/ipx/compare/v0.4.0-rc.1...v0.4.0) (2020-11-05)
212
-
213
- ### Features
214
-
215
- - support svg files ([#9](https://github.com/unjs/ipx/issues/9)) ([a020904](https://github.com/unjs/ipx/commit/a02090436e0116de641fa3d415dfeae1bee79379))
216
-
217
- ### Bug Fixes
218
-
219
- - remove meta ([a490fb6](https://github.com/unjs/ipx/commit/a490fb6bb13a5f215a1ffb39b6acbf6d5de85aca))
220
- - support adapter on client ([4ffd7e8](https://github.com/unjs/ipx/commit/4ffd7e84553b4b13dbb15bee801d27d014b9dc08))
package/dist/cli.js DELETED
@@ -1,558 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- 'use strict';
4
-
5
- const consola = require('consola');
6
- const listhen = require('listhen');
7
- const Sharp = require('sharp');
8
- const defu = require('defu');
9
- const imageMeta = require('image-meta');
10
- const ufo = require('ufo');
11
- const path = require('path');
12
- const isValidPath = require('is-valid-path');
13
- const fsExtra = require('fs-extra');
14
- const destr = require('destr');
15
- const http = require('http');
16
- const https = require('https');
17
- const fetch = require('node-fetch');
18
- const getEtag = require('etag');
19
- const xss = require('xss');
20
-
21
- function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
22
-
23
- const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
24
- const Sharp__default = /*#__PURE__*/_interopDefaultLegacy(Sharp);
25
- const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
26
- const imageMeta__default = /*#__PURE__*/_interopDefaultLegacy(imageMeta);
27
- const isValidPath__default = /*#__PURE__*/_interopDefaultLegacy(isValidPath);
28
- const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
29
- const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
30
- const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
31
- const fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
32
- const getEtag__default = /*#__PURE__*/_interopDefaultLegacy(getEtag);
33
- const xss__default = /*#__PURE__*/_interopDefaultLegacy(xss);
34
-
35
- const Handlers = /*#__PURE__*/Object.freeze({
36
- __proto__: null,
37
- get quality () { return quality; },
38
- get fit () { return fit; },
39
- get background () { return background; },
40
- get enlarge () { return enlarge; },
41
- get width () { return width; },
42
- get height () { return height; },
43
- get resize () { return resize; },
44
- get trim () { return trim; },
45
- get extend () { return extend; },
46
- get extract () { return extract; },
47
- get rotate () { return rotate; },
48
- get flip () { return flip; },
49
- get flop () { return flop; },
50
- get sharpen () { return sharpen; },
51
- get median () { return median; },
52
- get blur () { return blur; },
53
- get flatten () { return flatten; },
54
- get gamma () { return gamma; },
55
- get negate () { return negate; },
56
- get normalize () { return normalize; },
57
- get threshold () { return threshold; },
58
- get modulate () { return modulate; },
59
- get tint () { return tint; },
60
- get grayscale () { return grayscale; },
61
- get crop () { return crop; },
62
- get q () { return q; },
63
- get b () { return b; },
64
- get w () { return w; },
65
- get h () { return h; },
66
- get s () { return s; }
67
- });
68
-
69
- function getEnv(name, defaultValue) {
70
- var _a;
71
- return (_a = destr__default['default'](process.env[name])) != null ? _a : defaultValue;
72
- }
73
- function cachedPromise(fn) {
74
- let p;
75
- return (...args) => {
76
- if (p) {
77
- return p;
78
- }
79
- p = Promise.resolve(fn(...args));
80
- return p;
81
- };
82
- }
83
- class IPXError extends Error {
84
- }
85
- function createError(message, statusCode) {
86
- const err = new IPXError(message);
87
- err.statusMessage = "IPX: " + message;
88
- err.statusCode = statusCode;
89
- return err;
90
- }
91
-
92
- const createFilesystemSource = (options) => {
93
- const rootDir = path.resolve(options.dir);
94
- return async (id) => {
95
- const fsPath = path.resolve(path.join(rootDir, id));
96
- if (!isValidPath__default['default'](id) || id.includes("..") || !fsPath.startsWith(rootDir)) {
97
- throw createError("Forbidden path:" + id, 403);
98
- }
99
- let stats;
100
- try {
101
- stats = await fsExtra.stat(fsPath);
102
- } catch (err) {
103
- if (err.code === "ENOENT") {
104
- throw createError("File not found: " + fsPath, 404);
105
- } else {
106
- throw createError("File access error for " + fsPath + ":" + err.code, 403);
107
- }
108
- }
109
- if (!stats.isFile()) {
110
- throw createError("Path should be a file: " + fsPath, 400);
111
- }
112
- return {
113
- mtime: stats.mtime,
114
- maxAge: options.maxAge || 300,
115
- getData: cachedPromise(() => fsExtra.readFile(fsPath))
116
- };
117
- };
118
- };
119
-
120
- const createHTTPSource = (options) => {
121
- const httpsAgent = new https__default['default'].Agent({ keepAlive: true });
122
- const httpAgent = new http__default['default'].Agent({ keepAlive: true });
123
- let domains = options.domains || [];
124
- if (typeof domains === "string") {
125
- domains = domains.split(",").map((s) => s.trim());
126
- }
127
- const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
128
- return async (id, reqOptions) => {
129
- const parsedUrl = ufo.parseURL(id, "https://");
130
- if (!parsedUrl.host) {
131
- throw createError("Hostname is missing: " + id, 403);
132
- }
133
- if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => parsedUrl.host === host)) {
134
- throw createError("Forbidden host: " + parsedUrl.host, 403);
135
- }
136
- const response = await fetch__default['default'](id, {
137
- agent: id.startsWith("https") ? httpsAgent : httpAgent
138
- });
139
- if (!response.ok) {
140
- throw createError(response.statusText || "fetch error", response.status || 500);
141
- }
142
- let maxAge = options.maxAge || 300;
143
- const _cacheControl = response.headers.get("cache-control");
144
- if (_cacheControl) {
145
- const m = _cacheControl.match(/max-age=(\d+)/);
146
- if (m && m[1]) {
147
- maxAge = parseInt(m[1]);
148
- }
149
- }
150
- let mtime;
151
- const _lastModified = response.headers.get("last-modified");
152
- if (_lastModified) {
153
- mtime = new Date(_lastModified);
154
- }
155
- return {
156
- mtime,
157
- maxAge,
158
- getData: cachedPromise(() => response.buffer())
159
- };
160
- };
161
- };
162
-
163
- function VArg(arg) {
164
- return destr__default['default'](arg);
165
- }
166
- function parseArgs(args, mappers) {
167
- const vargs = args.split("_");
168
- return mappers.map((v, i) => v(vargs[i]));
169
- }
170
- function getHandler(key) {
171
- return Handlers[key];
172
- }
173
- function applyHandler(ctx, pipe, handler, argsStr) {
174
- const args = handler.args ? parseArgs(argsStr, handler.args) : [];
175
- return handler.apply(ctx, pipe, ...args);
176
- }
177
- function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
178
- const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
179
- let { width, height } = desiredDimensions;
180
- if (width > sourceDimensions.width) {
181
- width = sourceDimensions.width;
182
- height = Math.round(sourceDimensions.width / desiredAspectRatio);
183
- }
184
- if (height > sourceDimensions.height) {
185
- height = sourceDimensions.height;
186
- width = Math.round(sourceDimensions.height * desiredAspectRatio);
187
- }
188
- return { width, height };
189
- }
190
-
191
- const quality = {
192
- args: [VArg],
193
- order: -1,
194
- apply: (context, _pipe, quality2) => {
195
- context.quality = quality2;
196
- }
197
- };
198
- const fit = {
199
- args: [VArg],
200
- order: -1,
201
- apply: (context, _pipe, fit2) => {
202
- context.fit = fit2;
203
- }
204
- };
205
- const HEX_RE = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
206
- const SHORTHEX_RE = /^([a-f\d])([a-f\d])([a-f\d])$/i;
207
- const background = {
208
- args: [VArg],
209
- order: -1,
210
- apply: (context, _pipe, background2) => {
211
- background2 = String(background2);
212
- if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
213
- background2 = "#" + background2;
214
- }
215
- context.background = background2;
216
- }
217
- };
218
- const enlarge = {
219
- args: [],
220
- apply: (context) => {
221
- context.enlarge = true;
222
- }
223
- };
224
- const width = {
225
- args: [VArg],
226
- apply: (context, pipe, width2) => {
227
- return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
228
- }
229
- };
230
- const height = {
231
- args: [VArg],
232
- apply: (context, pipe, height2) => {
233
- return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
234
- }
235
- };
236
- const resize = {
237
- args: [VArg, VArg, VArg],
238
- apply: (context, pipe, size) => {
239
- let [width2, height2] = String(size).split("x").map((v) => Number(v));
240
- if (!context.enlarge) {
241
- const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
242
- width2 = clamped.width;
243
- height2 = clamped.height;
244
- }
245
- return pipe.resize(width2, height2, {
246
- fit: context.fit,
247
- background: context.background
248
- });
249
- }
250
- };
251
- const trim = {
252
- args: [VArg],
253
- apply: (_context, pipe, threshold2) => {
254
- return pipe.trim(threshold2);
255
- }
256
- };
257
- const extend = {
258
- args: [VArg, VArg, VArg, VArg],
259
- apply: (context, pipe, top, right, bottom, left) => {
260
- return pipe.extend({
261
- top,
262
- left,
263
- bottom,
264
- right,
265
- background: context.background
266
- });
267
- }
268
- };
269
- const extract = {
270
- args: [VArg, VArg, VArg, VArg],
271
- apply: (context, pipe, top, right, bottom, left) => {
272
- return pipe.extend({
273
- top,
274
- left,
275
- bottom,
276
- right,
277
- background: context.background
278
- });
279
- }
280
- };
281
- const rotate = {
282
- args: [VArg],
283
- apply: (context, pipe, angel) => {
284
- return pipe.rotate(angel, {
285
- background: context.background
286
- });
287
- }
288
- };
289
- const flip = {
290
- args: [],
291
- apply: (_context, pipe) => {
292
- return pipe.flip();
293
- }
294
- };
295
- const flop = {
296
- args: [],
297
- apply: (_context, pipe) => {
298
- return pipe.flop();
299
- }
300
- };
301
- const sharpen = {
302
- args: [VArg, VArg, VArg],
303
- apply: (_context, pipe, sigma, flat, jagged) => {
304
- return pipe.sharpen(sigma, flat, jagged);
305
- }
306
- };
307
- const median = {
308
- args: [VArg, VArg, VArg],
309
- apply: (_context, pipe, size) => {
310
- return pipe.median(size);
311
- }
312
- };
313
- const blur = {
314
- args: [VArg, VArg, VArg],
315
- apply: (_context, pipe) => {
316
- return pipe.blur();
317
- }
318
- };
319
- const flatten = {
320
- args: [VArg, VArg, VArg],
321
- apply: (context, pipe) => {
322
- return pipe.flatten({
323
- background: context.background
324
- });
325
- }
326
- };
327
- const gamma = {
328
- args: [VArg, VArg, VArg],
329
- apply: (_context, pipe, gamma2, gammaOut) => {
330
- return pipe.gamma(gamma2, gammaOut);
331
- }
332
- };
333
- const negate = {
334
- args: [VArg, VArg, VArg],
335
- apply: (_context, pipe) => {
336
- return pipe.negate();
337
- }
338
- };
339
- const normalize = {
340
- args: [VArg, VArg, VArg],
341
- apply: (_context, pipe) => {
342
- return pipe.normalize();
343
- }
344
- };
345
- const threshold = {
346
- args: [VArg],
347
- apply: (_context, pipe, threshold2) => {
348
- return pipe.threshold(threshold2);
349
- }
350
- };
351
- const modulate = {
352
- args: [VArg],
353
- apply: (_context, pipe, brightness, saturation, hue) => {
354
- return pipe.modulate({
355
- brightness,
356
- saturation,
357
- hue
358
- });
359
- }
360
- };
361
- const tint = {
362
- args: [VArg],
363
- apply: (_context, pipe, rgb) => {
364
- return pipe.tint(rgb);
365
- }
366
- };
367
- const grayscale = {
368
- args: [VArg],
369
- apply: (_context, pipe) => {
370
- return pipe.grayscale();
371
- }
372
- };
373
- const crop = extract;
374
- const q = quality;
375
- const b = background;
376
- const w = width;
377
- const h = height;
378
- const s = resize;
379
-
380
- const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff"];
381
- function createIPX(userOptions) {
382
- const defaults = {
383
- dir: getEnv("IPX_DIR", "."),
384
- domains: getEnv("IPX_DOMAINS", []),
385
- alias: getEnv("IPX_ALIAS", {}),
386
- sharp: {}
387
- };
388
- const options = defu__default['default'](userOptions, defaults);
389
- options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [ufo.withLeadingSlash(e[0]), e[1]]));
390
- const ctx = {
391
- sources: {}
392
- };
393
- if (options.dir) {
394
- ctx.sources.filesystem = createFilesystemSource({
395
- dir: options.dir
396
- });
397
- }
398
- if (options.domains) {
399
- ctx.sources.http = createHTTPSource({
400
- domains: options.domains
401
- });
402
- }
403
- return function ipx(id, modifiers = {}, reqOptions = {}) {
404
- if (!id) {
405
- throw createError("resource id is missing", 400);
406
- }
407
- id = ufo.hasProtocol(id) ? id : ufo.withLeadingSlash(id);
408
- for (const base in options.alias) {
409
- if (id.startsWith(base)) {
410
- id = ufo.joinURL(options.alias[base], id.substr(base.length));
411
- }
412
- }
413
- const getSrc = cachedPromise(() => {
414
- const source = ufo.hasProtocol(id) ? "http" : "filesystem";
415
- if (!ctx.sources[source]) {
416
- throw createError("Unknown source: " + source, 400);
417
- }
418
- return ctx.sources[source](id, reqOptions);
419
- });
420
- const getData = cachedPromise(async () => {
421
- const src = await getSrc();
422
- const data = await src.getData();
423
- const meta = imageMeta__default['default'](data);
424
- const mFormat = modifiers.f || modifiers.format;
425
- let format = mFormat || meta.type;
426
- if (format === "jpg") {
427
- format = "jpeg";
428
- }
429
- if (meta.type === "svg" && !mFormat) {
430
- return {
431
- data,
432
- format: "svg+xml",
433
- meta
434
- };
435
- }
436
- const animated = modifiers.animated !== void 0 || modifiers.a !== void 0;
437
- if (animated) {
438
- format = "webp";
439
- }
440
- let sharp = Sharp__default['default'](data, { animated });
441
- Object.assign(sharp.options, options.sharp);
442
- const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
443
- const aKey = (a.handler.order || a.name || "").toString();
444
- const bKey = (b.handler.order || b.name || "").toString();
445
- return aKey.localeCompare(bKey);
446
- });
447
- const handlerCtx = { meta };
448
- for (const h of handlers) {
449
- sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
450
- }
451
- if (SUPPORTED_FORMATS.includes(format)) {
452
- sharp = sharp.toFormat(format, {
453
- quality: handlerCtx.quality,
454
- progressive: format === "jpeg"
455
- });
456
- }
457
- const newData = await sharp.toBuffer();
458
- return {
459
- data: newData,
460
- format,
461
- meta
462
- };
463
- });
464
- return {
465
- src: getSrc,
466
- data: getData
467
- };
468
- };
469
- }
470
-
471
- async function _handleRequest(req, ipx) {
472
- const res = {
473
- statusCode: 200,
474
- statusMessage: "",
475
- headers: {},
476
- body: ""
477
- };
478
- const [modifiersStr = "", ...idSegments] = req.url.substr(1).split("/");
479
- const id = ufo.decode(idSegments.join("/"));
480
- if (!modifiersStr) {
481
- throw createError("Modifiers is missing in path: " + req.url, 400);
482
- }
483
- if (!id || id === "/") {
484
- throw createError("Resource id is missing: " + req.url, 400);
485
- }
486
- const modifiers = Object.create(null);
487
- if (modifiersStr !== "_") {
488
- for (const p of modifiersStr.split(",")) {
489
- const [key, value = ""] = p.split("_");
490
- modifiers[key] = ufo.decode(value);
491
- }
492
- }
493
- const img = ipx(id, modifiers, req.options);
494
- const src = await img.src();
495
- if (src.mtime) {
496
- if (req.headers["if-modified-since"]) {
497
- if (new Date(req.headers["if-modified-since"]) >= src.mtime) {
498
- res.statusCode = 304;
499
- return res;
500
- }
501
- }
502
- res.headers["Last-Modified"] = +src.mtime + "";
503
- }
504
- if (src.maxAge !== void 0) {
505
- res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
506
- }
507
- const { data, format } = await img.data();
508
- const etag = getEtag__default['default'](data);
509
- res.headers.ETag = etag;
510
- if (etag && req.headers["if-none-match"] === etag) {
511
- res.statusCode = 304;
512
- return res;
513
- }
514
- if (format) {
515
- res.headers["Content-Type"] = `image/${format}`;
516
- }
517
- res.body = data;
518
- return res;
519
- }
520
- function handleRequest(req, ipx) {
521
- return _handleRequest(req, ipx).catch((err) => {
522
- const statusCode = parseInt(err.statusCode) || 500;
523
- const statusMessage = err.statusMessage ? xss__default['default'](err.statusMessage) : `IPX Error (${statusCode})`;
524
- if (process.env.NODE_ENV !== "production" && statusCode === 500) {
525
- console.error(err);
526
- }
527
- return {
528
- statusCode,
529
- statusMessage,
530
- body: statusMessage,
531
- headers: {}
532
- };
533
- });
534
- }
535
- function createIPXMiddleware(ipx) {
536
- return function IPXMiddleware(req, res) {
537
- handleRequest({ url: req.url, headers: req.headers }, ipx).then((_res) => {
538
- res.statusCode = _res.statusCode;
539
- res.statusMessage = _res.statusMessage;
540
- for (const name in _res.headers) {
541
- res.setHeader(name, _res.headers[name]);
542
- }
543
- res.end(_res.body);
544
- });
545
- };
546
- }
547
-
548
- async function main() {
549
- const ipx = createIPX({});
550
- const middleware = createIPXMiddleware(ipx);
551
- await listhen.listen(middleware, {
552
- clipboard: false
553
- });
554
- }
555
- main().catch((err) => {
556
- consola__default['default'].error(err);
557
- process.exit(1);
558
- });