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 +2 -2
- package/dist/index.cjs +23 -27
- package/dist/index.mjs +11 -12
- package/package.json +10 -11
- package/CHANGELOG.md +0 -220
- package/dist/cli.js +0 -558
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 :
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
85
|
+
const rootDir = pathe.resolve(options.dir);
|
|
91
86
|
return async (id) => {
|
|
92
|
-
const fsPath =
|
|
93
|
-
if (!isValidPath__default
|
|
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
|
|
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(() =>
|
|
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
|
|
119
|
-
const httpAgent = new http__default
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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 '
|
|
10
|
+
import { fetch } from 'ohmyfetch';
|
|
12
11
|
import getEtag from 'etag';
|
|
13
12
|
import xss from 'xss';
|
|
14
13
|
|
|
15
|
-
|
|
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
|
-
|
|
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 (!
|
|
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.
|
|
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": "
|
|
20
|
+
"build": "unbuild",
|
|
21
21
|
"dev": "nodemon",
|
|
22
22
|
"lint": "eslint --ext .ts .",
|
|
23
|
-
"
|
|
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
|
-
"
|
|
34
|
-
"image-meta": "^0.0.1",
|
|
33
|
+
"image-meta": "^0.1.1",
|
|
35
34
|
"is-valid-path": "^0.1.1",
|
|
36
|
-
"listhen": "^0.2.
|
|
37
|
-
"
|
|
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.
|
|
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
|
-
});
|