ipx 0.7.0 → 0.8.1
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 +23 -21
- package/dist/cli.js +41 -12
- package/dist/index.cjs +41 -12
- package/dist/index.mjs +41 -12
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -46,31 +46,33 @@ Keep original format (`png`) and set width to `200`:
|
|
|
46
46
|
|
|
47
47
|
`http://localhost:3000/w_200/static/buffalo.png`
|
|
48
48
|
|
|
49
|
-
Resize to `
|
|
49
|
+
Resize to `200x200px` using `embed` method and change format to `webp`:
|
|
50
50
|
|
|
51
|
-
`http://localhost:3000/embed,f_webp,
|
|
51
|
+
`http://localhost:3000/embed,f_webp,s_200x200/static/buffalo.png`
|
|
52
52
|
|
|
53
53
|
### Modifiers
|
|
54
54
|
|
|
55
|
-
| Property
|
|
56
|
-
|
|
|
57
|
-
| width
|
|
58
|
-
| height
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
55
|
+
| Property | Docs | Example | Comments |
|
|
56
|
+
| ----------- | :-------------------------------------------------------------- | :------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| width / w | \_ | `http://localhost:3000/width_200/buffalo.png` |
|
|
58
|
+
| height / h | \_ | `http://localhost:3000/height_200/buffalo.png` |
|
|
59
|
+
| resize / s | \_ | `http://localhost:3000/s_200x200/buffalo.png` |
|
|
60
|
+
| trim | [Docs](https://sharp.pixelplumbing.com/api-resize#trim) | `http://localhost:3000/trim_100/buffalo.png` |
|
|
61
|
+
| format | [Docs](https://sharp.pixelplumbing.com/api-output#toformat) | `http://localhost:3000/format_webp/buffalo.png` | Supported format: `jpg`, `jpeg`, `png`, `webp`, `avif`, `gif`, `heif` |
|
|
62
|
+
| quality / q | \_ | `http://localhost:3000/quality_50/buffalo.png` | Accepted values: 0 to 100 |
|
|
63
|
+
| rotate | [Docs](https://sharp.pixelplumbing.com/api-operation#rotate) | `http://localhost:3000/rotate_45/buffalo.png` |
|
|
64
|
+
| enlarge | \_ | `http://localhost:3000/enlarge,s_2000x2000/buffalo.png` | Allow the image to be upscaled. By default the returned image will never be larger than the source in any dimension, while preserving the requested aspect ratio. |
|
|
65
|
+
| flip | [Docs](https://sharp.pixelplumbing.com/api-operation#flip) | `http://localhost:3000/flip/buffalo.png` |
|
|
66
|
+
| flop | [Docs](https://sharp.pixelplumbing.com/api-operation#flop) | `http://localhost:3000/flop/buffalo.png` |
|
|
67
|
+
| sharpen | [Docs](https://sharp.pixelplumbing.com/api-operation#sharpen) | `http://localhost:3000/sharpen_30/buffalo.png` |
|
|
68
|
+
| median | [Docs](https://sharp.pixelplumbing.com/api-operation#median) | `http://localhost:3000/median_10/buffalo.png` |
|
|
69
|
+
| gamma | [Docs](https://sharp.pixelplumbing.com/api-operation#gamma) | `http://localhost:3000/gamma_3/buffalo.png` |
|
|
70
|
+
| negate | [Docs](https://sharp.pixelplumbing.com/api-operation#negate) | `http://localhost:3000/negate/buffalo.png` |
|
|
71
|
+
| normalize | [Docs](https://sharp.pixelplumbing.com/api-operation#normalize) | `http://localhost:3000/normalize/buffalo.png` |
|
|
72
|
+
| threshold | [Docs](https://sharp.pixelplumbing.com/api-operation#threshold) | `http://localhost:3000/threshold_10/buffalo.png` |
|
|
73
|
+
| tint | [Docs](https://sharp.pixelplumbing.com/api-colour#tint) | `http://localhost:3000/tint_1098123/buffalo.png` |
|
|
74
|
+
| grayscale | [Docs](https://sharp.pixelplumbing.com/api-colour#grayscale) | `http://localhost:3000/grayscale/buffalo.png` |
|
|
75
|
+
| animated | - | `http://localhost:3000/animated/buffalo.gif` | Experimental |
|
|
74
76
|
|
|
75
77
|
### Config
|
|
76
78
|
|
package/dist/cli.js
CHANGED
|
@@ -37,6 +37,7 @@ const Handlers = /*#__PURE__*/Object.freeze({
|
|
|
37
37
|
get quality () { return quality; },
|
|
38
38
|
get fit () { return fit; },
|
|
39
39
|
get background () { return background; },
|
|
40
|
+
get enlarge () { return enlarge; },
|
|
40
41
|
get width () { return width; },
|
|
41
42
|
get height () { return height; },
|
|
42
43
|
get resize () { return resize; },
|
|
@@ -125,12 +126,12 @@ const createHTTPSource = (options) => {
|
|
|
125
126
|
}
|
|
126
127
|
const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
|
|
127
128
|
return async (id, reqOptions) => {
|
|
128
|
-
const
|
|
129
|
-
if (!
|
|
129
|
+
const url = new URL(id);
|
|
130
|
+
if (!url.hostname) {
|
|
130
131
|
throw createError("Hostname is missing: " + id, 403);
|
|
131
132
|
}
|
|
132
|
-
if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) =>
|
|
133
|
-
throw createError("Forbidden host: " +
|
|
133
|
+
if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => url.hostname === host)) {
|
|
134
|
+
throw createError("Forbidden host: " + url.hostname, 403);
|
|
134
135
|
}
|
|
135
136
|
const response = await fetch__default['default'](id, {
|
|
136
137
|
agent: id.startsWith("https") ? httpsAgent : httpAgent
|
|
@@ -173,6 +174,19 @@ function applyHandler(ctx, pipe, handler, argsStr) {
|
|
|
173
174
|
const args = handler.args ? parseArgs(argsStr, handler.args) : [];
|
|
174
175
|
return handler.apply(ctx, pipe, ...args);
|
|
175
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
|
+
}
|
|
176
190
|
|
|
177
191
|
const quality = {
|
|
178
192
|
args: [VArg],
|
|
@@ -194,27 +208,40 @@ const background = {
|
|
|
194
208
|
args: [VArg],
|
|
195
209
|
order: -1,
|
|
196
210
|
apply: (context, _pipe, background2) => {
|
|
211
|
+
background2 = String(background2);
|
|
197
212
|
if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
|
|
198
213
|
background2 = "#" + background2;
|
|
199
214
|
}
|
|
200
215
|
context.background = background2;
|
|
201
216
|
}
|
|
202
217
|
};
|
|
218
|
+
const enlarge = {
|
|
219
|
+
args: [],
|
|
220
|
+
apply: (context) => {
|
|
221
|
+
context.enlarge = true;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
203
224
|
const width = {
|
|
204
225
|
args: [VArg],
|
|
205
|
-
apply: (
|
|
206
|
-
return pipe.resize(width2, null);
|
|
226
|
+
apply: (context, pipe, width2) => {
|
|
227
|
+
return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
|
|
207
228
|
}
|
|
208
229
|
};
|
|
209
230
|
const height = {
|
|
210
231
|
args: [VArg],
|
|
211
|
-
apply: (
|
|
212
|
-
return pipe.resize(null, height2);
|
|
232
|
+
apply: (context, pipe, height2) => {
|
|
233
|
+
return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
|
|
213
234
|
}
|
|
214
235
|
};
|
|
215
236
|
const resize = {
|
|
216
237
|
args: [VArg, VArg, VArg],
|
|
217
|
-
apply: (context, pipe,
|
|
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
|
+
}
|
|
218
245
|
return pipe.resize(width2, height2, {
|
|
219
246
|
fit: context.fit,
|
|
220
247
|
background: context.background
|
|
@@ -253,8 +280,10 @@ const extract = {
|
|
|
253
280
|
};
|
|
254
281
|
const rotate = {
|
|
255
282
|
args: [VArg],
|
|
256
|
-
apply: (
|
|
257
|
-
return pipe.rotate(angel
|
|
283
|
+
apply: (context, pipe, angel) => {
|
|
284
|
+
return pipe.rotate(angel, {
|
|
285
|
+
background: context.background
|
|
286
|
+
});
|
|
258
287
|
}
|
|
259
288
|
};
|
|
260
289
|
const flip = {
|
|
@@ -415,7 +444,7 @@ function createIPX(userOptions) {
|
|
|
415
444
|
const bKey = (b.handler.order || b.name || "").toString();
|
|
416
445
|
return aKey.localeCompare(bKey);
|
|
417
446
|
});
|
|
418
|
-
const handlerCtx = {};
|
|
447
|
+
const handlerCtx = { meta };
|
|
419
448
|
for (const h of handlers) {
|
|
420
449
|
sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
|
|
421
450
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,7 @@ const Handlers = /*#__PURE__*/Object.freeze({
|
|
|
34
34
|
get quality () { return quality; },
|
|
35
35
|
get fit () { return fit; },
|
|
36
36
|
get background () { return background; },
|
|
37
|
+
get enlarge () { return enlarge; },
|
|
37
38
|
get width () { return width; },
|
|
38
39
|
get height () { return height; },
|
|
39
40
|
get resize () { return resize; },
|
|
@@ -122,12 +123,12 @@ const createHTTPSource = (options) => {
|
|
|
122
123
|
}
|
|
123
124
|
const hosts = domains.map((domain) => ufo.parseURL(domain, "https://").host);
|
|
124
125
|
return async (id, reqOptions) => {
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
126
|
+
const url = new URL(id);
|
|
127
|
+
if (!url.hostname) {
|
|
127
128
|
throw createError("Hostname is missing: " + id, 403);
|
|
128
129
|
}
|
|
129
|
-
if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) =>
|
|
130
|
-
throw createError("Forbidden host: " +
|
|
130
|
+
if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => url.hostname === host)) {
|
|
131
|
+
throw createError("Forbidden host: " + url.hostname, 403);
|
|
131
132
|
}
|
|
132
133
|
const response = await fetch__default['default'](id, {
|
|
133
134
|
agent: id.startsWith("https") ? httpsAgent : httpAgent
|
|
@@ -170,6 +171,19 @@ function applyHandler(ctx, pipe, handler, argsStr) {
|
|
|
170
171
|
const args = handler.args ? parseArgs(argsStr, handler.args) : [];
|
|
171
172
|
return handler.apply(ctx, pipe, ...args);
|
|
172
173
|
}
|
|
174
|
+
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
175
|
+
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
176
|
+
let { width, height } = desiredDimensions;
|
|
177
|
+
if (width > sourceDimensions.width) {
|
|
178
|
+
width = sourceDimensions.width;
|
|
179
|
+
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
180
|
+
}
|
|
181
|
+
if (height > sourceDimensions.height) {
|
|
182
|
+
height = sourceDimensions.height;
|
|
183
|
+
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
184
|
+
}
|
|
185
|
+
return { width, height };
|
|
186
|
+
}
|
|
173
187
|
|
|
174
188
|
const quality = {
|
|
175
189
|
args: [VArg],
|
|
@@ -191,27 +205,40 @@ const background = {
|
|
|
191
205
|
args: [VArg],
|
|
192
206
|
order: -1,
|
|
193
207
|
apply: (context, _pipe, background2) => {
|
|
208
|
+
background2 = String(background2);
|
|
194
209
|
if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
|
|
195
210
|
background2 = "#" + background2;
|
|
196
211
|
}
|
|
197
212
|
context.background = background2;
|
|
198
213
|
}
|
|
199
214
|
};
|
|
215
|
+
const enlarge = {
|
|
216
|
+
args: [],
|
|
217
|
+
apply: (context) => {
|
|
218
|
+
context.enlarge = true;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
200
221
|
const width = {
|
|
201
222
|
args: [VArg],
|
|
202
|
-
apply: (
|
|
203
|
-
return pipe.resize(width2, null);
|
|
223
|
+
apply: (context, pipe, width2) => {
|
|
224
|
+
return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
|
|
204
225
|
}
|
|
205
226
|
};
|
|
206
227
|
const height = {
|
|
207
228
|
args: [VArg],
|
|
208
|
-
apply: (
|
|
209
|
-
return pipe.resize(null, height2);
|
|
229
|
+
apply: (context, pipe, height2) => {
|
|
230
|
+
return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
|
|
210
231
|
}
|
|
211
232
|
};
|
|
212
233
|
const resize = {
|
|
213
234
|
args: [VArg, VArg, VArg],
|
|
214
|
-
apply: (context, pipe,
|
|
235
|
+
apply: (context, pipe, size) => {
|
|
236
|
+
let [width2, height2] = String(size).split("x").map((v) => Number(v));
|
|
237
|
+
if (!context.enlarge) {
|
|
238
|
+
const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
|
|
239
|
+
width2 = clamped.width;
|
|
240
|
+
height2 = clamped.height;
|
|
241
|
+
}
|
|
215
242
|
return pipe.resize(width2, height2, {
|
|
216
243
|
fit: context.fit,
|
|
217
244
|
background: context.background
|
|
@@ -250,8 +277,10 @@ const extract = {
|
|
|
250
277
|
};
|
|
251
278
|
const rotate = {
|
|
252
279
|
args: [VArg],
|
|
253
|
-
apply: (
|
|
254
|
-
return pipe.rotate(angel
|
|
280
|
+
apply: (context, pipe, angel) => {
|
|
281
|
+
return pipe.rotate(angel, {
|
|
282
|
+
background: context.background
|
|
283
|
+
});
|
|
255
284
|
}
|
|
256
285
|
};
|
|
257
286
|
const flip = {
|
|
@@ -412,7 +441,7 @@ function createIPX(userOptions) {
|
|
|
412
441
|
const bKey = (b.handler.order || b.name || "").toString();
|
|
413
442
|
return aKey.localeCompare(bKey);
|
|
414
443
|
});
|
|
415
|
-
const handlerCtx = {};
|
|
444
|
+
const handlerCtx = { meta };
|
|
416
445
|
for (const h of handlers) {
|
|
417
446
|
sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
|
|
418
447
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -17,6 +17,7 @@ var Handlers = /*#__PURE__*/Object.freeze({
|
|
|
17
17
|
get quality () { return quality; },
|
|
18
18
|
get fit () { return fit; },
|
|
19
19
|
get background () { return background; },
|
|
20
|
+
get enlarge () { return enlarge; },
|
|
20
21
|
get width () { return width; },
|
|
21
22
|
get height () { return height; },
|
|
22
23
|
get resize () { return resize; },
|
|
@@ -105,12 +106,12 @@ const createHTTPSource = (options) => {
|
|
|
105
106
|
}
|
|
106
107
|
const hosts = domains.map((domain) => parseURL(domain, "https://").host);
|
|
107
108
|
return async (id, reqOptions) => {
|
|
108
|
-
const
|
|
109
|
-
if (!
|
|
109
|
+
const url = new URL(id);
|
|
110
|
+
if (!url.hostname) {
|
|
110
111
|
throw createError("Hostname is missing: " + id, 403);
|
|
111
112
|
}
|
|
112
|
-
if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) =>
|
|
113
|
-
throw createError("Forbidden host: " +
|
|
113
|
+
if (!(reqOptions == null ? void 0 : reqOptions.bypassDomain) && !hosts.find((host) => url.hostname === host)) {
|
|
114
|
+
throw createError("Forbidden host: " + url.hostname, 403);
|
|
114
115
|
}
|
|
115
116
|
const response = await fetch(id, {
|
|
116
117
|
agent: id.startsWith("https") ? httpsAgent : httpAgent
|
|
@@ -153,6 +154,19 @@ function applyHandler(ctx, pipe, handler, argsStr) {
|
|
|
153
154
|
const args = handler.args ? parseArgs(argsStr, handler.args) : [];
|
|
154
155
|
return handler.apply(ctx, pipe, ...args);
|
|
155
156
|
}
|
|
157
|
+
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
158
|
+
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
159
|
+
let { width, height } = desiredDimensions;
|
|
160
|
+
if (width > sourceDimensions.width) {
|
|
161
|
+
width = sourceDimensions.width;
|
|
162
|
+
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
163
|
+
}
|
|
164
|
+
if (height > sourceDimensions.height) {
|
|
165
|
+
height = sourceDimensions.height;
|
|
166
|
+
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
167
|
+
}
|
|
168
|
+
return { width, height };
|
|
169
|
+
}
|
|
156
170
|
|
|
157
171
|
const quality = {
|
|
158
172
|
args: [VArg],
|
|
@@ -174,27 +188,40 @@ const background = {
|
|
|
174
188
|
args: [VArg],
|
|
175
189
|
order: -1,
|
|
176
190
|
apply: (context, _pipe, background2) => {
|
|
191
|
+
background2 = String(background2);
|
|
177
192
|
if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
|
|
178
193
|
background2 = "#" + background2;
|
|
179
194
|
}
|
|
180
195
|
context.background = background2;
|
|
181
196
|
}
|
|
182
197
|
};
|
|
198
|
+
const enlarge = {
|
|
199
|
+
args: [],
|
|
200
|
+
apply: (context) => {
|
|
201
|
+
context.enlarge = true;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
183
204
|
const width = {
|
|
184
205
|
args: [VArg],
|
|
185
|
-
apply: (
|
|
186
|
-
return pipe.resize(width2, null);
|
|
206
|
+
apply: (context, pipe, width2) => {
|
|
207
|
+
return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
|
|
187
208
|
}
|
|
188
209
|
};
|
|
189
210
|
const height = {
|
|
190
211
|
args: [VArg],
|
|
191
|
-
apply: (
|
|
192
|
-
return pipe.resize(null, height2);
|
|
212
|
+
apply: (context, pipe, height2) => {
|
|
213
|
+
return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
|
|
193
214
|
}
|
|
194
215
|
};
|
|
195
216
|
const resize = {
|
|
196
217
|
args: [VArg, VArg, VArg],
|
|
197
|
-
apply: (context, pipe,
|
|
218
|
+
apply: (context, pipe, size) => {
|
|
219
|
+
let [width2, height2] = String(size).split("x").map((v) => Number(v));
|
|
220
|
+
if (!context.enlarge) {
|
|
221
|
+
const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
|
|
222
|
+
width2 = clamped.width;
|
|
223
|
+
height2 = clamped.height;
|
|
224
|
+
}
|
|
198
225
|
return pipe.resize(width2, height2, {
|
|
199
226
|
fit: context.fit,
|
|
200
227
|
background: context.background
|
|
@@ -233,8 +260,10 @@ const extract = {
|
|
|
233
260
|
};
|
|
234
261
|
const rotate = {
|
|
235
262
|
args: [VArg],
|
|
236
|
-
apply: (
|
|
237
|
-
return pipe.rotate(angel
|
|
263
|
+
apply: (context, pipe, angel) => {
|
|
264
|
+
return pipe.rotate(angel, {
|
|
265
|
+
background: context.background
|
|
266
|
+
});
|
|
238
267
|
}
|
|
239
268
|
};
|
|
240
269
|
const flip = {
|
|
@@ -395,7 +424,7 @@ function createIPX(userOptions) {
|
|
|
395
424
|
const bKey = (b.handler.order || b.name || "").toString();
|
|
396
425
|
return aKey.localeCompare(bKey);
|
|
397
426
|
});
|
|
398
|
-
const handlerCtx = {};
|
|
427
|
+
const handlerCtx = { meta };
|
|
399
428
|
for (const h of handlers) {
|
|
400
429
|
sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
|
|
401
430
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"is-valid-path": "^0.1.1",
|
|
36
36
|
"listhen": "^0.2.4",
|
|
37
37
|
"node-fetch": "^2.6.1",
|
|
38
|
-
"sharp": "^0.
|
|
39
|
-
"ufo": "^0.7.
|
|
38
|
+
"sharp": "^0.29.0",
|
|
39
|
+
"ufo": "^0.7.9",
|
|
40
40
|
"xss": "^1.0.9"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|