ipx 2.0.0-0 → 2.0.0-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/LICENSE +0 -0
- package/README.md +5 -5
- package/dist/cli.cjs +3 -3
- package/dist/cli.mjs +3 -3
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +5 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +2 -2
- package/dist/shared/{ipx.42c0c175.mjs → ipx.57fad794.mjs} +51 -16
- package/dist/shared/{ipx.0fc4e4c7.cjs → ipx.680a50a5.cjs} +50 -15
- package/package.json +21 -20
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ High performance, secure and easy-to-use image optimizer.
|
|
|
7
7
|
|
|
8
8
|
Powered by [sharp](https://github.com/lovell/sharp) and [libvips](https://github.com/libvips/libvips).
|
|
9
9
|
|
|
10
|
-
> [!IMPORTANT]
|
|
11
|
-
> This is the development branch for IPX v2. Check out [ipx/v1](https://github.com/unjs/ipx/tree/v1) for latest stable docs.
|
|
10
|
+
> [!IMPORTANT]
|
|
11
|
+
> This is the development branch for IPX v2. Check out [ipx/v1](https://github.com/unjs/ipx/tree/v1) for latest stable docs and [#71](https://github.com/unjs/ipx/issues/171) for v2 roadmap.
|
|
12
12
|
|
|
13
13
|
## Using CLI
|
|
14
14
|
|
|
@@ -17,13 +17,13 @@ You can use `ipx` command to start server.
|
|
|
17
17
|
Using `npx`:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx ipx@
|
|
20
|
+
npx ipx@next-2 serve --dir ./
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Usin `bun`
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
bun x ipx@
|
|
26
|
+
bun x npx ipx@next-2 serve --dir ./
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
The default serve directory is the current working directory.
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
createIPXMiddleware,
|
|
39
39
|
ipxFSStorage,
|
|
40
40
|
ipxHttpStorage,
|
|
41
|
-
} from "
|
|
41
|
+
} from "ipx";
|
|
42
42
|
|
|
43
43
|
const ipx = createIPX({
|
|
44
44
|
storage: ipxFSStorage({ dir: "./public" }),
|
package/dist/cli.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const listhen = require('listhen');
|
|
4
4
|
const citty = require('citty');
|
|
5
5
|
const cli = require('listhen/cli');
|
|
6
|
-
const nodeFs = require('./shared/ipx.
|
|
6
|
+
const nodeFs = require('./shared/ipx.680a50a5.cjs');
|
|
7
7
|
require('defu');
|
|
8
8
|
require('image-meta');
|
|
9
9
|
require('ufo');
|
|
@@ -11,11 +11,11 @@ require('h3');
|
|
|
11
11
|
require('destr');
|
|
12
12
|
require('@fastify/accept-negotiator');
|
|
13
13
|
require('etag');
|
|
14
|
-
require('
|
|
14
|
+
require('ofetch');
|
|
15
15
|
require('pathe');
|
|
16
16
|
|
|
17
17
|
const name = "ipx";
|
|
18
|
-
const version = "2.0.0-
|
|
18
|
+
const version = "2.0.0-1";
|
|
19
19
|
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
20
20
|
|
|
21
21
|
const serve = citty.defineCommand({
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { listen } from 'listhen';
|
|
2
2
|
import { defineCommand, runMain } from 'citty';
|
|
3
3
|
import { getArgs, parseArgs } from 'listhen/cli';
|
|
4
|
-
import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.
|
|
4
|
+
import { c as createIPX, g as ipxFSStorage, i as ipxHttpStorage, e as createIPXNodeServer } from './shared/ipx.57fad794.mjs';
|
|
5
5
|
import 'defu';
|
|
6
6
|
import 'image-meta';
|
|
7
7
|
import 'ufo';
|
|
@@ -9,11 +9,11 @@ import 'h3';
|
|
|
9
9
|
import 'destr';
|
|
10
10
|
import '@fastify/accept-negotiator';
|
|
11
11
|
import 'etag';
|
|
12
|
-
import '
|
|
12
|
+
import 'ofetch';
|
|
13
13
|
import 'pathe';
|
|
14
14
|
|
|
15
15
|
const name = "ipx";
|
|
16
|
-
const version = "2.0.0-
|
|
16
|
+
const version = "2.0.0-1";
|
|
17
17
|
const description = "High performance, secure and easy-to-use image optimizer.";
|
|
18
18
|
|
|
19
19
|
const serve = defineCommand({
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const nodeFs = require('./shared/ipx.
|
|
3
|
+
const nodeFs = require('./shared/ipx.680a50a5.cjs');
|
|
4
4
|
require('defu');
|
|
5
5
|
require('image-meta');
|
|
6
6
|
require('ufo');
|
|
@@ -8,7 +8,7 @@ require('h3');
|
|
|
8
8
|
require('destr');
|
|
9
9
|
require('@fastify/accept-negotiator');
|
|
10
10
|
require('etag');
|
|
11
|
-
require('
|
|
11
|
+
require('ofetch');
|
|
12
12
|
require('pathe');
|
|
13
13
|
|
|
14
14
|
function unstorageToIPXStorage(storage, prefix) {
|
package/dist/index.d.cts
CHANGED
|
@@ -128,7 +128,11 @@ type IPXOptions = {
|
|
|
128
128
|
};
|
|
129
129
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
130
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer |
|
|
131
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer | {
|
|
132
|
+
error: {
|
|
133
|
+
message: string;
|
|
134
|
+
};
|
|
135
|
+
} | null>>;
|
|
132
136
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
133
137
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
134
138
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
package/dist/index.d.mts
CHANGED
|
@@ -128,7 +128,11 @@ type IPXOptions = {
|
|
|
128
128
|
};
|
|
129
129
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
130
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer |
|
|
131
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer | {
|
|
132
|
+
error: {
|
|
133
|
+
message: string;
|
|
134
|
+
};
|
|
135
|
+
} | null>>;
|
|
132
136
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
133
137
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
134
138
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
package/dist/index.d.ts
CHANGED
|
@@ -128,7 +128,11 @@ type IPXOptions = {
|
|
|
128
128
|
};
|
|
129
129
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
130
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer |
|
|
131
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer | {
|
|
132
|
+
error: {
|
|
133
|
+
message: string;
|
|
134
|
+
};
|
|
135
|
+
} | null>>;
|
|
132
136
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
133
137
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
134
138
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.
|
|
1
|
+
export { c as createIPX, b as createIPXH3App, a as createIPXH3Handler, e as createIPXNodeServer, f as createIPXPlainServer, d as createIPXWebServer, g as ipxFSStorage, i as ipxHttpStorage } from './shared/ipx.57fad794.mjs';
|
|
2
2
|
import 'defu';
|
|
3
3
|
import 'image-meta';
|
|
4
4
|
import 'ufo';
|
|
@@ -6,7 +6,7 @@ import 'h3';
|
|
|
6
6
|
import 'destr';
|
|
7
7
|
import '@fastify/accept-negotiator';
|
|
8
8
|
import 'etag';
|
|
9
|
-
import '
|
|
9
|
+
import 'ofetch';
|
|
10
10
|
import 'pathe';
|
|
11
11
|
|
|
12
12
|
function unstorageToIPXStorage(storage, prefix) {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defu } from 'defu';
|
|
2
2
|
import { imageMeta } from 'image-meta';
|
|
3
3
|
import { withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
|
|
4
|
-
import { createError, defineEventHandler,
|
|
4
|
+
import { createError, defineEventHandler, setResponseStatus, createApp, toWebHandler, toNodeListener, toPlainHandler, getRequestHeader, setResponseHeader } from 'h3';
|
|
5
5
|
import destr from 'destr';
|
|
6
6
|
import { negotiate } from '@fastify/accept-negotiator';
|
|
7
7
|
import getEtag from 'etag';
|
|
8
|
-
import {
|
|
8
|
+
import { ofetch } from 'ofetch';
|
|
9
9
|
import { resolve, join, parse } from 'pathe';
|
|
10
10
|
|
|
11
11
|
const Handlers = {
|
|
@@ -335,6 +335,7 @@ function createIPX(userOptions) {
|
|
|
335
335
|
if (!id) {
|
|
336
336
|
throw createError({
|
|
337
337
|
statusCode: 400,
|
|
338
|
+
statusText: `IPX_MISSING_ID`,
|
|
338
339
|
message: `Resource id is missing`
|
|
339
340
|
});
|
|
340
341
|
}
|
|
@@ -348,6 +349,7 @@ function createIPX(userOptions) {
|
|
|
348
349
|
if (!storage) {
|
|
349
350
|
throw createError({
|
|
350
351
|
statusCode: 500,
|
|
352
|
+
statusText: `IPX_NO_STORAGE`,
|
|
351
353
|
message: "No storage configured!"
|
|
352
354
|
});
|
|
353
355
|
}
|
|
@@ -356,6 +358,7 @@ function createIPX(userOptions) {
|
|
|
356
358
|
if (!sourceMeta) {
|
|
357
359
|
throw createError({
|
|
358
360
|
statusCode: 404,
|
|
361
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
359
362
|
message: `Resource not found: ${id}`
|
|
360
363
|
});
|
|
361
364
|
}
|
|
@@ -369,6 +372,7 @@ function createIPX(userOptions) {
|
|
|
369
372
|
if (!sourceData) {
|
|
370
373
|
throw createError({
|
|
371
374
|
statusCode: 404,
|
|
375
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
372
376
|
message: `Resource not found: ${id}`
|
|
373
377
|
});
|
|
374
378
|
}
|
|
@@ -376,7 +380,16 @@ function createIPX(userOptions) {
|
|
|
376
380
|
});
|
|
377
381
|
const process = cachedPromise(async () => {
|
|
378
382
|
const sourceData = await getSourceData();
|
|
379
|
-
|
|
383
|
+
let imageMeta$1;
|
|
384
|
+
try {
|
|
385
|
+
imageMeta$1 = imageMeta(sourceData);
|
|
386
|
+
} catch {
|
|
387
|
+
throw createError({
|
|
388
|
+
statusCode: 400,
|
|
389
|
+
statusText: `IPX_INVALID_IMAGE`,
|
|
390
|
+
message: `Cannot parse image metadata: ${id}`
|
|
391
|
+
});
|
|
392
|
+
}
|
|
380
393
|
let mFormat = modifiers.f || modifiers.format;
|
|
381
394
|
if (mFormat === "jpg") {
|
|
382
395
|
mFormat = "jpeg";
|
|
@@ -432,7 +445,7 @@ function createIPX(userOptions) {
|
|
|
432
445
|
const MODIFIER_SEP = /[&,]/g;
|
|
433
446
|
const MODIFIER_VAL_SEP = /[:=_]/;
|
|
434
447
|
function createIPXH3Handler(ipx) {
|
|
435
|
-
|
|
448
|
+
const _handler = async (event) => {
|
|
436
449
|
const [modifiersString = "", ...idSegments] = event.path.slice(
|
|
437
450
|
1
|
|
438
451
|
/* leading slash */
|
|
@@ -441,12 +454,14 @@ function createIPXH3Handler(ipx) {
|
|
|
441
454
|
if (!modifiersString) {
|
|
442
455
|
throw createError({
|
|
443
456
|
statusCode: 400,
|
|
457
|
+
statusText: `IPX_MISSING_MODIFIERS`,
|
|
444
458
|
message: `Modifiers are missing: ${id}`
|
|
445
459
|
});
|
|
446
460
|
}
|
|
447
461
|
if (!id || id === "/") {
|
|
448
462
|
throw createError({
|
|
449
463
|
statusCode: 400,
|
|
464
|
+
statusText: `IPX_MISSING_ID`,
|
|
450
465
|
message: `Resource id is missing: ${event.path}`
|
|
451
466
|
});
|
|
452
467
|
}
|
|
@@ -499,6 +514,19 @@ function createIPXH3Handler(ipx) {
|
|
|
499
514
|
}
|
|
500
515
|
setResponseHeader(event, "content-security-policy", "default-src 'none'");
|
|
501
516
|
return data;
|
|
517
|
+
};
|
|
518
|
+
return defineEventHandler(async (event) => {
|
|
519
|
+
try {
|
|
520
|
+
return await _handler(event);
|
|
521
|
+
} catch (_error) {
|
|
522
|
+
const error = createError(_error);
|
|
523
|
+
setResponseStatus(event, error.statusCode, error.statusMessage);
|
|
524
|
+
return {
|
|
525
|
+
error: {
|
|
526
|
+
message: `[${error.statusCode}] [${error.statusMessage || "IPX_ERROR"}] ${error.message}`
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
502
530
|
});
|
|
503
531
|
}
|
|
504
532
|
function createIPXH3App(ipx) {
|
|
@@ -557,24 +585,20 @@ function ipxHttpStorage(_options = {}) {
|
|
|
557
585
|
if (!url.hostname) {
|
|
558
586
|
throw createError({
|
|
559
587
|
statusCode: 403,
|
|
588
|
+
statusText: `IPX_MISSING_HOSTNAME`,
|
|
560
589
|
message: `Hostname is missing: ${id}`
|
|
561
590
|
});
|
|
562
591
|
}
|
|
563
592
|
if (!allowAllDomains && !domains.has(url.hostname)) {
|
|
564
593
|
throw createError({
|
|
565
594
|
statusCode: 403,
|
|
595
|
+
statusText: `IPX_FORBIDDEN_HOST`,
|
|
566
596
|
message: `Forbidden host: ${url.hostname}`
|
|
567
597
|
});
|
|
568
598
|
}
|
|
569
599
|
return url.toString();
|
|
570
600
|
}
|
|
571
601
|
function parseResponse(response) {
|
|
572
|
-
if (!response.ok) {
|
|
573
|
-
throw createError({
|
|
574
|
-
statusCode: response.status || 500,
|
|
575
|
-
message: `Fetch error: ${response.statusText}`
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
602
|
let maxAge = defaultMaxAge;
|
|
579
603
|
const _cacheControl = response.headers.get("cache-control");
|
|
580
604
|
if (_cacheControl) {
|
|
@@ -595,7 +619,10 @@ function ipxHttpStorage(_options = {}) {
|
|
|
595
619
|
async getMeta(id) {
|
|
596
620
|
const url = validateId(id);
|
|
597
621
|
try {
|
|
598
|
-
const response = await
|
|
622
|
+
const response = await ofetch.raw(url, {
|
|
623
|
+
...fetchOptions,
|
|
624
|
+
method: "HEAD"
|
|
625
|
+
});
|
|
599
626
|
const { maxAge, mtime } = parseResponse(response);
|
|
600
627
|
return { mtime, maxAge };
|
|
601
628
|
} catch {
|
|
@@ -604,8 +631,12 @@ function ipxHttpStorage(_options = {}) {
|
|
|
604
631
|
},
|
|
605
632
|
async getData(id) {
|
|
606
633
|
const url = validateId(id);
|
|
607
|
-
const response = await
|
|
608
|
-
|
|
634
|
+
const response = await ofetch(url, {
|
|
635
|
+
...fetchOptions,
|
|
636
|
+
method: "GET",
|
|
637
|
+
responseType: "arrayBuffer"
|
|
638
|
+
});
|
|
639
|
+
return response;
|
|
609
640
|
}
|
|
610
641
|
};
|
|
611
642
|
}
|
|
@@ -618,6 +649,7 @@ function ipxFSStorage(_options = {}) {
|
|
|
618
649
|
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
619
650
|
throw createError({
|
|
620
651
|
statusCode: 403,
|
|
652
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
621
653
|
message: `Forbidden path: ${id}`
|
|
622
654
|
});
|
|
623
655
|
}
|
|
@@ -635,16 +667,19 @@ function ipxFSStorage(_options = {}) {
|
|
|
635
667
|
} catch (error) {
|
|
636
668
|
throw error.code === "ENOENT" ? createError({
|
|
637
669
|
statusCode: 404,
|
|
638
|
-
|
|
670
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
671
|
+
message: `File not found: ${id}`
|
|
639
672
|
}) : createError({
|
|
640
673
|
statusCode: 403,
|
|
641
|
-
|
|
674
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
675
|
+
message: `File access forbidden: (${error.code}) ${id}`
|
|
642
676
|
});
|
|
643
677
|
}
|
|
644
678
|
if (!stats.isFile()) {
|
|
645
679
|
throw createError({
|
|
646
680
|
statusCode: 400,
|
|
647
|
-
|
|
681
|
+
statusText: `IPX_INVALID_FILE`,
|
|
682
|
+
message: `Path should be a file: ${id}`
|
|
648
683
|
});
|
|
649
684
|
}
|
|
650
685
|
return {
|
|
@@ -7,7 +7,7 @@ const h3 = require('h3');
|
|
|
7
7
|
const destr = require('destr');
|
|
8
8
|
const acceptNegotiator = require('@fastify/accept-negotiator');
|
|
9
9
|
const getEtag = require('etag');
|
|
10
|
-
const
|
|
10
|
+
const ofetch = require('ofetch');
|
|
11
11
|
const pathe = require('pathe');
|
|
12
12
|
|
|
13
13
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
@@ -342,6 +342,7 @@ function createIPX(userOptions) {
|
|
|
342
342
|
if (!id) {
|
|
343
343
|
throw h3.createError({
|
|
344
344
|
statusCode: 400,
|
|
345
|
+
statusText: `IPX_MISSING_ID`,
|
|
345
346
|
message: `Resource id is missing`
|
|
346
347
|
});
|
|
347
348
|
}
|
|
@@ -355,6 +356,7 @@ function createIPX(userOptions) {
|
|
|
355
356
|
if (!storage) {
|
|
356
357
|
throw h3.createError({
|
|
357
358
|
statusCode: 500,
|
|
359
|
+
statusText: `IPX_NO_STORAGE`,
|
|
358
360
|
message: "No storage configured!"
|
|
359
361
|
});
|
|
360
362
|
}
|
|
@@ -363,6 +365,7 @@ function createIPX(userOptions) {
|
|
|
363
365
|
if (!sourceMeta) {
|
|
364
366
|
throw h3.createError({
|
|
365
367
|
statusCode: 404,
|
|
368
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
366
369
|
message: `Resource not found: ${id}`
|
|
367
370
|
});
|
|
368
371
|
}
|
|
@@ -376,6 +379,7 @@ function createIPX(userOptions) {
|
|
|
376
379
|
if (!sourceData) {
|
|
377
380
|
throw h3.createError({
|
|
378
381
|
statusCode: 404,
|
|
382
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
379
383
|
message: `Resource not found: ${id}`
|
|
380
384
|
});
|
|
381
385
|
}
|
|
@@ -383,7 +387,16 @@ function createIPX(userOptions) {
|
|
|
383
387
|
});
|
|
384
388
|
const process = cachedPromise(async () => {
|
|
385
389
|
const sourceData = await getSourceData();
|
|
386
|
-
|
|
390
|
+
let imageMeta$1;
|
|
391
|
+
try {
|
|
392
|
+
imageMeta$1 = imageMeta.imageMeta(sourceData);
|
|
393
|
+
} catch {
|
|
394
|
+
throw h3.createError({
|
|
395
|
+
statusCode: 400,
|
|
396
|
+
statusText: `IPX_INVALID_IMAGE`,
|
|
397
|
+
message: `Cannot parse image metadata: ${id}`
|
|
398
|
+
});
|
|
399
|
+
}
|
|
387
400
|
let mFormat = modifiers.f || modifiers.format;
|
|
388
401
|
if (mFormat === "jpg") {
|
|
389
402
|
mFormat = "jpeg";
|
|
@@ -439,7 +452,7 @@ function createIPX(userOptions) {
|
|
|
439
452
|
const MODIFIER_SEP = /[&,]/g;
|
|
440
453
|
const MODIFIER_VAL_SEP = /[:=_]/;
|
|
441
454
|
function createIPXH3Handler(ipx) {
|
|
442
|
-
|
|
455
|
+
const _handler = async (event) => {
|
|
443
456
|
const [modifiersString = "", ...idSegments] = event.path.slice(
|
|
444
457
|
1
|
|
445
458
|
/* leading slash */
|
|
@@ -448,12 +461,14 @@ function createIPXH3Handler(ipx) {
|
|
|
448
461
|
if (!modifiersString) {
|
|
449
462
|
throw h3.createError({
|
|
450
463
|
statusCode: 400,
|
|
464
|
+
statusText: `IPX_MISSING_MODIFIERS`,
|
|
451
465
|
message: `Modifiers are missing: ${id}`
|
|
452
466
|
});
|
|
453
467
|
}
|
|
454
468
|
if (!id || id === "/") {
|
|
455
469
|
throw h3.createError({
|
|
456
470
|
statusCode: 400,
|
|
471
|
+
statusText: `IPX_MISSING_ID`,
|
|
457
472
|
message: `Resource id is missing: ${event.path}`
|
|
458
473
|
});
|
|
459
474
|
}
|
|
@@ -506,6 +521,19 @@ function createIPXH3Handler(ipx) {
|
|
|
506
521
|
}
|
|
507
522
|
h3.setResponseHeader(event, "content-security-policy", "default-src 'none'");
|
|
508
523
|
return data;
|
|
524
|
+
};
|
|
525
|
+
return h3.defineEventHandler(async (event) => {
|
|
526
|
+
try {
|
|
527
|
+
return await _handler(event);
|
|
528
|
+
} catch (_error) {
|
|
529
|
+
const error = h3.createError(_error);
|
|
530
|
+
h3.setResponseStatus(event, error.statusCode, error.statusMessage);
|
|
531
|
+
return {
|
|
532
|
+
error: {
|
|
533
|
+
message: `[${error.statusCode}] [${error.statusMessage || "IPX_ERROR"}] ${error.message}`
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
509
537
|
});
|
|
510
538
|
}
|
|
511
539
|
function createIPXH3App(ipx) {
|
|
@@ -564,24 +592,20 @@ function ipxHttpStorage(_options = {}) {
|
|
|
564
592
|
if (!url.hostname) {
|
|
565
593
|
throw h3.createError({
|
|
566
594
|
statusCode: 403,
|
|
595
|
+
statusText: `IPX_MISSING_HOSTNAME`,
|
|
567
596
|
message: `Hostname is missing: ${id}`
|
|
568
597
|
});
|
|
569
598
|
}
|
|
570
599
|
if (!allowAllDomains && !domains.has(url.hostname)) {
|
|
571
600
|
throw h3.createError({
|
|
572
601
|
statusCode: 403,
|
|
602
|
+
statusText: `IPX_FORBIDDEN_HOST`,
|
|
573
603
|
message: `Forbidden host: ${url.hostname}`
|
|
574
604
|
});
|
|
575
605
|
}
|
|
576
606
|
return url.toString();
|
|
577
607
|
}
|
|
578
608
|
function parseResponse(response) {
|
|
579
|
-
if (!response.ok) {
|
|
580
|
-
throw h3.createError({
|
|
581
|
-
statusCode: response.status || 500,
|
|
582
|
-
message: `Fetch error: ${response.statusText}`
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
609
|
let maxAge = defaultMaxAge;
|
|
586
610
|
const _cacheControl = response.headers.get("cache-control");
|
|
587
611
|
if (_cacheControl) {
|
|
@@ -602,7 +626,10 @@ function ipxHttpStorage(_options = {}) {
|
|
|
602
626
|
async getMeta(id) {
|
|
603
627
|
const url = validateId(id);
|
|
604
628
|
try {
|
|
605
|
-
const response = await
|
|
629
|
+
const response = await ofetch.ofetch.raw(url, {
|
|
630
|
+
...fetchOptions,
|
|
631
|
+
method: "HEAD"
|
|
632
|
+
});
|
|
606
633
|
const { maxAge, mtime } = parseResponse(response);
|
|
607
634
|
return { mtime, maxAge };
|
|
608
635
|
} catch {
|
|
@@ -611,8 +638,12 @@ function ipxHttpStorage(_options = {}) {
|
|
|
611
638
|
},
|
|
612
639
|
async getData(id) {
|
|
613
640
|
const url = validateId(id);
|
|
614
|
-
const response = await
|
|
615
|
-
|
|
641
|
+
const response = await ofetch.ofetch(url, {
|
|
642
|
+
...fetchOptions,
|
|
643
|
+
method: "GET",
|
|
644
|
+
responseType: "arrayBuffer"
|
|
645
|
+
});
|
|
646
|
+
return response;
|
|
616
647
|
}
|
|
617
648
|
};
|
|
618
649
|
}
|
|
@@ -625,6 +656,7 @@ function ipxFSStorage(_options = {}) {
|
|
|
625
656
|
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
626
657
|
throw h3.createError({
|
|
627
658
|
statusCode: 403,
|
|
659
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
628
660
|
message: `Forbidden path: ${id}`
|
|
629
661
|
});
|
|
630
662
|
}
|
|
@@ -642,16 +674,19 @@ function ipxFSStorage(_options = {}) {
|
|
|
642
674
|
} catch (error) {
|
|
643
675
|
throw error.code === "ENOENT" ? h3.createError({
|
|
644
676
|
statusCode: 404,
|
|
645
|
-
|
|
677
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
678
|
+
message: `File not found: ${id}`
|
|
646
679
|
}) : h3.createError({
|
|
647
680
|
statusCode: 403,
|
|
648
|
-
|
|
681
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
682
|
+
message: `File access forbidden: (${error.code}) ${id}`
|
|
649
683
|
});
|
|
650
684
|
}
|
|
651
685
|
if (!stats.isFile()) {
|
|
652
686
|
throw h3.createError({
|
|
653
687
|
statusCode: 400,
|
|
654
|
-
|
|
688
|
+
statusText: `IPX_INVALID_FILE`,
|
|
689
|
+
message: `Path should be a file: ${id}`
|
|
655
690
|
});
|
|
656
691
|
}
|
|
657
692
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-1",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"description": "High performance, secure and easy-to-use image optimizer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
"dist",
|
|
25
25
|
"bin"
|
|
26
26
|
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "unbuild",
|
|
29
|
+
"dev": "listhen -w playground",
|
|
30
|
+
"ipx": "jiti ./src/cli.ts",
|
|
31
|
+
"lint": "eslint --ext .ts . && prettier -c src test",
|
|
32
|
+
"lint:fix": "eslint --ext .ts . --fix && prettier -w src test",
|
|
33
|
+
"prepack": "pnpm build",
|
|
34
|
+
"release": "pnpm test && changelogen --release --push && npm publish",
|
|
35
|
+
"prerelease": "pnpm test && pnpm build && changelogen --release --prerelease --push --publish --publishTag next-2",
|
|
36
|
+
"start": "node bin/ipx.js",
|
|
37
|
+
"test": "pnpm lint && vitest run --coverage"
|
|
38
|
+
},
|
|
27
39
|
"dependencies": {
|
|
28
40
|
"@fastify/accept-negotiator": "^1.1.0",
|
|
29
41
|
"citty": "^0.1.4",
|
|
@@ -31,39 +43,28 @@
|
|
|
31
43
|
"defu": "^6.1.2",
|
|
32
44
|
"destr": "^2.0.1",
|
|
33
45
|
"etag": "^1.8.1",
|
|
34
|
-
"h3": "^1.8.
|
|
46
|
+
"h3": "^1.8.2",
|
|
35
47
|
"image-meta": "^0.1.1",
|
|
36
|
-
"listhen": "^1.5.
|
|
37
|
-
"
|
|
48
|
+
"listhen": "^1.5.5",
|
|
49
|
+
"ofetch": "^1.3.3",
|
|
38
50
|
"pathe": "^1.1.1",
|
|
39
|
-
"sharp": "^0.32.
|
|
51
|
+
"sharp": "^0.32.6",
|
|
40
52
|
"ufo": "^1.3.0",
|
|
41
53
|
"unstorage": "^1.9.0"
|
|
42
54
|
},
|
|
43
55
|
"devDependencies": {
|
|
44
56
|
"@types/etag": "^1.8.1",
|
|
45
57
|
"@types/is-valid-path": "^0.1.0",
|
|
46
|
-
"@vitest/coverage-v8": "^0.34.
|
|
58
|
+
"@vitest/coverage-v8": "^0.34.5",
|
|
47
59
|
"changelogen": "^0.5.5",
|
|
48
|
-
"eslint": "^8.
|
|
60
|
+
"eslint": "^8.50.0",
|
|
49
61
|
"eslint-config-unjs": "^0.2.1",
|
|
50
62
|
"jiti": "^1.20.0",
|
|
51
63
|
"prettier": "^3.0.3",
|
|
52
64
|
"serve-handler": "^6.1.5",
|
|
53
65
|
"typescript": "^5.2.2",
|
|
54
66
|
"unbuild": "^2.0.0",
|
|
55
|
-
"vitest": "^0.34.
|
|
67
|
+
"vitest": "^0.34.5"
|
|
56
68
|
},
|
|
57
|
-
"packageManager": "pnpm@8.
|
|
58
|
-
"scripts": {
|
|
59
|
-
"build": "unbuild",
|
|
60
|
-
"dev": "listhen -w playground",
|
|
61
|
-
"ipx": "jiti ./src/cli.ts",
|
|
62
|
-
"lint": "eslint --ext .ts . && prettier -c src test",
|
|
63
|
-
"lint:fix": "eslint --ext .ts . --fix && prettier -w src test",
|
|
64
|
-
"release": "pnpm test && changelogen --release --push && npm publish",
|
|
65
|
-
"prerelease": "pnpm test && pnpm build && changelogen --release --prerelease --push --publish --publishTag v2",
|
|
66
|
-
"start": "node bin/ipx.js",
|
|
67
|
-
"test": "pnpm lint && vitest run --coverage"
|
|
68
|
-
}
|
|
69
|
+
"packageManager": "pnpm@8.8.0"
|
|
69
70
|
}
|