ipx 2.0.0-0 → 2.0.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/LICENSE +0 -0
- package/README.md +32 -17
- package/dist/chunks/svgo-xss.cjs +103 -0
- package/dist/chunks/svgo-xss.mjs +101 -0
- package/dist/cli.cjs +4 -4
- package/dist/cli.mjs +4 -4
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +13 -11
- package/dist/index.d.mts +13 -11
- package/dist/index.d.ts +13 -11
- package/dist/index.mjs +3 -3
- package/dist/shared/{ipx.42c0c175.mjs → ipx.4d79c6c9.mjs} +116 -46
- package/dist/shared/{ipx.0fc4e4c7.cjs → ipx.ebaf2d0c.cjs} +115 -45
- package/package.json +26 -23
package/LICENSE
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
[![npm version][npm-version-src]][npm-version-href]
|
|
4
4
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
> [!NOTE]
|
|
7
|
+
> This is the active branch for IPX v2. Check out [ipx/v1](https://github.com/unjs/ipx/tree/v1) for v1 docs.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
High performance, secure and easy-to-use image optimizer powered by [sharp](https://github.com/lovell/sharp) and [svgo](https://github.com/svg/svgo).
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
> This is the development branch for IPX v2. Check out [ipx/v1](https://github.com/unjs/ipx/tree/v1) for latest stable docs.
|
|
11
|
+
Used by [Nuxt Image](https://image.nuxt.com/) and [Netlify](https://www.npmjs.com/package/@netlify/ipx) and open to everyone!
|
|
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 serve --dir ./
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Usin `bun`
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
bun x ipx
|
|
26
|
+
bun x npx ipx serve --dir ./
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
The default serve directory is the current working directory.
|
|
@@ -33,28 +33,32 @@ The default serve directory is the current working directory.
|
|
|
33
33
|
You can use IPX as a middleware or directly use IPX interface.
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
createIPX,
|
|
38
|
-
createIPXMiddleware,
|
|
39
|
-
ipxFSStorage,
|
|
40
|
-
ipxHttpStorage,
|
|
41
|
-
} from "./src";
|
|
36
|
+
import { createIPX, ipxFSStorage, ipxHttpStorage } from "ipx";
|
|
42
37
|
|
|
43
38
|
const ipx = createIPX({
|
|
44
39
|
storage: ipxFSStorage({ dir: "./public" }),
|
|
45
40
|
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
|
|
46
41
|
});
|
|
47
|
-
|
|
48
|
-
const ipxMiddleware = createIPXMiddleware(ipx);
|
|
49
42
|
```
|
|
50
43
|
|
|
51
44
|
**Example**: Using with [unjs/h3](https://github.com/unjs/h3):
|
|
52
45
|
|
|
53
46
|
```js
|
|
54
|
-
import { createIPX, createIPXMiddleware } from "ipx";
|
|
55
47
|
import { listen } from "listhen";
|
|
48
|
+
import { createApp, toNodeListener } from "h3";
|
|
49
|
+
import {
|
|
50
|
+
createIPX,
|
|
51
|
+
ipxFSStorage,
|
|
52
|
+
ipxHttpStorage,
|
|
53
|
+
createIPXH3Handler,
|
|
54
|
+
} from "ipx";
|
|
56
55
|
|
|
57
|
-
const
|
|
56
|
+
const ipx = createIPX({
|
|
57
|
+
storage: ipxFSStorage({ dir: "./public" }),
|
|
58
|
+
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const app = createApp().use("/", createIPXH3Handler(ipx));
|
|
58
62
|
|
|
59
63
|
listen(toNodeListener(app));
|
|
60
64
|
```
|
|
@@ -64,8 +68,19 @@ listen(toNodeListener(app));
|
|
|
64
68
|
```js
|
|
65
69
|
import { listen } from "listhen";
|
|
66
70
|
import express from "express";
|
|
71
|
+
import {
|
|
72
|
+
createIPX,
|
|
73
|
+
ipxFSStorage,
|
|
74
|
+
ipxHttpStorage,
|
|
75
|
+
createIPXNodeServer,
|
|
76
|
+
} from "ipx";
|
|
77
|
+
|
|
78
|
+
const ipx = createIPX({
|
|
79
|
+
storage: ipxFSStorage({ dir: "./public" }),
|
|
80
|
+
httpStorage: ipxHttpStorage({ domains: ["picsum.photos"] }),
|
|
81
|
+
});
|
|
67
82
|
|
|
68
|
-
const app = express().use("/",
|
|
83
|
+
const app = express().use("/", createIPXNodeServer(ipx));
|
|
69
84
|
|
|
70
85
|
listen(app);
|
|
71
86
|
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const xss = {
|
|
4
|
+
name: "removeXSS",
|
|
5
|
+
fn() {
|
|
6
|
+
return {
|
|
7
|
+
element: {
|
|
8
|
+
enter: (node, parentNode) => {
|
|
9
|
+
if (node.name === "script") {
|
|
10
|
+
parentNode.children = parentNode.children.filter(
|
|
11
|
+
(child) => child !== node
|
|
12
|
+
);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const event of ALL_EVENTS) {
|
|
16
|
+
for (const [name] of Object.entries(node.attributes)) {
|
|
17
|
+
if (name === event) {
|
|
18
|
+
delete node.attributes[name];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const ALL_EVENTS = [
|
|
28
|
+
"onbegin",
|
|
29
|
+
"onend",
|
|
30
|
+
"onrepeat",
|
|
31
|
+
"onabort",
|
|
32
|
+
"onerror",
|
|
33
|
+
"onresize",
|
|
34
|
+
"onscroll",
|
|
35
|
+
"onunload",
|
|
36
|
+
"onbegin",
|
|
37
|
+
"onend",
|
|
38
|
+
"onrepeat",
|
|
39
|
+
"oncancel",
|
|
40
|
+
"oncanplay",
|
|
41
|
+
"oncanplaythrough",
|
|
42
|
+
"onchange",
|
|
43
|
+
"onclick",
|
|
44
|
+
"onclose",
|
|
45
|
+
"oncuechange",
|
|
46
|
+
"ondblclick",
|
|
47
|
+
"ondrag",
|
|
48
|
+
"ondragend",
|
|
49
|
+
"ondragenter",
|
|
50
|
+
"ondragleave",
|
|
51
|
+
"ondragover",
|
|
52
|
+
"ondragstart",
|
|
53
|
+
"ondrop",
|
|
54
|
+
"ondurationchange",
|
|
55
|
+
"onemptied",
|
|
56
|
+
"onended",
|
|
57
|
+
"onerror",
|
|
58
|
+
"onfocus",
|
|
59
|
+
"oninput",
|
|
60
|
+
"oninvalid",
|
|
61
|
+
"onkeydown",
|
|
62
|
+
"onkeypress",
|
|
63
|
+
"onkeyup",
|
|
64
|
+
"onload",
|
|
65
|
+
"onloadeddata",
|
|
66
|
+
"onloadedmetadata",
|
|
67
|
+
"onloadstart",
|
|
68
|
+
"onmousedown",
|
|
69
|
+
"onmouseenter",
|
|
70
|
+
"onmouseleave",
|
|
71
|
+
"onmousemove",
|
|
72
|
+
"onmouseout",
|
|
73
|
+
"onmouseover",
|
|
74
|
+
"onmouseup",
|
|
75
|
+
"onmousewheel",
|
|
76
|
+
"onpause",
|
|
77
|
+
"onplay",
|
|
78
|
+
"onplaying",
|
|
79
|
+
"onprogress",
|
|
80
|
+
"onratechange",
|
|
81
|
+
"onreset",
|
|
82
|
+
"onresize",
|
|
83
|
+
"onscroll",
|
|
84
|
+
"onseeked",
|
|
85
|
+
"onseeking",
|
|
86
|
+
"onselect",
|
|
87
|
+
"onshow",
|
|
88
|
+
"onstalled",
|
|
89
|
+
"onsubmit",
|
|
90
|
+
"onsuspend",
|
|
91
|
+
"ontimeupdate",
|
|
92
|
+
"ontoggle",
|
|
93
|
+
"onvolumechange",
|
|
94
|
+
"onwaiting",
|
|
95
|
+
"oncopy",
|
|
96
|
+
"oncut",
|
|
97
|
+
"onpaste",
|
|
98
|
+
"onactivate",
|
|
99
|
+
"onfocusin",
|
|
100
|
+
"onfocusout"
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
exports.xss = xss;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const xss = {
|
|
2
|
+
name: "removeXSS",
|
|
3
|
+
fn() {
|
|
4
|
+
return {
|
|
5
|
+
element: {
|
|
6
|
+
enter: (node, parentNode) => {
|
|
7
|
+
if (node.name === "script") {
|
|
8
|
+
parentNode.children = parentNode.children.filter(
|
|
9
|
+
(child) => child !== node
|
|
10
|
+
);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
for (const event of ALL_EVENTS) {
|
|
14
|
+
for (const [name] of Object.entries(node.attributes)) {
|
|
15
|
+
if (name === event) {
|
|
16
|
+
delete node.attributes[name];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const ALL_EVENTS = [
|
|
26
|
+
"onbegin",
|
|
27
|
+
"onend",
|
|
28
|
+
"onrepeat",
|
|
29
|
+
"onabort",
|
|
30
|
+
"onerror",
|
|
31
|
+
"onresize",
|
|
32
|
+
"onscroll",
|
|
33
|
+
"onunload",
|
|
34
|
+
"onbegin",
|
|
35
|
+
"onend",
|
|
36
|
+
"onrepeat",
|
|
37
|
+
"oncancel",
|
|
38
|
+
"oncanplay",
|
|
39
|
+
"oncanplaythrough",
|
|
40
|
+
"onchange",
|
|
41
|
+
"onclick",
|
|
42
|
+
"onclose",
|
|
43
|
+
"oncuechange",
|
|
44
|
+
"ondblclick",
|
|
45
|
+
"ondrag",
|
|
46
|
+
"ondragend",
|
|
47
|
+
"ondragenter",
|
|
48
|
+
"ondragleave",
|
|
49
|
+
"ondragover",
|
|
50
|
+
"ondragstart",
|
|
51
|
+
"ondrop",
|
|
52
|
+
"ondurationchange",
|
|
53
|
+
"onemptied",
|
|
54
|
+
"onended",
|
|
55
|
+
"onerror",
|
|
56
|
+
"onfocus",
|
|
57
|
+
"oninput",
|
|
58
|
+
"oninvalid",
|
|
59
|
+
"onkeydown",
|
|
60
|
+
"onkeypress",
|
|
61
|
+
"onkeyup",
|
|
62
|
+
"onload",
|
|
63
|
+
"onloadeddata",
|
|
64
|
+
"onloadedmetadata",
|
|
65
|
+
"onloadstart",
|
|
66
|
+
"onmousedown",
|
|
67
|
+
"onmouseenter",
|
|
68
|
+
"onmouseleave",
|
|
69
|
+
"onmousemove",
|
|
70
|
+
"onmouseout",
|
|
71
|
+
"onmouseover",
|
|
72
|
+
"onmouseup",
|
|
73
|
+
"onmousewheel",
|
|
74
|
+
"onpause",
|
|
75
|
+
"onplay",
|
|
76
|
+
"onplaying",
|
|
77
|
+
"onprogress",
|
|
78
|
+
"onratechange",
|
|
79
|
+
"onreset",
|
|
80
|
+
"onresize",
|
|
81
|
+
"onscroll",
|
|
82
|
+
"onseeked",
|
|
83
|
+
"onseeking",
|
|
84
|
+
"onselect",
|
|
85
|
+
"onshow",
|
|
86
|
+
"onstalled",
|
|
87
|
+
"onsubmit",
|
|
88
|
+
"onsuspend",
|
|
89
|
+
"ontimeupdate",
|
|
90
|
+
"ontoggle",
|
|
91
|
+
"onvolumechange",
|
|
92
|
+
"onwaiting",
|
|
93
|
+
"oncopy",
|
|
94
|
+
"oncut",
|
|
95
|
+
"onpaste",
|
|
96
|
+
"onactivate",
|
|
97
|
+
"onfocusin",
|
|
98
|
+
"onfocusout"
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
export { xss };
|
package/dist/cli.cjs
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
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.ebaf2d0c.cjs');
|
|
7
7
|
require('defu');
|
|
8
|
-
require('image-meta');
|
|
9
8
|
require('ufo');
|
|
10
9
|
require('h3');
|
|
10
|
+
require('image-meta');
|
|
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";
|
|
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,19 +1,19 @@
|
|
|
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.4d79c6c9.mjs';
|
|
5
5
|
import 'defu';
|
|
6
|
-
import 'image-meta';
|
|
7
6
|
import 'ufo';
|
|
8
7
|
import 'h3';
|
|
8
|
+
import 'image-meta';
|
|
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";
|
|
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,14 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const nodeFs = require('./shared/ipx.
|
|
3
|
+
const nodeFs = require('./shared/ipx.ebaf2d0c.cjs');
|
|
4
4
|
require('defu');
|
|
5
|
-
require('image-meta');
|
|
6
5
|
require('ufo');
|
|
7
6
|
require('h3');
|
|
7
|
+
require('image-meta');
|
|
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
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Color, KernelEnum, Sharp, SharpOptions } from 'sharp';
|
|
2
|
+
import { ImageMeta } from 'image-meta';
|
|
3
|
+
import { Config } from 'svgo';
|
|
2
4
|
import * as h3 from 'h3';
|
|
3
5
|
import { Storage, Driver } from 'unstorage';
|
|
4
6
|
|
|
@@ -27,12 +29,6 @@ interface IPXStorage {
|
|
|
27
29
|
getMeta: (id: string, opts?: IPXStorageOptions) => MaybePromise<IPXStorageMeta | undefined>;
|
|
28
30
|
getData: (id: string, opts?: IPXStorageOptions) => MaybePromise<ArrayBuffer | undefined>;
|
|
29
31
|
}
|
|
30
|
-
interface ImageMeta {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
type: string;
|
|
34
|
-
mimeType: string;
|
|
35
|
-
}
|
|
36
32
|
|
|
37
33
|
declare const quality: Handler;
|
|
38
34
|
declare const fit: Handler;
|
|
@@ -114,9 +110,9 @@ type IPXSourceMeta = {
|
|
|
114
110
|
type IPX = (id: string, modifiers?: Partial<Record<HandlerName | "f" | "format" | "a" | "animated", string>>, requestOptions?: any) => {
|
|
115
111
|
getSourceMeta: () => Promise<IPXSourceMeta>;
|
|
116
112
|
process: () => Promise<{
|
|
117
|
-
data: Buffer;
|
|
118
|
-
meta
|
|
119
|
-
format
|
|
113
|
+
data: Buffer | string;
|
|
114
|
+
meta?: ImageMeta;
|
|
115
|
+
format?: string;
|
|
120
116
|
}>;
|
|
121
117
|
};
|
|
122
118
|
type IPXOptions = {
|
|
@@ -125,10 +121,15 @@ type IPXOptions = {
|
|
|
125
121
|
sharpOptions?: SharpOptions;
|
|
126
122
|
storage: IPXStorage;
|
|
127
123
|
httpStorage?: IPXStorage;
|
|
124
|
+
svgo?: false | Config;
|
|
128
125
|
};
|
|
129
126
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
127
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer |
|
|
128
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<string | void | Buffer | {
|
|
129
|
+
error: {
|
|
130
|
+
message: string;
|
|
131
|
+
};
|
|
132
|
+
}>>;
|
|
132
133
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
133
134
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
134
135
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
|
@@ -139,6 +140,7 @@ type HTTPStorageOptions = {
|
|
|
139
140
|
maxAge?: number;
|
|
140
141
|
domains?: string | string[];
|
|
141
142
|
allowAllDomains?: boolean;
|
|
143
|
+
ignoreCacheControl?: boolean;
|
|
142
144
|
};
|
|
143
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
144
146
|
|
|
@@ -150,4 +152,4 @@ declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
|
150
152
|
|
|
151
153
|
declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
|
|
152
154
|
|
|
153
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type
|
|
155
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Color, KernelEnum, Sharp, SharpOptions } from 'sharp';
|
|
2
|
+
import { ImageMeta } from 'image-meta';
|
|
3
|
+
import { Config } from 'svgo';
|
|
2
4
|
import * as h3 from 'h3';
|
|
3
5
|
import { Storage, Driver } from 'unstorage';
|
|
4
6
|
|
|
@@ -27,12 +29,6 @@ interface IPXStorage {
|
|
|
27
29
|
getMeta: (id: string, opts?: IPXStorageOptions) => MaybePromise<IPXStorageMeta | undefined>;
|
|
28
30
|
getData: (id: string, opts?: IPXStorageOptions) => MaybePromise<ArrayBuffer | undefined>;
|
|
29
31
|
}
|
|
30
|
-
interface ImageMeta {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
type: string;
|
|
34
|
-
mimeType: string;
|
|
35
|
-
}
|
|
36
32
|
|
|
37
33
|
declare const quality: Handler;
|
|
38
34
|
declare const fit: Handler;
|
|
@@ -114,9 +110,9 @@ type IPXSourceMeta = {
|
|
|
114
110
|
type IPX = (id: string, modifiers?: Partial<Record<HandlerName | "f" | "format" | "a" | "animated", string>>, requestOptions?: any) => {
|
|
115
111
|
getSourceMeta: () => Promise<IPXSourceMeta>;
|
|
116
112
|
process: () => Promise<{
|
|
117
|
-
data: Buffer;
|
|
118
|
-
meta
|
|
119
|
-
format
|
|
113
|
+
data: Buffer | string;
|
|
114
|
+
meta?: ImageMeta;
|
|
115
|
+
format?: string;
|
|
120
116
|
}>;
|
|
121
117
|
};
|
|
122
118
|
type IPXOptions = {
|
|
@@ -125,10 +121,15 @@ type IPXOptions = {
|
|
|
125
121
|
sharpOptions?: SharpOptions;
|
|
126
122
|
storage: IPXStorage;
|
|
127
123
|
httpStorage?: IPXStorage;
|
|
124
|
+
svgo?: false | Config;
|
|
128
125
|
};
|
|
129
126
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
127
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer |
|
|
128
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<string | void | Buffer | {
|
|
129
|
+
error: {
|
|
130
|
+
message: string;
|
|
131
|
+
};
|
|
132
|
+
}>>;
|
|
132
133
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
133
134
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
134
135
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
|
@@ -139,6 +140,7 @@ type HTTPStorageOptions = {
|
|
|
139
140
|
maxAge?: number;
|
|
140
141
|
domains?: string | string[];
|
|
141
142
|
allowAllDomains?: boolean;
|
|
143
|
+
ignoreCacheControl?: boolean;
|
|
142
144
|
};
|
|
143
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
144
146
|
|
|
@@ -150,4 +152,4 @@ declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
|
150
152
|
|
|
151
153
|
declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
|
|
152
154
|
|
|
153
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type
|
|
155
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Color, KernelEnum, Sharp, SharpOptions } from 'sharp';
|
|
2
|
+
import { ImageMeta } from 'image-meta';
|
|
3
|
+
import { Config } from 'svgo';
|
|
2
4
|
import * as h3 from 'h3';
|
|
3
5
|
import { Storage, Driver } from 'unstorage';
|
|
4
6
|
|
|
@@ -27,12 +29,6 @@ interface IPXStorage {
|
|
|
27
29
|
getMeta: (id: string, opts?: IPXStorageOptions) => MaybePromise<IPXStorageMeta | undefined>;
|
|
28
30
|
getData: (id: string, opts?: IPXStorageOptions) => MaybePromise<ArrayBuffer | undefined>;
|
|
29
31
|
}
|
|
30
|
-
interface ImageMeta {
|
|
31
|
-
width: number;
|
|
32
|
-
height: number;
|
|
33
|
-
type: string;
|
|
34
|
-
mimeType: string;
|
|
35
|
-
}
|
|
36
32
|
|
|
37
33
|
declare const quality: Handler;
|
|
38
34
|
declare const fit: Handler;
|
|
@@ -114,9 +110,9 @@ type IPXSourceMeta = {
|
|
|
114
110
|
type IPX = (id: string, modifiers?: Partial<Record<HandlerName | "f" | "format" | "a" | "animated", string>>, requestOptions?: any) => {
|
|
115
111
|
getSourceMeta: () => Promise<IPXSourceMeta>;
|
|
116
112
|
process: () => Promise<{
|
|
117
|
-
data: Buffer;
|
|
118
|
-
meta
|
|
119
|
-
format
|
|
113
|
+
data: Buffer | string;
|
|
114
|
+
meta?: ImageMeta;
|
|
115
|
+
format?: string;
|
|
120
116
|
}>;
|
|
121
117
|
};
|
|
122
118
|
type IPXOptions = {
|
|
@@ -125,10 +121,15 @@ type IPXOptions = {
|
|
|
125
121
|
sharpOptions?: SharpOptions;
|
|
126
122
|
storage: IPXStorage;
|
|
127
123
|
httpStorage?: IPXStorage;
|
|
124
|
+
svgo?: false | Config;
|
|
128
125
|
};
|
|
129
126
|
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
130
127
|
|
|
131
|
-
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<Buffer |
|
|
128
|
+
declare function createIPXH3Handler(ipx: IPX): h3.EventHandler<h3.EventHandlerRequest, Promise<string | void | Buffer | {
|
|
129
|
+
error: {
|
|
130
|
+
message: string;
|
|
131
|
+
};
|
|
132
|
+
}>>;
|
|
132
133
|
declare function createIPXH3App(ipx: IPX): h3.App;
|
|
133
134
|
declare function createIPXWebServer(ipx: IPX): h3.WebHandler;
|
|
134
135
|
declare function createIPXNodeServer(ipx: IPX): h3.NodeListener;
|
|
@@ -139,6 +140,7 @@ type HTTPStorageOptions = {
|
|
|
139
140
|
maxAge?: number;
|
|
140
141
|
domains?: string | string[];
|
|
141
142
|
allowAllDomains?: boolean;
|
|
143
|
+
ignoreCacheControl?: boolean;
|
|
142
144
|
};
|
|
143
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
144
146
|
|
|
@@ -150,4 +152,4 @@ declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
|
150
152
|
|
|
151
153
|
declare function unstorageToIPXStorage(storage: Storage | Driver, prefix: string): IPXStorage;
|
|
152
154
|
|
|
153
|
-
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type
|
|
155
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
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.4d79c6c9.mjs';
|
|
2
2
|
import 'defu';
|
|
3
|
-
import 'image-meta';
|
|
4
3
|
import 'ufo';
|
|
5
4
|
import 'h3';
|
|
5
|
+
import 'image-meta';
|
|
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
|
-
import { imageMeta } from 'image-meta';
|
|
3
2
|
import { withLeadingSlash, hasProtocol, joinURL, decode } from 'ufo';
|
|
4
|
-
import { createError, defineEventHandler,
|
|
3
|
+
import { createError, defineEventHandler, setResponseStatus, createApp, toWebHandler, toNodeListener, toPlainHandler, getRequestHeader, appendResponseHeader, send, getResponseHeader, setResponseHeader } from 'h3';
|
|
4
|
+
import { imageMeta } from 'image-meta';
|
|
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 = {
|
|
@@ -62,11 +62,11 @@ function applyHandler(context, pipe, handler, argumentsString) {
|
|
|
62
62
|
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
63
63
|
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
64
64
|
let { width, height } = desiredDimensions;
|
|
65
|
-
if (width > sourceDimensions.width) {
|
|
65
|
+
if (sourceDimensions.width && width > sourceDimensions.width) {
|
|
66
66
|
width = sourceDimensions.width;
|
|
67
67
|
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
68
68
|
}
|
|
69
|
-
if (height > sourceDimensions.height) {
|
|
69
|
+
if (sourceDimensions.height && height > sourceDimensions.height) {
|
|
70
70
|
height = sourceDimensions.height;
|
|
71
71
|
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
72
72
|
}
|
|
@@ -317,7 +317,7 @@ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
|
|
|
317
317
|
function createIPX(userOptions) {
|
|
318
318
|
const options = defu(userOptions, {
|
|
319
319
|
alias: getEnv("IPX_ALIAS") || {},
|
|
320
|
-
maxAge: getEnv("IPX_MAX_AGE")
|
|
320
|
+
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
321
321
|
sharpOptions: {}
|
|
322
322
|
});
|
|
323
323
|
options.alias = Object.fromEntries(
|
|
@@ -331,10 +331,16 @@ function createIPX(userOptions) {
|
|
|
331
331
|
(r) => r.default || r
|
|
332
332
|
);
|
|
333
333
|
});
|
|
334
|
+
const getSVGO = cachedPromise(async () => {
|
|
335
|
+
const { optimize } = await import('svgo');
|
|
336
|
+
const { xss } = await import('../chunks/svgo-xss.mjs');
|
|
337
|
+
return { optimize, xss };
|
|
338
|
+
});
|
|
334
339
|
return function ipx(id, modifiers = {}, opts = {}) {
|
|
335
340
|
if (!id) {
|
|
336
341
|
throw createError({
|
|
337
342
|
statusCode: 400,
|
|
343
|
+
statusText: `IPX_MISSING_ID`,
|
|
338
344
|
message: `Resource id is missing`
|
|
339
345
|
});
|
|
340
346
|
}
|
|
@@ -348,6 +354,7 @@ function createIPX(userOptions) {
|
|
|
348
354
|
if (!storage) {
|
|
349
355
|
throw createError({
|
|
350
356
|
statusCode: 500,
|
|
357
|
+
statusText: `IPX_NO_STORAGE`,
|
|
351
358
|
message: "No storage configured!"
|
|
352
359
|
});
|
|
353
360
|
}
|
|
@@ -356,11 +363,13 @@ function createIPX(userOptions) {
|
|
|
356
363
|
if (!sourceMeta) {
|
|
357
364
|
throw createError({
|
|
358
365
|
statusCode: 404,
|
|
366
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
359
367
|
message: `Resource not found: ${id}`
|
|
360
368
|
});
|
|
361
369
|
}
|
|
370
|
+
const _maxAge = sourceMeta.maxAge ?? options.maxAge;
|
|
362
371
|
return {
|
|
363
|
-
maxAge: typeof
|
|
372
|
+
maxAge: typeof _maxAge === "string" ? Number.parseInt(_maxAge) : _maxAge,
|
|
364
373
|
mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
|
|
365
374
|
};
|
|
366
375
|
});
|
|
@@ -369,6 +378,7 @@ function createIPX(userOptions) {
|
|
|
369
378
|
if (!sourceData) {
|
|
370
379
|
throw createError({
|
|
371
380
|
statusCode: 404,
|
|
381
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
372
382
|
message: `Resource not found: ${id}`
|
|
373
383
|
});
|
|
374
384
|
}
|
|
@@ -376,18 +386,40 @@ function createIPX(userOptions) {
|
|
|
376
386
|
});
|
|
377
387
|
const process = cachedPromise(async () => {
|
|
378
388
|
const sourceData = await getSourceData();
|
|
379
|
-
|
|
389
|
+
let imageMeta$1;
|
|
390
|
+
try {
|
|
391
|
+
imageMeta$1 = imageMeta(sourceData);
|
|
392
|
+
} catch {
|
|
393
|
+
throw createError({
|
|
394
|
+
statusCode: 400,
|
|
395
|
+
statusText: `IPX_INVALID_IMAGE`,
|
|
396
|
+
message: `Cannot parse image metadata: ${id}`
|
|
397
|
+
});
|
|
398
|
+
}
|
|
380
399
|
let mFormat = modifiers.f || modifiers.format;
|
|
381
400
|
if (mFormat === "jpg") {
|
|
382
401
|
mFormat = "jpeg";
|
|
383
402
|
}
|
|
384
|
-
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type) ? imageMeta$1.type : "jpeg";
|
|
403
|
+
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type || "") ? imageMeta$1.type : "jpeg";
|
|
385
404
|
if (imageMeta$1.type === "svg" && !mFormat) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
405
|
+
if (options.svgo === false) {
|
|
406
|
+
return {
|
|
407
|
+
data: sourceData,
|
|
408
|
+
format: "svg+xml",
|
|
409
|
+
meta: imageMeta$1
|
|
410
|
+
};
|
|
411
|
+
} else {
|
|
412
|
+
const { optimize, xss } = await getSVGO();
|
|
413
|
+
const svg = optimize(sourceData.toString("utf8"), {
|
|
414
|
+
...options.svgo,
|
|
415
|
+
plugins: [xss, ...options.svgo?.plugins || []]
|
|
416
|
+
}).data;
|
|
417
|
+
return {
|
|
418
|
+
data: svg,
|
|
419
|
+
format: "svg+xml",
|
|
420
|
+
meta: imageMeta$1
|
|
421
|
+
};
|
|
422
|
+
}
|
|
391
423
|
}
|
|
392
424
|
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
|
|
393
425
|
const Sharp = await getSharp();
|
|
@@ -409,7 +441,7 @@ function createIPX(userOptions) {
|
|
|
409
441
|
for (const h of handlers) {
|
|
410
442
|
sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
|
|
411
443
|
}
|
|
412
|
-
if (SUPPORTED_FORMATS.has(format)) {
|
|
444
|
+
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
413
445
|
sharp = sharp.toFormat(format, {
|
|
414
446
|
quality: handlerContext.quality,
|
|
415
447
|
progressive: format === "jpeg"
|
|
@@ -432,7 +464,7 @@ function createIPX(userOptions) {
|
|
|
432
464
|
const MODIFIER_SEP = /[&,]/g;
|
|
433
465
|
const MODIFIER_VAL_SEP = /[:=_]/;
|
|
434
466
|
function createIPXH3Handler(ipx) {
|
|
435
|
-
|
|
467
|
+
const _handler = async (event) => {
|
|
436
468
|
const [modifiersString = "", ...idSegments] = event.path.slice(
|
|
437
469
|
1
|
|
438
470
|
/* leading slash */
|
|
@@ -441,12 +473,14 @@ function createIPXH3Handler(ipx) {
|
|
|
441
473
|
if (!modifiersString) {
|
|
442
474
|
throw createError({
|
|
443
475
|
statusCode: 400,
|
|
476
|
+
statusText: `IPX_MISSING_MODIFIERS`,
|
|
444
477
|
message: `Modifiers are missing: ${id}`
|
|
445
478
|
});
|
|
446
479
|
}
|
|
447
480
|
if (!id || id === "/") {
|
|
448
481
|
throw createError({
|
|
449
482
|
statusCode: 400,
|
|
483
|
+
statusText: `IPX_MISSING_ID`,
|
|
450
484
|
message: `Resource id is missing: ${event.path}`
|
|
451
485
|
});
|
|
452
486
|
}
|
|
@@ -468,37 +502,59 @@ function createIPXH3Handler(ipx) {
|
|
|
468
502
|
delete modifiers.format;
|
|
469
503
|
if (autoFormat) {
|
|
470
504
|
modifiers.format = autoFormat;
|
|
471
|
-
|
|
505
|
+
appendResponseHeader(event, "vary", "Accept");
|
|
472
506
|
}
|
|
473
507
|
}
|
|
474
508
|
const img = ipx(id, modifiers);
|
|
475
509
|
const sourceMeta = await img.getSourceMeta();
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
setResponseHeader(event, "last-modified", sourceMeta.mtime.toUTCString());
|
|
482
|
-
}
|
|
510
|
+
sendResponseHeaderIfNotSet(
|
|
511
|
+
event,
|
|
512
|
+
"content-security-policy",
|
|
513
|
+
"default-src 'none'"
|
|
514
|
+
);
|
|
483
515
|
if (typeof sourceMeta.maxAge === "number") {
|
|
484
|
-
|
|
516
|
+
sendResponseHeaderIfNotSet(
|
|
485
517
|
event,
|
|
486
518
|
"cache-control",
|
|
487
519
|
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
488
520
|
);
|
|
489
521
|
}
|
|
522
|
+
if (sourceMeta.mtime) {
|
|
523
|
+
sendResponseHeaderIfNotSet(
|
|
524
|
+
event,
|
|
525
|
+
"last-modified",
|
|
526
|
+
sourceMeta.mtime.toUTCString()
|
|
527
|
+
);
|
|
528
|
+
const _ifModifiedSince = getRequestHeader(event, "if-modified-since");
|
|
529
|
+
if (_ifModifiedSince && new Date(_ifModifiedSince) >= sourceMeta.mtime) {
|
|
530
|
+
setResponseStatus(event, 304);
|
|
531
|
+
return send(event);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
490
534
|
const { data, format } = await img.process();
|
|
491
535
|
const etag = getEtag(data);
|
|
492
|
-
|
|
536
|
+
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
493
537
|
if (etag && getRequestHeader(event, "if-none-match") === etag) {
|
|
494
538
|
setResponseStatus(event, 304);
|
|
495
|
-
return
|
|
539
|
+
return send(event);
|
|
496
540
|
}
|
|
497
541
|
if (format) {
|
|
498
|
-
|
|
542
|
+
sendResponseHeaderIfNotSet(event, "content-type", `image/${format}`);
|
|
499
543
|
}
|
|
500
|
-
setResponseHeader(event, "content-security-policy", "default-src 'none'");
|
|
501
544
|
return data;
|
|
545
|
+
};
|
|
546
|
+
return defineEventHandler(async (event) => {
|
|
547
|
+
try {
|
|
548
|
+
return await _handler(event);
|
|
549
|
+
} catch (_error) {
|
|
550
|
+
const error = createError(_error);
|
|
551
|
+
setResponseStatus(event, error.statusCode, error.statusMessage);
|
|
552
|
+
return {
|
|
553
|
+
error: {
|
|
554
|
+
message: `[${error.statusCode}] [${error.statusMessage || "IPX_ERROR"}] ${error.message}`
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
502
558
|
});
|
|
503
559
|
}
|
|
504
560
|
function createIPXH3App(ipx) {
|
|
@@ -515,6 +571,11 @@ function createIPXNodeServer(ipx) {
|
|
|
515
571
|
function createIPXPlainServer(ipx) {
|
|
516
572
|
return toPlainHandler(createIPXH3App(ipx));
|
|
517
573
|
}
|
|
574
|
+
function sendResponseHeaderIfNotSet(event, name, value) {
|
|
575
|
+
if (!getResponseHeader(event, name)) {
|
|
576
|
+
setResponseHeader(event, name, value);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
518
579
|
function autoDetectFormat(acceptHeader, animated) {
|
|
519
580
|
if (animated) {
|
|
520
581
|
const acceptMime2 = negotiate(acceptHeader, ["image/webp", "image/gif"]);
|
|
@@ -557,30 +618,28 @@ function ipxHttpStorage(_options = {}) {
|
|
|
557
618
|
if (!url.hostname) {
|
|
558
619
|
throw createError({
|
|
559
620
|
statusCode: 403,
|
|
621
|
+
statusText: `IPX_MISSING_HOSTNAME`,
|
|
560
622
|
message: `Hostname is missing: ${id}`
|
|
561
623
|
});
|
|
562
624
|
}
|
|
563
625
|
if (!allowAllDomains && !domains.has(url.hostname)) {
|
|
564
626
|
throw createError({
|
|
565
627
|
statusCode: 403,
|
|
628
|
+
statusText: `IPX_FORBIDDEN_HOST`,
|
|
566
629
|
message: `Forbidden host: ${url.hostname}`
|
|
567
630
|
});
|
|
568
631
|
}
|
|
569
632
|
return url.toString();
|
|
570
633
|
}
|
|
571
634
|
function parseResponse(response) {
|
|
572
|
-
if (!response.ok) {
|
|
573
|
-
throw createError({
|
|
574
|
-
statusCode: response.status || 500,
|
|
575
|
-
message: `Fetch error: ${response.statusText}`
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
635
|
let maxAge = defaultMaxAge;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
636
|
+
if (_options.ignoreCacheControl) {
|
|
637
|
+
const _cacheControl = response.headers.get("cache-control");
|
|
638
|
+
if (_cacheControl) {
|
|
639
|
+
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
640
|
+
if (m && m[1]) {
|
|
641
|
+
maxAge = Number.parseInt(m[1]);
|
|
642
|
+
}
|
|
584
643
|
}
|
|
585
644
|
}
|
|
586
645
|
let mtime;
|
|
@@ -595,7 +654,10 @@ function ipxHttpStorage(_options = {}) {
|
|
|
595
654
|
async getMeta(id) {
|
|
596
655
|
const url = validateId(id);
|
|
597
656
|
try {
|
|
598
|
-
const response = await
|
|
657
|
+
const response = await ofetch.raw(url, {
|
|
658
|
+
...fetchOptions,
|
|
659
|
+
method: "HEAD"
|
|
660
|
+
});
|
|
599
661
|
const { maxAge, mtime } = parseResponse(response);
|
|
600
662
|
return { mtime, maxAge };
|
|
601
663
|
} catch {
|
|
@@ -604,8 +666,12 @@ function ipxHttpStorage(_options = {}) {
|
|
|
604
666
|
},
|
|
605
667
|
async getData(id) {
|
|
606
668
|
const url = validateId(id);
|
|
607
|
-
const response = await
|
|
608
|
-
|
|
669
|
+
const response = await ofetch(url, {
|
|
670
|
+
...fetchOptions,
|
|
671
|
+
method: "GET",
|
|
672
|
+
responseType: "arrayBuffer"
|
|
673
|
+
});
|
|
674
|
+
return response;
|
|
609
675
|
}
|
|
610
676
|
};
|
|
611
677
|
}
|
|
@@ -618,6 +684,7 @@ function ipxFSStorage(_options = {}) {
|
|
|
618
684
|
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
619
685
|
throw createError({
|
|
620
686
|
statusCode: 403,
|
|
687
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
621
688
|
message: `Forbidden path: ${id}`
|
|
622
689
|
});
|
|
623
690
|
}
|
|
@@ -635,16 +702,19 @@ function ipxFSStorage(_options = {}) {
|
|
|
635
702
|
} catch (error) {
|
|
636
703
|
throw error.code === "ENOENT" ? createError({
|
|
637
704
|
statusCode: 404,
|
|
638
|
-
|
|
705
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
706
|
+
message: `File not found: ${id}`
|
|
639
707
|
}) : createError({
|
|
640
708
|
statusCode: 403,
|
|
641
|
-
|
|
709
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
710
|
+
message: `File access forbidden: (${error.code}) ${id}`
|
|
642
711
|
});
|
|
643
712
|
}
|
|
644
713
|
if (!stats.isFile()) {
|
|
645
714
|
throw createError({
|
|
646
715
|
statusCode: 400,
|
|
647
|
-
|
|
716
|
+
statusText: `IPX_INVALID_FILE`,
|
|
717
|
+
message: `Path should be a file: ${id}`
|
|
648
718
|
});
|
|
649
719
|
}
|
|
650
720
|
return {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const defu = require('defu');
|
|
4
|
-
const imageMeta = require('image-meta');
|
|
5
4
|
const ufo = require('ufo');
|
|
6
5
|
const h3 = require('h3');
|
|
6
|
+
const imageMeta = require('image-meta');
|
|
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; }
|
|
@@ -69,11 +69,11 @@ function applyHandler(context, pipe, handler, argumentsString) {
|
|
|
69
69
|
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
70
70
|
const desiredAspectRatio = desiredDimensions.width / desiredDimensions.height;
|
|
71
71
|
let { width, height } = desiredDimensions;
|
|
72
|
-
if (width > sourceDimensions.width) {
|
|
72
|
+
if (sourceDimensions.width && width > sourceDimensions.width) {
|
|
73
73
|
width = sourceDimensions.width;
|
|
74
74
|
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
75
75
|
}
|
|
76
|
-
if (height > sourceDimensions.height) {
|
|
76
|
+
if (sourceDimensions.height && height > sourceDimensions.height) {
|
|
77
77
|
height = sourceDimensions.height;
|
|
78
78
|
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
79
79
|
}
|
|
@@ -324,7 +324,7 @@ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
|
|
|
324
324
|
function createIPX(userOptions) {
|
|
325
325
|
const options = defu.defu(userOptions, {
|
|
326
326
|
alias: getEnv("IPX_ALIAS") || {},
|
|
327
|
-
maxAge: getEnv("IPX_MAX_AGE")
|
|
327
|
+
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
328
328
|
sharpOptions: {}
|
|
329
329
|
});
|
|
330
330
|
options.alias = Object.fromEntries(
|
|
@@ -338,10 +338,16 @@ function createIPX(userOptions) {
|
|
|
338
338
|
(r) => r.default || r
|
|
339
339
|
);
|
|
340
340
|
});
|
|
341
|
+
const getSVGO = cachedPromise(async () => {
|
|
342
|
+
const { optimize } = await import('svgo');
|
|
343
|
+
const { xss } = await import('../chunks/svgo-xss.cjs');
|
|
344
|
+
return { optimize, xss };
|
|
345
|
+
});
|
|
341
346
|
return function ipx(id, modifiers = {}, opts = {}) {
|
|
342
347
|
if (!id) {
|
|
343
348
|
throw h3.createError({
|
|
344
349
|
statusCode: 400,
|
|
350
|
+
statusText: `IPX_MISSING_ID`,
|
|
345
351
|
message: `Resource id is missing`
|
|
346
352
|
});
|
|
347
353
|
}
|
|
@@ -355,6 +361,7 @@ function createIPX(userOptions) {
|
|
|
355
361
|
if (!storage) {
|
|
356
362
|
throw h3.createError({
|
|
357
363
|
statusCode: 500,
|
|
364
|
+
statusText: `IPX_NO_STORAGE`,
|
|
358
365
|
message: "No storage configured!"
|
|
359
366
|
});
|
|
360
367
|
}
|
|
@@ -363,11 +370,13 @@ function createIPX(userOptions) {
|
|
|
363
370
|
if (!sourceMeta) {
|
|
364
371
|
throw h3.createError({
|
|
365
372
|
statusCode: 404,
|
|
373
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
366
374
|
message: `Resource not found: ${id}`
|
|
367
375
|
});
|
|
368
376
|
}
|
|
377
|
+
const _maxAge = sourceMeta.maxAge ?? options.maxAge;
|
|
369
378
|
return {
|
|
370
|
-
maxAge: typeof
|
|
379
|
+
maxAge: typeof _maxAge === "string" ? Number.parseInt(_maxAge) : _maxAge,
|
|
371
380
|
mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
|
|
372
381
|
};
|
|
373
382
|
});
|
|
@@ -376,6 +385,7 @@ function createIPX(userOptions) {
|
|
|
376
385
|
if (!sourceData) {
|
|
377
386
|
throw h3.createError({
|
|
378
387
|
statusCode: 404,
|
|
388
|
+
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
379
389
|
message: `Resource not found: ${id}`
|
|
380
390
|
});
|
|
381
391
|
}
|
|
@@ -383,18 +393,40 @@ function createIPX(userOptions) {
|
|
|
383
393
|
});
|
|
384
394
|
const process = cachedPromise(async () => {
|
|
385
395
|
const sourceData = await getSourceData();
|
|
386
|
-
|
|
396
|
+
let imageMeta$1;
|
|
397
|
+
try {
|
|
398
|
+
imageMeta$1 = imageMeta.imageMeta(sourceData);
|
|
399
|
+
} catch {
|
|
400
|
+
throw h3.createError({
|
|
401
|
+
statusCode: 400,
|
|
402
|
+
statusText: `IPX_INVALID_IMAGE`,
|
|
403
|
+
message: `Cannot parse image metadata: ${id}`
|
|
404
|
+
});
|
|
405
|
+
}
|
|
387
406
|
let mFormat = modifiers.f || modifiers.format;
|
|
388
407
|
if (mFormat === "jpg") {
|
|
389
408
|
mFormat = "jpeg";
|
|
390
409
|
}
|
|
391
|
-
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type) ? imageMeta$1.type : "jpeg";
|
|
410
|
+
const format = mFormat && SUPPORTED_FORMATS.has(mFormat) ? mFormat : SUPPORTED_FORMATS.has(imageMeta$1.type || "") ? imageMeta$1.type : "jpeg";
|
|
392
411
|
if (imageMeta$1.type === "svg" && !mFormat) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
412
|
+
if (options.svgo === false) {
|
|
413
|
+
return {
|
|
414
|
+
data: sourceData,
|
|
415
|
+
format: "svg+xml",
|
|
416
|
+
meta: imageMeta$1
|
|
417
|
+
};
|
|
418
|
+
} else {
|
|
419
|
+
const { optimize, xss } = await getSVGO();
|
|
420
|
+
const svg = optimize(sourceData.toString("utf8"), {
|
|
421
|
+
...options.svgo,
|
|
422
|
+
plugins: [xss, ...options.svgo?.plugins || []]
|
|
423
|
+
}).data;
|
|
424
|
+
return {
|
|
425
|
+
data: svg,
|
|
426
|
+
format: "svg+xml",
|
|
427
|
+
meta: imageMeta$1
|
|
428
|
+
};
|
|
429
|
+
}
|
|
398
430
|
}
|
|
399
431
|
const animated = modifiers.animated !== void 0 || modifiers.a !== void 0 || format === "gif";
|
|
400
432
|
const Sharp = await getSharp();
|
|
@@ -416,7 +448,7 @@ function createIPX(userOptions) {
|
|
|
416
448
|
for (const h of handlers) {
|
|
417
449
|
sharp = applyHandler(handlerContext, sharp, h.handler, h.args) || sharp;
|
|
418
450
|
}
|
|
419
|
-
if (SUPPORTED_FORMATS.has(format)) {
|
|
451
|
+
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
420
452
|
sharp = sharp.toFormat(format, {
|
|
421
453
|
quality: handlerContext.quality,
|
|
422
454
|
progressive: format === "jpeg"
|
|
@@ -439,7 +471,7 @@ function createIPX(userOptions) {
|
|
|
439
471
|
const MODIFIER_SEP = /[&,]/g;
|
|
440
472
|
const MODIFIER_VAL_SEP = /[:=_]/;
|
|
441
473
|
function createIPXH3Handler(ipx) {
|
|
442
|
-
|
|
474
|
+
const _handler = async (event) => {
|
|
443
475
|
const [modifiersString = "", ...idSegments] = event.path.slice(
|
|
444
476
|
1
|
|
445
477
|
/* leading slash */
|
|
@@ -448,12 +480,14 @@ function createIPXH3Handler(ipx) {
|
|
|
448
480
|
if (!modifiersString) {
|
|
449
481
|
throw h3.createError({
|
|
450
482
|
statusCode: 400,
|
|
483
|
+
statusText: `IPX_MISSING_MODIFIERS`,
|
|
451
484
|
message: `Modifiers are missing: ${id}`
|
|
452
485
|
});
|
|
453
486
|
}
|
|
454
487
|
if (!id || id === "/") {
|
|
455
488
|
throw h3.createError({
|
|
456
489
|
statusCode: 400,
|
|
490
|
+
statusText: `IPX_MISSING_ID`,
|
|
457
491
|
message: `Resource id is missing: ${event.path}`
|
|
458
492
|
});
|
|
459
493
|
}
|
|
@@ -475,37 +509,59 @@ function createIPXH3Handler(ipx) {
|
|
|
475
509
|
delete modifiers.format;
|
|
476
510
|
if (autoFormat) {
|
|
477
511
|
modifiers.format = autoFormat;
|
|
478
|
-
h3.
|
|
512
|
+
h3.appendResponseHeader(event, "vary", "Accept");
|
|
479
513
|
}
|
|
480
514
|
}
|
|
481
515
|
const img = ipx(id, modifiers);
|
|
482
516
|
const sourceMeta = await img.getSourceMeta();
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
h3.setResponseHeader(event, "last-modified", sourceMeta.mtime.toUTCString());
|
|
489
|
-
}
|
|
517
|
+
sendResponseHeaderIfNotSet(
|
|
518
|
+
event,
|
|
519
|
+
"content-security-policy",
|
|
520
|
+
"default-src 'none'"
|
|
521
|
+
);
|
|
490
522
|
if (typeof sourceMeta.maxAge === "number") {
|
|
491
|
-
|
|
523
|
+
sendResponseHeaderIfNotSet(
|
|
492
524
|
event,
|
|
493
525
|
"cache-control",
|
|
494
526
|
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
495
527
|
);
|
|
496
528
|
}
|
|
529
|
+
if (sourceMeta.mtime) {
|
|
530
|
+
sendResponseHeaderIfNotSet(
|
|
531
|
+
event,
|
|
532
|
+
"last-modified",
|
|
533
|
+
sourceMeta.mtime.toUTCString()
|
|
534
|
+
);
|
|
535
|
+
const _ifModifiedSince = h3.getRequestHeader(event, "if-modified-since");
|
|
536
|
+
if (_ifModifiedSince && new Date(_ifModifiedSince) >= sourceMeta.mtime) {
|
|
537
|
+
h3.setResponseStatus(event, 304);
|
|
538
|
+
return h3.send(event);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
497
541
|
const { data, format } = await img.process();
|
|
498
542
|
const etag = getEtag__default(data);
|
|
499
|
-
|
|
543
|
+
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
500
544
|
if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
|
|
501
545
|
h3.setResponseStatus(event, 304);
|
|
502
|
-
return
|
|
546
|
+
return h3.send(event);
|
|
503
547
|
}
|
|
504
548
|
if (format) {
|
|
505
|
-
|
|
549
|
+
sendResponseHeaderIfNotSet(event, "content-type", `image/${format}`);
|
|
506
550
|
}
|
|
507
|
-
h3.setResponseHeader(event, "content-security-policy", "default-src 'none'");
|
|
508
551
|
return data;
|
|
552
|
+
};
|
|
553
|
+
return h3.defineEventHandler(async (event) => {
|
|
554
|
+
try {
|
|
555
|
+
return await _handler(event);
|
|
556
|
+
} catch (_error) {
|
|
557
|
+
const error = h3.createError(_error);
|
|
558
|
+
h3.setResponseStatus(event, error.statusCode, error.statusMessage);
|
|
559
|
+
return {
|
|
560
|
+
error: {
|
|
561
|
+
message: `[${error.statusCode}] [${error.statusMessage || "IPX_ERROR"}] ${error.message}`
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
509
565
|
});
|
|
510
566
|
}
|
|
511
567
|
function createIPXH3App(ipx) {
|
|
@@ -522,6 +578,11 @@ function createIPXNodeServer(ipx) {
|
|
|
522
578
|
function createIPXPlainServer(ipx) {
|
|
523
579
|
return h3.toPlainHandler(createIPXH3App(ipx));
|
|
524
580
|
}
|
|
581
|
+
function sendResponseHeaderIfNotSet(event, name, value) {
|
|
582
|
+
if (!h3.getResponseHeader(event, name)) {
|
|
583
|
+
h3.setResponseHeader(event, name, value);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
525
586
|
function autoDetectFormat(acceptHeader, animated) {
|
|
526
587
|
if (animated) {
|
|
527
588
|
const acceptMime2 = acceptNegotiator.negotiate(acceptHeader, ["image/webp", "image/gif"]);
|
|
@@ -564,30 +625,28 @@ function ipxHttpStorage(_options = {}) {
|
|
|
564
625
|
if (!url.hostname) {
|
|
565
626
|
throw h3.createError({
|
|
566
627
|
statusCode: 403,
|
|
628
|
+
statusText: `IPX_MISSING_HOSTNAME`,
|
|
567
629
|
message: `Hostname is missing: ${id}`
|
|
568
630
|
});
|
|
569
631
|
}
|
|
570
632
|
if (!allowAllDomains && !domains.has(url.hostname)) {
|
|
571
633
|
throw h3.createError({
|
|
572
634
|
statusCode: 403,
|
|
635
|
+
statusText: `IPX_FORBIDDEN_HOST`,
|
|
573
636
|
message: `Forbidden host: ${url.hostname}`
|
|
574
637
|
});
|
|
575
638
|
}
|
|
576
639
|
return url.toString();
|
|
577
640
|
}
|
|
578
641
|
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
642
|
let maxAge = defaultMaxAge;
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
643
|
+
if (_options.ignoreCacheControl) {
|
|
644
|
+
const _cacheControl = response.headers.get("cache-control");
|
|
645
|
+
if (_cacheControl) {
|
|
646
|
+
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
647
|
+
if (m && m[1]) {
|
|
648
|
+
maxAge = Number.parseInt(m[1]);
|
|
649
|
+
}
|
|
591
650
|
}
|
|
592
651
|
}
|
|
593
652
|
let mtime;
|
|
@@ -602,7 +661,10 @@ function ipxHttpStorage(_options = {}) {
|
|
|
602
661
|
async getMeta(id) {
|
|
603
662
|
const url = validateId(id);
|
|
604
663
|
try {
|
|
605
|
-
const response = await
|
|
664
|
+
const response = await ofetch.ofetch.raw(url, {
|
|
665
|
+
...fetchOptions,
|
|
666
|
+
method: "HEAD"
|
|
667
|
+
});
|
|
606
668
|
const { maxAge, mtime } = parseResponse(response);
|
|
607
669
|
return { mtime, maxAge };
|
|
608
670
|
} catch {
|
|
@@ -611,8 +673,12 @@ function ipxHttpStorage(_options = {}) {
|
|
|
611
673
|
},
|
|
612
674
|
async getData(id) {
|
|
613
675
|
const url = validateId(id);
|
|
614
|
-
const response = await
|
|
615
|
-
|
|
676
|
+
const response = await ofetch.ofetch(url, {
|
|
677
|
+
...fetchOptions,
|
|
678
|
+
method: "GET",
|
|
679
|
+
responseType: "arrayBuffer"
|
|
680
|
+
});
|
|
681
|
+
return response;
|
|
616
682
|
}
|
|
617
683
|
};
|
|
618
684
|
}
|
|
@@ -625,6 +691,7 @@ function ipxFSStorage(_options = {}) {
|
|
|
625
691
|
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
626
692
|
throw h3.createError({
|
|
627
693
|
statusCode: 403,
|
|
694
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
628
695
|
message: `Forbidden path: ${id}`
|
|
629
696
|
});
|
|
630
697
|
}
|
|
@@ -642,16 +709,19 @@ function ipxFSStorage(_options = {}) {
|
|
|
642
709
|
} catch (error) {
|
|
643
710
|
throw error.code === "ENOENT" ? h3.createError({
|
|
644
711
|
statusCode: 404,
|
|
645
|
-
|
|
712
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
713
|
+
message: `File not found: ${id}`
|
|
646
714
|
}) : h3.createError({
|
|
647
715
|
statusCode: 403,
|
|
648
|
-
|
|
716
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
717
|
+
message: `File access forbidden: (${error.code}) ${id}`
|
|
649
718
|
});
|
|
650
719
|
}
|
|
651
720
|
if (!stats.isFile()) {
|
|
652
721
|
throw h3.createError({
|
|
653
722
|
statusCode: 400,
|
|
654
|
-
|
|
723
|
+
statusText: `IPX_INVALID_FILE`,
|
|
724
|
+
message: `Path should be a file: ${id}`
|
|
655
725
|
});
|
|
656
726
|
}
|
|
657
727
|
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",
|
|
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,30 @@
|
|
|
31
43
|
"defu": "^6.1.2",
|
|
32
44
|
"destr": "^2.0.1",
|
|
33
45
|
"etag": "^1.8.1",
|
|
34
|
-
"h3": "^1.8.
|
|
35
|
-
"image-meta": "^0.
|
|
36
|
-
"listhen": "^1.5.
|
|
37
|
-
"
|
|
46
|
+
"h3": "^1.8.2",
|
|
47
|
+
"image-meta": "^0.2.0",
|
|
48
|
+
"listhen": "^1.5.5",
|
|
49
|
+
"ofetch": "^1.3.3",
|
|
38
50
|
"pathe": "^1.1.1",
|
|
39
|
-
"sharp": "^0.32.
|
|
40
|
-
"
|
|
41
|
-
"
|
|
51
|
+
"sharp": "^0.32.6",
|
|
52
|
+
"svgo": "^3.0.2",
|
|
53
|
+
"ufo": "^1.3.1",
|
|
54
|
+
"unstorage": "^1.9.0",
|
|
55
|
+
"xss": "^1.0.14"
|
|
42
56
|
},
|
|
43
57
|
"devDependencies": {
|
|
44
58
|
"@types/etag": "^1.8.1",
|
|
45
59
|
"@types/is-valid-path": "^0.1.0",
|
|
46
|
-
"@vitest/coverage-v8": "^0.34.
|
|
60
|
+
"@vitest/coverage-v8": "^0.34.6",
|
|
47
61
|
"changelogen": "^0.5.5",
|
|
48
|
-
"eslint": "^8.
|
|
62
|
+
"eslint": "^8.51.0",
|
|
49
63
|
"eslint-config-unjs": "^0.2.1",
|
|
50
64
|
"jiti": "^1.20.0",
|
|
51
65
|
"prettier": "^3.0.3",
|
|
52
66
|
"serve-handler": "^6.1.5",
|
|
53
67
|
"typescript": "^5.2.2",
|
|
54
68
|
"unbuild": "^2.0.0",
|
|
55
|
-
"vitest": "^0.34.
|
|
69
|
+
"vitest": "^0.34.6"
|
|
56
70
|
},
|
|
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
|
-
}
|
|
71
|
+
"packageManager": "pnpm@8.8.0"
|
|
69
72
|
}
|