ipx 0.9.3 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/dist/chunks/middleware.cjs +589 -0
- package/dist/chunks/middleware.mjs +70 -38
- package/dist/cli.cjs +32 -0
- package/dist/cli.mjs +0 -1
- package/dist/index.cjs +22 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.mjs +0 -1
- package/package.json +25 -26
package/LICENSE
CHANGED
|
File without changes
|
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const defu = require('defu');
|
|
4
|
+
const imageMeta = require('image-meta');
|
|
5
|
+
const ufo = require('ufo');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const pathe = require('pathe');
|
|
8
|
+
const http = require('http');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const ohmyfetch = require('ohmyfetch');
|
|
11
|
+
const destr = require('destr');
|
|
12
|
+
const getEtag = require('etag');
|
|
13
|
+
const xss = require('xss');
|
|
14
|
+
|
|
15
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
16
|
+
|
|
17
|
+
const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
|
|
18
|
+
const http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
|
19
|
+
const https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
|
20
|
+
const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);
|
|
21
|
+
const getEtag__default = /*#__PURE__*/_interopDefaultLegacy(getEtag);
|
|
22
|
+
const xss__default = /*#__PURE__*/_interopDefaultLegacy(xss);
|
|
23
|
+
|
|
24
|
+
const Handlers = {
|
|
25
|
+
__proto__: null,
|
|
26
|
+
get quality () { return quality; },
|
|
27
|
+
get fit () { return fit; },
|
|
28
|
+
get position () { return position; },
|
|
29
|
+
get background () { return background; },
|
|
30
|
+
get enlarge () { return enlarge; },
|
|
31
|
+
get width () { return width; },
|
|
32
|
+
get height () { return height; },
|
|
33
|
+
get resize () { return resize; },
|
|
34
|
+
get trim () { return trim; },
|
|
35
|
+
get extend () { return extend; },
|
|
36
|
+
get extract () { return extract; },
|
|
37
|
+
get rotate () { return rotate; },
|
|
38
|
+
get flip () { return flip; },
|
|
39
|
+
get flop () { return flop; },
|
|
40
|
+
get sharpen () { return sharpen; },
|
|
41
|
+
get median () { return median; },
|
|
42
|
+
get blur () { return blur; },
|
|
43
|
+
get flatten () { return flatten; },
|
|
44
|
+
get gamma () { return gamma; },
|
|
45
|
+
get negate () { return negate; },
|
|
46
|
+
get normalize () { return normalize; },
|
|
47
|
+
get threshold () { return threshold; },
|
|
48
|
+
get modulate () { return modulate; },
|
|
49
|
+
get tint () { return tint; },
|
|
50
|
+
get grayscale () { return grayscale; },
|
|
51
|
+
get crop () { return crop; },
|
|
52
|
+
get q () { return q; },
|
|
53
|
+
get b () { return b; },
|
|
54
|
+
get w () { return w; },
|
|
55
|
+
get h () { return h; },
|
|
56
|
+
get s () { return s; },
|
|
57
|
+
get pos () { return pos; }
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function getEnv(name, defaultValue) {
|
|
61
|
+
return destr__default(process.env[name]) ?? defaultValue;
|
|
62
|
+
}
|
|
63
|
+
function cachedPromise(fn) {
|
|
64
|
+
let p;
|
|
65
|
+
return (...args) => {
|
|
66
|
+
if (p) {
|
|
67
|
+
return p;
|
|
68
|
+
}
|
|
69
|
+
p = Promise.resolve(fn(...args));
|
|
70
|
+
return p;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
class IPXError extends Error {
|
|
74
|
+
}
|
|
75
|
+
function createError(statusMessage, statusCode, trace) {
|
|
76
|
+
const err = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
|
|
77
|
+
err.statusMessage = "IPX: " + statusMessage;
|
|
78
|
+
err.statusCode = statusCode;
|
|
79
|
+
return err;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const createFilesystemSource = (options) => {
|
|
83
|
+
const rootDir = pathe.resolve(options.dir);
|
|
84
|
+
return async (id) => {
|
|
85
|
+
const fsPath = pathe.resolve(pathe.join(rootDir, id));
|
|
86
|
+
if (!isValidPath(fsPath) || !fsPath.startsWith(rootDir)) {
|
|
87
|
+
throw createError("Forbidden path", 403, id);
|
|
88
|
+
}
|
|
89
|
+
let stats;
|
|
90
|
+
try {
|
|
91
|
+
stats = await fs.promises.stat(fsPath);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err.code === "ENOENT") {
|
|
94
|
+
throw createError("File not found", 404, fsPath);
|
|
95
|
+
} else {
|
|
96
|
+
throw createError("File access error " + err.code, 403, fsPath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!stats.isFile()) {
|
|
100
|
+
throw createError("Path should be a file", 400, fsPath);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
mtime: stats.mtime,
|
|
104
|
+
maxAge: options.maxAge,
|
|
105
|
+
getData: cachedPromise(() => fs.promises.readFile(fsPath))
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const isWindows = process.platform === "win32";
|
|
110
|
+
function isValidPath(fp) {
|
|
111
|
+
if (isWindows) {
|
|
112
|
+
fp = fp.slice(pathe.parse(fp).root.length);
|
|
113
|
+
}
|
|
114
|
+
if (/[<>:"|?*]/.test(fp)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const createHTTPSource = (options) => {
|
|
121
|
+
const httpsAgent = new https__default.Agent({ keepAlive: true });
|
|
122
|
+
const httpAgent = new http__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 url = new URL(id);
|
|
130
|
+
if (!url.hostname) {
|
|
131
|
+
throw createError("Hostname is missing", 403, id);
|
|
132
|
+
}
|
|
133
|
+
if (!reqOptions?.bypassDomain && !hosts.find((host) => url.hostname === host)) {
|
|
134
|
+
throw createError("Forbidden host", 403, url.hostname);
|
|
135
|
+
}
|
|
136
|
+
const response = await ohmyfetch.fetch(id, {
|
|
137
|
+
agent: id.startsWith("https") ? httpsAgent : httpAgent,
|
|
138
|
+
...options.fetchOptions
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
throw createError("Fetch error", response.status || 500, response.statusText);
|
|
142
|
+
}
|
|
143
|
+
let maxAge = options.maxAge;
|
|
144
|
+
const _cacheControl = response.headers.get("cache-control");
|
|
145
|
+
if (_cacheControl) {
|
|
146
|
+
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
147
|
+
if (m && m[1]) {
|
|
148
|
+
maxAge = parseInt(m[1]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
let mtime;
|
|
152
|
+
const _lastModified = response.headers.get("last-modified");
|
|
153
|
+
if (_lastModified) {
|
|
154
|
+
mtime = new Date(_lastModified);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
mtime,
|
|
158
|
+
maxAge,
|
|
159
|
+
getData: cachedPromise(() => response.arrayBuffer().then((ab) => Buffer.from(ab)))
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
function VArg(arg) {
|
|
165
|
+
return destr__default(arg);
|
|
166
|
+
}
|
|
167
|
+
function parseArgs(args, mappers) {
|
|
168
|
+
const vargs = args.split("_");
|
|
169
|
+
return mappers.map((v, i) => v(vargs[i]));
|
|
170
|
+
}
|
|
171
|
+
function getHandler(key) {
|
|
172
|
+
return Handlers[key];
|
|
173
|
+
}
|
|
174
|
+
function applyHandler(ctx, pipe, handler, argsStr) {
|
|
175
|
+
const args = handler.args ? parseArgs(argsStr, handler.args) : [];
|
|
176
|
+
return handler.apply(ctx, pipe, ...args);
|
|
177
|
+
}
|
|
178
|
+
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
179
|
+
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
180
|
+
let { width, height } = desiredDimensions;
|
|
181
|
+
if (width > sourceDimensions.width) {
|
|
182
|
+
width = sourceDimensions.width;
|
|
183
|
+
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
184
|
+
}
|
|
185
|
+
if (height > sourceDimensions.height) {
|
|
186
|
+
height = sourceDimensions.height;
|
|
187
|
+
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
188
|
+
}
|
|
189
|
+
return { width, height };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const quality = {
|
|
193
|
+
args: [VArg],
|
|
194
|
+
order: -1,
|
|
195
|
+
apply: (context, _pipe, quality2) => {
|
|
196
|
+
context.quality = quality2;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const fit = {
|
|
200
|
+
args: [VArg],
|
|
201
|
+
order: -1,
|
|
202
|
+
apply: (context, _pipe, fit2) => {
|
|
203
|
+
context.fit = fit2;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const position = {
|
|
207
|
+
args: [VArg],
|
|
208
|
+
order: -1,
|
|
209
|
+
apply: (context, _pipe, position2) => {
|
|
210
|
+
context.position = position2;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const HEX_RE = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
|
|
214
|
+
const SHORTHEX_RE = /^([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
215
|
+
const background = {
|
|
216
|
+
args: [VArg],
|
|
217
|
+
order: -1,
|
|
218
|
+
apply: (context, _pipe, background2) => {
|
|
219
|
+
background2 = String(background2);
|
|
220
|
+
if (!background2.startsWith("#") && (HEX_RE.test(background2) || SHORTHEX_RE.test(background2))) {
|
|
221
|
+
background2 = "#" + background2;
|
|
222
|
+
}
|
|
223
|
+
context.background = background2;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const enlarge = {
|
|
227
|
+
args: [],
|
|
228
|
+
apply: (context) => {
|
|
229
|
+
context.enlarge = true;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const width = {
|
|
233
|
+
args: [VArg],
|
|
234
|
+
apply: (context, pipe, width2) => {
|
|
235
|
+
return pipe.resize(width2, null, { withoutEnlargement: !context.enlarge });
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
const height = {
|
|
239
|
+
args: [VArg],
|
|
240
|
+
apply: (context, pipe, height2) => {
|
|
241
|
+
return pipe.resize(null, height2, { withoutEnlargement: !context.enlarge });
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const resize = {
|
|
245
|
+
args: [VArg, VArg, VArg],
|
|
246
|
+
apply: (context, pipe, size) => {
|
|
247
|
+
let [width2, height2] = String(size).split("x").map((v) => Number(v));
|
|
248
|
+
if (!width2) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!height2) {
|
|
252
|
+
height2 = width2;
|
|
253
|
+
}
|
|
254
|
+
if (!context.enlarge) {
|
|
255
|
+
const clamped = clampDimensionsPreservingAspectRatio(context.meta, { width: width2, height: height2 });
|
|
256
|
+
width2 = clamped.width;
|
|
257
|
+
height2 = clamped.height;
|
|
258
|
+
}
|
|
259
|
+
return pipe.resize(width2, height2, {
|
|
260
|
+
fit: context.fit,
|
|
261
|
+
position: context.position,
|
|
262
|
+
background: context.background
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const trim = {
|
|
267
|
+
args: [VArg],
|
|
268
|
+
apply: (_context, pipe, threshold2) => {
|
|
269
|
+
return pipe.trim(threshold2);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
const extend = {
|
|
273
|
+
args: [VArg, VArg, VArg, VArg],
|
|
274
|
+
apply: (context, pipe, top, right, bottom, left) => {
|
|
275
|
+
return pipe.extend({
|
|
276
|
+
top,
|
|
277
|
+
left,
|
|
278
|
+
bottom,
|
|
279
|
+
right,
|
|
280
|
+
background: context.background
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
const extract = {
|
|
285
|
+
args: [VArg, VArg, VArg, VArg],
|
|
286
|
+
apply: (context, pipe, top, right, bottom, left) => {
|
|
287
|
+
return pipe.extend({
|
|
288
|
+
top,
|
|
289
|
+
left,
|
|
290
|
+
bottom,
|
|
291
|
+
right,
|
|
292
|
+
background: context.background
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const rotate = {
|
|
297
|
+
args: [VArg],
|
|
298
|
+
apply: (context, pipe, angel) => {
|
|
299
|
+
return pipe.rotate(angel, {
|
|
300
|
+
background: context.background
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const flip = {
|
|
305
|
+
args: [],
|
|
306
|
+
apply: (_context, pipe) => {
|
|
307
|
+
return pipe.flip();
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const flop = {
|
|
311
|
+
args: [],
|
|
312
|
+
apply: (_context, pipe) => {
|
|
313
|
+
return pipe.flop();
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const sharpen = {
|
|
317
|
+
args: [VArg, VArg, VArg],
|
|
318
|
+
apply: (_context, pipe, sigma, flat, jagged) => {
|
|
319
|
+
return pipe.sharpen(sigma, flat, jagged);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
const median = {
|
|
323
|
+
args: [VArg, VArg, VArg],
|
|
324
|
+
apply: (_context, pipe, size) => {
|
|
325
|
+
return pipe.median(size);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const blur = {
|
|
329
|
+
args: [VArg, VArg, VArg],
|
|
330
|
+
apply: (_context, pipe) => {
|
|
331
|
+
return pipe.blur();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const flatten = {
|
|
335
|
+
args: [VArg, VArg, VArg],
|
|
336
|
+
apply: (context, pipe) => {
|
|
337
|
+
return pipe.flatten({
|
|
338
|
+
background: context.background
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const gamma = {
|
|
343
|
+
args: [VArg, VArg, VArg],
|
|
344
|
+
apply: (_context, pipe, gamma2, gammaOut) => {
|
|
345
|
+
return pipe.gamma(gamma2, gammaOut);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const negate = {
|
|
349
|
+
args: [VArg, VArg, VArg],
|
|
350
|
+
apply: (_context, pipe) => {
|
|
351
|
+
return pipe.negate();
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const normalize = {
|
|
355
|
+
args: [VArg, VArg, VArg],
|
|
356
|
+
apply: (_context, pipe) => {
|
|
357
|
+
return pipe.normalize();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
const threshold = {
|
|
361
|
+
args: [VArg],
|
|
362
|
+
apply: (_context, pipe, threshold2) => {
|
|
363
|
+
return pipe.threshold(threshold2);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const modulate = {
|
|
367
|
+
args: [VArg],
|
|
368
|
+
apply: (_context, pipe, brightness, saturation, hue) => {
|
|
369
|
+
return pipe.modulate({
|
|
370
|
+
brightness,
|
|
371
|
+
saturation,
|
|
372
|
+
hue
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const tint = {
|
|
377
|
+
args: [VArg],
|
|
378
|
+
apply: (_context, pipe, rgb) => {
|
|
379
|
+
return pipe.tint(rgb);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
const grayscale = {
|
|
383
|
+
args: [VArg],
|
|
384
|
+
apply: (_context, pipe) => {
|
|
385
|
+
return pipe.grayscale();
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const crop = extract;
|
|
389
|
+
const q = quality;
|
|
390
|
+
const b = background;
|
|
391
|
+
const w = width;
|
|
392
|
+
const h = height;
|
|
393
|
+
const s = resize;
|
|
394
|
+
const pos = position;
|
|
395
|
+
|
|
396
|
+
const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
|
|
397
|
+
function createIPX(userOptions) {
|
|
398
|
+
const defaults = {
|
|
399
|
+
dir: getEnv("IPX_DIR", "."),
|
|
400
|
+
domains: getEnv("IPX_DOMAINS", []),
|
|
401
|
+
alias: getEnv("IPX_ALIAS", {}),
|
|
402
|
+
fetchOptions: getEnv("IPX_FETCH_OPTIONS", {}),
|
|
403
|
+
maxAge: getEnv("IPX_MAX_AGE", 300),
|
|
404
|
+
sharp: {}
|
|
405
|
+
};
|
|
406
|
+
const options = defu__default(userOptions, defaults);
|
|
407
|
+
options.alias = Object.fromEntries(Object.entries(options.alias).map((e) => [ufo.withLeadingSlash(e[0]), e[1]]));
|
|
408
|
+
const ctx = {
|
|
409
|
+
sources: {}
|
|
410
|
+
};
|
|
411
|
+
if (options.dir) {
|
|
412
|
+
ctx.sources.filesystem = createFilesystemSource({
|
|
413
|
+
dir: options.dir,
|
|
414
|
+
maxAge: options.maxAge
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (options.domains) {
|
|
418
|
+
ctx.sources.http = createHTTPSource({
|
|
419
|
+
domains: options.domains,
|
|
420
|
+
fetchOptions: options.fetchOptions,
|
|
421
|
+
maxAge: options.maxAge
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return function ipx(id, modifiers = {}, reqOptions = {}) {
|
|
425
|
+
if (!id) {
|
|
426
|
+
throw createError("resource id is missing", 400);
|
|
427
|
+
}
|
|
428
|
+
id = ufo.hasProtocol(id) ? id : ufo.withLeadingSlash(id);
|
|
429
|
+
for (const base in options.alias) {
|
|
430
|
+
if (id.startsWith(base)) {
|
|
431
|
+
id = ufo.joinURL(options.alias[base], id.substr(base.length));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const getSrc = cachedPromise(() => {
|
|
435
|
+
const source = ufo.hasProtocol(id) ? "http" : "filesystem";
|
|
436
|
+
if (!ctx.sources[source]) {
|
|
437
|
+
throw createError("Unknown source", 400, source);
|
|
438
|
+
}
|
|
439
|
+
return ctx.sources[source](id, reqOptions);
|
|
440
|
+
});
|
|
441
|
+
const getData = cachedPromise(async () => {
|
|
442
|
+
const src = await getSrc();
|
|
443
|
+
const data = await src.getData();
|
|
444
|
+
const meta = imageMeta.imageMeta(data);
|
|
445
|
+
const mFormat = modifiers.f || modifiers.format;
|
|
446
|
+
let format = mFormat || meta.type;
|
|
447
|
+
if (format === "jpg") {
|
|
448
|
+
format = "jpeg";
|
|
449
|
+
}
|
|
450
|
+
if (meta.type === "svg" && !mFormat) {
|
|
451
|
+
return {
|
|
452
|
+
data,
|
|
453
|
+
format: "svg+xml",
|
|
454
|
+
meta
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
|
|
458
|
+
const Sharp = await import('sharp').then((r) => r.default || r);
|
|
459
|
+
let sharp = Sharp(data, { animated });
|
|
460
|
+
Object.assign(sharp.options, options.sharp);
|
|
461
|
+
const handlers = Object.entries(modifiers).map(([name, args]) => ({ handler: getHandler(name), name, args })).filter((h) => h.handler).sort((a, b) => {
|
|
462
|
+
const aKey = (a.handler.order || a.name || "").toString();
|
|
463
|
+
const bKey = (b.handler.order || b.name || "").toString();
|
|
464
|
+
return aKey.localeCompare(bKey);
|
|
465
|
+
});
|
|
466
|
+
const handlerCtx = { meta };
|
|
467
|
+
for (const h of handlers) {
|
|
468
|
+
sharp = applyHandler(handlerCtx, sharp, h.handler, h.args) || sharp;
|
|
469
|
+
}
|
|
470
|
+
if (SUPPORTED_FORMATS.includes(format)) {
|
|
471
|
+
sharp = sharp.toFormat(format, {
|
|
472
|
+
quality: handlerCtx.quality,
|
|
473
|
+
progressive: format === "jpeg"
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
const newData = await sharp.toBuffer();
|
|
477
|
+
return {
|
|
478
|
+
data: newData,
|
|
479
|
+
format,
|
|
480
|
+
meta
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
return {
|
|
484
|
+
src: getSrc,
|
|
485
|
+
data: getData
|
|
486
|
+
};
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const MODIFIER_SEP = /[,&]/g;
|
|
491
|
+
const MODIFIER_VAL_SEP = /[_=:]/g;
|
|
492
|
+
async function _handleRequest(req, ipx) {
|
|
493
|
+
const res = {
|
|
494
|
+
statusCode: 200,
|
|
495
|
+
statusMessage: "",
|
|
496
|
+
headers: {},
|
|
497
|
+
body: ""
|
|
498
|
+
};
|
|
499
|
+
const [modifiersStr = "", ...idSegments] = req.url.substring(1).split("/");
|
|
500
|
+
const id = safeString(ufo.decode(idSegments.join("/")));
|
|
501
|
+
if (!modifiersStr) {
|
|
502
|
+
throw createError("Modifiers are missing", 400, req.url);
|
|
503
|
+
}
|
|
504
|
+
if (!id || id === "/") {
|
|
505
|
+
throw createError("Resource id is missing", 400, req.url);
|
|
506
|
+
}
|
|
507
|
+
const modifiers = /* @__PURE__ */ Object.create(null);
|
|
508
|
+
if (modifiersStr !== "_") {
|
|
509
|
+
for (const p of modifiersStr.split(MODIFIER_SEP)) {
|
|
510
|
+
const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
|
|
511
|
+
modifiers[safeString(key)] = safeString(ufo.decode(value));
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const img = ipx(id, modifiers, req.options);
|
|
515
|
+
const src = await img.src();
|
|
516
|
+
if (src.mtime) {
|
|
517
|
+
if (req.headers["if-modified-since"]) {
|
|
518
|
+
if (new Date(req.headers["if-modified-since"]) >= src.mtime) {
|
|
519
|
+
res.statusCode = 304;
|
|
520
|
+
return res;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
res.headers["Last-Modified"] = +src.mtime + "";
|
|
524
|
+
}
|
|
525
|
+
if (typeof src.maxAge === "number") {
|
|
526
|
+
res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
|
|
527
|
+
}
|
|
528
|
+
const { data, format } = await img.data();
|
|
529
|
+
const etag = getEtag__default(data);
|
|
530
|
+
res.headers.ETag = etag;
|
|
531
|
+
if (etag && req.headers["if-none-match"] === etag) {
|
|
532
|
+
res.statusCode = 304;
|
|
533
|
+
return res;
|
|
534
|
+
}
|
|
535
|
+
if (format) {
|
|
536
|
+
res.headers["Content-Type"] = `image/${format}`;
|
|
537
|
+
}
|
|
538
|
+
res.body = data;
|
|
539
|
+
return sanetizeReponse(res);
|
|
540
|
+
}
|
|
541
|
+
function handleRequest(req, ipx) {
|
|
542
|
+
return _handleRequest(req, ipx).catch((err) => {
|
|
543
|
+
const statusCode = parseInt(err.statusCode) || 500;
|
|
544
|
+
const statusMessage = err.statusMessage ? err.statusMessage : `IPX Error (${statusCode})`;
|
|
545
|
+
if (process.env.NODE_ENV !== "production" && statusCode === 500) {
|
|
546
|
+
console.error(err);
|
|
547
|
+
}
|
|
548
|
+
return sanetizeReponse({
|
|
549
|
+
statusCode,
|
|
550
|
+
statusMessage,
|
|
551
|
+
body: "IPX Error: " + err,
|
|
552
|
+
headers: {}
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function createIPXMiddleware(ipx) {
|
|
557
|
+
return function IPXMiddleware(req, res) {
|
|
558
|
+
handleRequest({ url: req.url, headers: req.headers }, ipx).then((_res) => {
|
|
559
|
+
res.statusCode = _res.statusCode;
|
|
560
|
+
res.statusMessage = _res.statusMessage;
|
|
561
|
+
for (const name in _res.headers) {
|
|
562
|
+
res.setHeader(name, _res.headers[name]);
|
|
563
|
+
}
|
|
564
|
+
res.end(_res.body);
|
|
565
|
+
});
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function sanetizeReponse(res) {
|
|
569
|
+
return {
|
|
570
|
+
statusCode: res.statusCode || 200,
|
|
571
|
+
statusMessage: res.statusMessage ? safeString(res.statusMessage) : "OK",
|
|
572
|
+
headers: safeStringObject(res.headers || {}),
|
|
573
|
+
body: typeof res.body === "string" ? xss__default(safeString(res.body)) : res.body || ""
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
function safeString(input) {
|
|
577
|
+
return JSON.stringify(input).replace(/^"|"$/g, "");
|
|
578
|
+
}
|
|
579
|
+
function safeStringObject(input) {
|
|
580
|
+
const dst = {};
|
|
581
|
+
for (const key in input) {
|
|
582
|
+
dst[key] = safeString(input[key]);
|
|
583
|
+
}
|
|
584
|
+
return dst;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
exports.createIPX = createIPX;
|
|
588
|
+
exports.createIPXMiddleware = createIPXMiddleware;
|
|
589
|
+
exports.handleRequest = handleRequest;
|
|
@@ -2,8 +2,7 @@ import defu from 'defu';
|
|
|
2
2
|
import { imageMeta } from 'image-meta';
|
|
3
3
|
import { parseURL, withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
|
|
4
4
|
import { promises } from 'fs';
|
|
5
|
-
import { resolve, join } from 'pathe';
|
|
6
|
-
import isValidPath from 'is-valid-path';
|
|
5
|
+
import { resolve, join, parse } from 'pathe';
|
|
7
6
|
import http from 'http';
|
|
8
7
|
import https from 'https';
|
|
9
8
|
import { fetch } from 'ohmyfetch';
|
|
@@ -62,9 +61,9 @@ function cachedPromise(fn) {
|
|
|
62
61
|
}
|
|
63
62
|
class IPXError extends Error {
|
|
64
63
|
}
|
|
65
|
-
function createError(
|
|
66
|
-
const err = new IPXError(
|
|
67
|
-
err.statusMessage = "IPX: " +
|
|
64
|
+
function createError(statusMessage, statusCode, trace) {
|
|
65
|
+
const err = new IPXError(statusMessage + (trace ? ` (${trace})` : ""));
|
|
66
|
+
err.statusMessage = "IPX: " + statusMessage;
|
|
68
67
|
err.statusCode = statusCode;
|
|
69
68
|
return err;
|
|
70
69
|
}
|
|
@@ -73,29 +72,39 @@ const createFilesystemSource = (options) => {
|
|
|
73
72
|
const rootDir = resolve(options.dir);
|
|
74
73
|
return async (id) => {
|
|
75
74
|
const fsPath = resolve(join(rootDir, id));
|
|
76
|
-
if (!isValidPath(
|
|
77
|
-
throw createError("Forbidden path
|
|
75
|
+
if (!isValidPath(fsPath) || !fsPath.startsWith(rootDir)) {
|
|
76
|
+
throw createError("Forbidden path", 403, id);
|
|
78
77
|
}
|
|
79
78
|
let stats;
|
|
80
79
|
try {
|
|
81
80
|
stats = await promises.stat(fsPath);
|
|
82
81
|
} catch (err) {
|
|
83
82
|
if (err.code === "ENOENT") {
|
|
84
|
-
throw createError("File not found
|
|
83
|
+
throw createError("File not found", 404, fsPath);
|
|
85
84
|
} else {
|
|
86
|
-
throw createError("File access error
|
|
85
|
+
throw createError("File access error " + err.code, 403, fsPath);
|
|
87
86
|
}
|
|
88
87
|
}
|
|
89
88
|
if (!stats.isFile()) {
|
|
90
|
-
throw createError("Path should be a file
|
|
89
|
+
throw createError("Path should be a file", 400, fsPath);
|
|
91
90
|
}
|
|
92
91
|
return {
|
|
93
92
|
mtime: stats.mtime,
|
|
94
|
-
maxAge: options.maxAge
|
|
93
|
+
maxAge: options.maxAge,
|
|
95
94
|
getData: cachedPromise(() => promises.readFile(fsPath))
|
|
96
95
|
};
|
|
97
96
|
};
|
|
98
97
|
};
|
|
98
|
+
const isWindows = process.platform === "win32";
|
|
99
|
+
function isValidPath(fp) {
|
|
100
|
+
if (isWindows) {
|
|
101
|
+
fp = fp.slice(parse(fp).root.length);
|
|
102
|
+
}
|
|
103
|
+
if (/[<>:"|?*]/.test(fp)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
99
108
|
|
|
100
109
|
const createHTTPSource = (options) => {
|
|
101
110
|
const httpsAgent = new https.Agent({ keepAlive: true });
|
|
@@ -108,18 +117,19 @@ const createHTTPSource = (options) => {
|
|
|
108
117
|
return async (id, reqOptions) => {
|
|
109
118
|
const url = new URL(id);
|
|
110
119
|
if (!url.hostname) {
|
|
111
|
-
throw createError("Hostname is missing
|
|
120
|
+
throw createError("Hostname is missing", 403, id);
|
|
112
121
|
}
|
|
113
122
|
if (!reqOptions?.bypassDomain && !hosts.find((host) => url.hostname === host)) {
|
|
114
|
-
throw createError("Forbidden host
|
|
123
|
+
throw createError("Forbidden host", 403, url.hostname);
|
|
115
124
|
}
|
|
116
125
|
const response = await fetch(id, {
|
|
117
|
-
agent: id.startsWith("https") ? httpsAgent : httpAgent
|
|
126
|
+
agent: id.startsWith("https") ? httpsAgent : httpAgent,
|
|
127
|
+
...options.fetchOptions
|
|
118
128
|
});
|
|
119
129
|
if (!response.ok) {
|
|
120
|
-
throw createError(
|
|
130
|
+
throw createError("Fetch error", response.status || 500, response.statusText);
|
|
121
131
|
}
|
|
122
|
-
let maxAge = options.maxAge
|
|
132
|
+
let maxAge = options.maxAge;
|
|
123
133
|
const _cacheControl = response.headers.get("cache-control");
|
|
124
134
|
if (_cacheControl) {
|
|
125
135
|
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
@@ -135,7 +145,7 @@ const createHTTPSource = (options) => {
|
|
|
135
145
|
return {
|
|
136
146
|
mtime,
|
|
137
147
|
maxAge,
|
|
138
|
-
getData: cachedPromise(() => response.
|
|
148
|
+
getData: cachedPromise(() => response.arrayBuffer().then((ab) => Buffer.from(ab)))
|
|
139
149
|
};
|
|
140
150
|
};
|
|
141
151
|
};
|
|
@@ -372,12 +382,14 @@ const h = height;
|
|
|
372
382
|
const s = resize;
|
|
373
383
|
const pos = position;
|
|
374
384
|
|
|
375
|
-
const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff"];
|
|
385
|
+
const SUPPORTED_FORMATS = ["jpeg", "png", "webp", "avif", "tiff", "gif"];
|
|
376
386
|
function createIPX(userOptions) {
|
|
377
387
|
const defaults = {
|
|
378
388
|
dir: getEnv("IPX_DIR", "."),
|
|
379
389
|
domains: getEnv("IPX_DOMAINS", []),
|
|
380
390
|
alias: getEnv("IPX_ALIAS", {}),
|
|
391
|
+
fetchOptions: getEnv("IPX_FETCH_OPTIONS", {}),
|
|
392
|
+
maxAge: getEnv("IPX_MAX_AGE", 300),
|
|
381
393
|
sharp: {}
|
|
382
394
|
};
|
|
383
395
|
const options = defu(userOptions, defaults);
|
|
@@ -387,12 +399,15 @@ function createIPX(userOptions) {
|
|
|
387
399
|
};
|
|
388
400
|
if (options.dir) {
|
|
389
401
|
ctx.sources.filesystem = createFilesystemSource({
|
|
390
|
-
dir: options.dir
|
|
402
|
+
dir: options.dir,
|
|
403
|
+
maxAge: options.maxAge
|
|
391
404
|
});
|
|
392
405
|
}
|
|
393
406
|
if (options.domains) {
|
|
394
407
|
ctx.sources.http = createHTTPSource({
|
|
395
|
-
domains: options.domains
|
|
408
|
+
domains: options.domains,
|
|
409
|
+
fetchOptions: options.fetchOptions,
|
|
410
|
+
maxAge: options.maxAge
|
|
396
411
|
});
|
|
397
412
|
}
|
|
398
413
|
return function ipx(id, modifiers = {}, reqOptions = {}) {
|
|
@@ -408,7 +423,7 @@ function createIPX(userOptions) {
|
|
|
408
423
|
const getSrc = cachedPromise(() => {
|
|
409
424
|
const source = hasProtocol(id) ? "http" : "filesystem";
|
|
410
425
|
if (!ctx.sources[source]) {
|
|
411
|
-
throw createError("Unknown source
|
|
426
|
+
throw createError("Unknown source", 400, source);
|
|
412
427
|
}
|
|
413
428
|
return ctx.sources[source](id, reqOptions);
|
|
414
429
|
});
|
|
@@ -428,10 +443,7 @@ function createIPX(userOptions) {
|
|
|
428
443
|
meta
|
|
429
444
|
};
|
|
430
445
|
}
|
|
431
|
-
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0;
|
|
432
|
-
if (animated) {
|
|
433
|
-
format = "webp";
|
|
434
|
-
}
|
|
446
|
+
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
|
|
435
447
|
const Sharp = await import('sharp').then((r) => r.default || r);
|
|
436
448
|
let sharp = Sharp(data, { animated });
|
|
437
449
|
Object.assign(sharp.options, options.sharp);
|
|
@@ -464,6 +476,8 @@ function createIPX(userOptions) {
|
|
|
464
476
|
};
|
|
465
477
|
}
|
|
466
478
|
|
|
479
|
+
const MODIFIER_SEP = /[,&]/g;
|
|
480
|
+
const MODIFIER_VAL_SEP = /[_=:]/g;
|
|
467
481
|
async function _handleRequest(req, ipx) {
|
|
468
482
|
const res = {
|
|
469
483
|
statusCode: 200,
|
|
@@ -471,19 +485,19 @@ async function _handleRequest(req, ipx) {
|
|
|
471
485
|
headers: {},
|
|
472
486
|
body: ""
|
|
473
487
|
};
|
|
474
|
-
const [modifiersStr = "", ...idSegments] = req.url.
|
|
475
|
-
const id = decode(idSegments.join("/"));
|
|
488
|
+
const [modifiersStr = "", ...idSegments] = req.url.substring(1).split("/");
|
|
489
|
+
const id = safeString(decode(idSegments.join("/")));
|
|
476
490
|
if (!modifiersStr) {
|
|
477
|
-
throw createError("Modifiers
|
|
491
|
+
throw createError("Modifiers are missing", 400, req.url);
|
|
478
492
|
}
|
|
479
493
|
if (!id || id === "/") {
|
|
480
|
-
throw createError("Resource id is missing
|
|
494
|
+
throw createError("Resource id is missing", 400, req.url);
|
|
481
495
|
}
|
|
482
496
|
const modifiers = /* @__PURE__ */ Object.create(null);
|
|
483
497
|
if (modifiersStr !== "_") {
|
|
484
|
-
for (const p of modifiersStr.split(
|
|
485
|
-
const [key, value = ""] = p.split(
|
|
486
|
-
modifiers[key] = decode(value);
|
|
498
|
+
for (const p of modifiersStr.split(MODIFIER_SEP)) {
|
|
499
|
+
const [key, value = ""] = p.split(MODIFIER_VAL_SEP);
|
|
500
|
+
modifiers[safeString(key)] = safeString(decode(value));
|
|
487
501
|
}
|
|
488
502
|
}
|
|
489
503
|
const img = ipx(id, modifiers, req.options);
|
|
@@ -497,7 +511,7 @@ async function _handleRequest(req, ipx) {
|
|
|
497
511
|
}
|
|
498
512
|
res.headers["Last-Modified"] = +src.mtime + "";
|
|
499
513
|
}
|
|
500
|
-
if (src.maxAge
|
|
514
|
+
if (typeof src.maxAge === "number") {
|
|
501
515
|
res.headers["Cache-Control"] = `max-age=${+src.maxAge}, public, s-maxage=${+src.maxAge}`;
|
|
502
516
|
}
|
|
503
517
|
const { data, format } = await img.data();
|
|
@@ -511,21 +525,21 @@ async function _handleRequest(req, ipx) {
|
|
|
511
525
|
res.headers["Content-Type"] = `image/${format}`;
|
|
512
526
|
}
|
|
513
527
|
res.body = data;
|
|
514
|
-
return res;
|
|
528
|
+
return sanetizeReponse(res);
|
|
515
529
|
}
|
|
516
530
|
function handleRequest(req, ipx) {
|
|
517
531
|
return _handleRequest(req, ipx).catch((err) => {
|
|
518
532
|
const statusCode = parseInt(err.statusCode) || 500;
|
|
519
|
-
const statusMessage = err.statusMessage ?
|
|
533
|
+
const statusMessage = err.statusMessage ? err.statusMessage : `IPX Error (${statusCode})`;
|
|
520
534
|
if (process.env.NODE_ENV !== "production" && statusCode === 500) {
|
|
521
535
|
console.error(err);
|
|
522
536
|
}
|
|
523
|
-
return {
|
|
537
|
+
return sanetizeReponse({
|
|
524
538
|
statusCode,
|
|
525
539
|
statusMessage,
|
|
526
|
-
body:
|
|
540
|
+
body: "IPX Error: " + err,
|
|
527
541
|
headers: {}
|
|
528
|
-
};
|
|
542
|
+
});
|
|
529
543
|
});
|
|
530
544
|
}
|
|
531
545
|
function createIPXMiddleware(ipx) {
|
|
@@ -540,5 +554,23 @@ function createIPXMiddleware(ipx) {
|
|
|
540
554
|
});
|
|
541
555
|
};
|
|
542
556
|
}
|
|
557
|
+
function sanetizeReponse(res) {
|
|
558
|
+
return {
|
|
559
|
+
statusCode: res.statusCode || 200,
|
|
560
|
+
statusMessage: res.statusMessage ? safeString(res.statusMessage) : "OK",
|
|
561
|
+
headers: safeStringObject(res.headers || {}),
|
|
562
|
+
body: typeof res.body === "string" ? xss(safeString(res.body)) : res.body || ""
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function safeString(input) {
|
|
566
|
+
return JSON.stringify(input).replace(/^"|"$/g, "");
|
|
567
|
+
}
|
|
568
|
+
function safeStringObject(input) {
|
|
569
|
+
const dst = {};
|
|
570
|
+
for (const key in input) {
|
|
571
|
+
dst[key] = safeString(input[key]);
|
|
572
|
+
}
|
|
573
|
+
return dst;
|
|
574
|
+
}
|
|
543
575
|
|
|
544
576
|
export { createIPXMiddleware as a, createIPX as c, handleRequest as h };
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const consola = require('consola');
|
|
4
|
+
const listhen = require('listhen');
|
|
5
|
+
const middleware = require('./chunks/middleware.cjs');
|
|
6
|
+
require('defu');
|
|
7
|
+
require('image-meta');
|
|
8
|
+
require('ufo');
|
|
9
|
+
require('fs');
|
|
10
|
+
require('pathe');
|
|
11
|
+
require('http');
|
|
12
|
+
require('https');
|
|
13
|
+
require('ohmyfetch');
|
|
14
|
+
require('destr');
|
|
15
|
+
require('etag');
|
|
16
|
+
require('xss');
|
|
17
|
+
|
|
18
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
|
|
19
|
+
|
|
20
|
+
const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const ipx = middleware.createIPX({});
|
|
24
|
+
const middleware$1 = middleware.createIPXMiddleware(ipx);
|
|
25
|
+
await listhen.listen(middleware$1, {
|
|
26
|
+
clipboard: false
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
main().catch((err) => {
|
|
30
|
+
consola__default.error(err);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
package/dist/cli.mjs
CHANGED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const middleware = require('./chunks/middleware.cjs');
|
|
6
|
+
require('defu');
|
|
7
|
+
require('image-meta');
|
|
8
|
+
require('ufo');
|
|
9
|
+
require('fs');
|
|
10
|
+
require('pathe');
|
|
11
|
+
require('http');
|
|
12
|
+
require('https');
|
|
13
|
+
require('ohmyfetch');
|
|
14
|
+
require('destr');
|
|
15
|
+
require('etag');
|
|
16
|
+
require('xss');
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
exports.createIPX = middleware.createIPX;
|
|
21
|
+
exports.createIPXMiddleware = middleware.createIPXMiddleware;
|
|
22
|
+
exports.handleRequest = middleware.handleRequest;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface SourceData {
|
|
|
6
6
|
getData: () => Promise<Buffer>;
|
|
7
7
|
}
|
|
8
8
|
declare type Source = (src: string, reqOptions?: any) => Promise<SourceData>;
|
|
9
|
-
declare type SourceFactory = (options
|
|
9
|
+
declare type SourceFactory<T = Record<string, any>> = (options: T) => Source;
|
|
10
10
|
|
|
11
11
|
interface ImageMeta {
|
|
12
12
|
width: number;
|
|
@@ -27,8 +27,10 @@ declare type IPX = (id: string, modifiers?: Record<string, string>, reqOptions?:
|
|
|
27
27
|
};
|
|
28
28
|
interface IPXOptions {
|
|
29
29
|
dir?: false | string;
|
|
30
|
+
maxAge?: number;
|
|
30
31
|
domains?: false | string[];
|
|
31
32
|
alias: Record<string, string>;
|
|
33
|
+
fetchOptions: RequestInit;
|
|
32
34
|
sharp?: {
|
|
33
35
|
[key: string]: any;
|
|
34
36
|
};
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
@@ -9,51 +9,50 @@
|
|
|
9
9
|
"import": "./dist/index.mjs"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"main": "dist/index.cjs",
|
|
13
|
-
"module": "dist/index.mjs",
|
|
12
|
+
"main": "./dist/index.cjs",
|
|
13
|
+
"module": "./dist/index.mjs",
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"bin": "./bin/ipx.mjs",
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"bin"
|
|
19
19
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "unbuild",
|
|
22
|
-
"dev": "nodemon",
|
|
23
|
-
"lint": "eslint --ext .ts .",
|
|
24
|
-
"prepack": "yarn build",
|
|
25
|
-
"release": "yarn test && standard-version && git push --follow-tags && npm publish",
|
|
26
|
-
"start": "node bin/ipx.js",
|
|
27
|
-
"test": "yarn lint && jest"
|
|
28
|
-
},
|
|
29
20
|
"dependencies": {
|
|
30
21
|
"consola": "^2.15.3",
|
|
31
|
-
"defu": "^
|
|
32
|
-
"destr": "^1.1.
|
|
22
|
+
"defu": "^6.0.0",
|
|
23
|
+
"destr": "^1.1.1",
|
|
33
24
|
"etag": "^1.8.1",
|
|
34
25
|
"image-meta": "^0.1.1",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"xss": "^1.0.10"
|
|
26
|
+
"listhen": "^0.2.13",
|
|
27
|
+
"ohmyfetch": "^0.4.18",
|
|
28
|
+
"pathe": "^0.3.0",
|
|
29
|
+
"sharp": "^0.30.7",
|
|
30
|
+
"ufo": "^0.8.4",
|
|
31
|
+
"xss": "^1.0.13"
|
|
42
32
|
},
|
|
43
33
|
"devDependencies": {
|
|
44
34
|
"@nuxtjs/eslint-config-typescript": "latest",
|
|
45
35
|
"@types/etag": "latest",
|
|
46
36
|
"@types/is-valid-path": "latest",
|
|
47
|
-
"@types/jest": "latest",
|
|
48
37
|
"@types/node-fetch": "latest",
|
|
49
38
|
"@types/sharp": "latest",
|
|
39
|
+
"c8": "latest",
|
|
50
40
|
"eslint": "latest",
|
|
51
|
-
"jest": "latest",
|
|
52
41
|
"jiti": "latest",
|
|
53
42
|
"nodemon": "latest",
|
|
43
|
+
"serve-handler": "^6.1.3",
|
|
54
44
|
"standard-version": "latest",
|
|
55
|
-
"ts-jest": "latest",
|
|
56
45
|
"typescript": "latest",
|
|
57
|
-
"unbuild": "latest"
|
|
46
|
+
"unbuild": "latest",
|
|
47
|
+
"vitest": "latest"
|
|
48
|
+
},
|
|
49
|
+
"packageManager": "pnpm@7.3.0",
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "unbuild",
|
|
52
|
+
"dev": "nodemon",
|
|
53
|
+
"lint": "eslint --ext .ts .",
|
|
54
|
+
"release": "pnpm test && standard-version && git push --follow-tags && pnpm publish",
|
|
55
|
+
"start": "node bin/ipx.js",
|
|
56
|
+
"test": "pnpm lint && vitest run --coverage"
|
|
58
57
|
}
|
|
59
|
-
}
|
|
58
|
+
}
|