ipx 2.0.2 → 2.1.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/dist/cli.cjs +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.cjs +23 -6
- package/dist/index.d.cts +6 -3
- package/dist/index.d.mts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.mjs +23 -6
- package/dist/shared/{ipx.cf456174.cjs → ipx.7601f01b.cjs} +64 -46
- package/dist/shared/{ipx.8dfec2f9.mjs → ipx.b027cc1c.mjs} +64 -46
- package/package.json +17 -17
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.7601f01b.cjs');
|
|
7
7
|
require('defu');
|
|
8
8
|
require('ufo');
|
|
9
9
|
require('h3');
|
|
@@ -15,7 +15,7 @@ require('ofetch');
|
|
|
15
15
|
require('pathe');
|
|
16
16
|
|
|
17
17
|
const name = "ipx";
|
|
18
|
-
const version = "2.0
|
|
18
|
+
const version = "2.1.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,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.b027cc1c.mjs';
|
|
5
5
|
import 'defu';
|
|
6
6
|
import 'ufo';
|
|
7
7
|
import 'h3';
|
|
@@ -13,7 +13,7 @@ import 'ofetch';
|
|
|
13
13
|
import 'pathe';
|
|
14
14
|
|
|
15
15
|
const name = "ipx";
|
|
16
|
-
const version = "2.0
|
|
16
|
+
const version = "2.1.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,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const nodeFs = require('./shared/ipx.
|
|
3
|
+
const nodeFs = require('./shared/ipx.7601f01b.cjs');
|
|
4
|
+
const h3 = require('h3');
|
|
4
5
|
require('defu');
|
|
5
6
|
require('ufo');
|
|
6
|
-
require('h3');
|
|
7
7
|
require('image-meta');
|
|
8
8
|
require('destr');
|
|
9
9
|
require('@fastify/accept-negotiator');
|
|
@@ -11,8 +11,9 @@ require('etag');
|
|
|
11
11
|
require('ofetch');
|
|
12
12
|
require('pathe');
|
|
13
13
|
|
|
14
|
-
function unstorageToIPXStorage(storage,
|
|
15
|
-
const
|
|
14
|
+
function unstorageToIPXStorage(storage, _options = {}) {
|
|
15
|
+
const options = typeof _options === "string" ? { prefix: _options } : _options;
|
|
16
|
+
const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
|
|
16
17
|
return {
|
|
17
18
|
name: "ipx:" + (storage.name || "unstorage"),
|
|
18
19
|
async getMeta(id, opts = {}) {
|
|
@@ -28,8 +29,24 @@ function unstorageToIPXStorage(storage, prefix) {
|
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
const storageKey = resolveKey(id);
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
let data = await storage.getItemRaw(storageKey, opts);
|
|
33
|
+
if (!data) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (data instanceof Blob) {
|
|
37
|
+
data = await data.arrayBuffer();
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return Buffer.from(data);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw h3.createError({
|
|
43
|
+
statusCode: 500,
|
|
44
|
+
statusText: `IPX_STORAGE_ERROR`,
|
|
45
|
+
message: `Failed to parse storage data to Buffer:
|
|
46
|
+
${error.message}`,
|
|
47
|
+
cause: error
|
|
48
|
+
});
|
|
49
|
+
}
|
|
33
50
|
}
|
|
34
51
|
};
|
|
35
52
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
|
|
|
145
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
146
146
|
|
|
147
147
|
type NodeFSSOptions = {
|
|
148
|
-
dir?: string;
|
|
148
|
+
dir?: string | string[];
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
};
|
|
151
151
|
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
type UnstorageIPXStorageOptions = {
|
|
154
|
+
prefix?: string;
|
|
155
|
+
};
|
|
156
|
+
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
154
157
|
|
|
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 };
|
|
158
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
|
|
|
145
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
146
146
|
|
|
147
147
|
type NodeFSSOptions = {
|
|
148
|
-
dir?: string;
|
|
148
|
+
dir?: string | string[];
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
};
|
|
151
151
|
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
type UnstorageIPXStorageOptions = {
|
|
154
|
+
prefix?: string;
|
|
155
|
+
};
|
|
156
|
+
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
154
157
|
|
|
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 };
|
|
158
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -145,11 +145,14 @@ type HTTPStorageOptions = {
|
|
|
145
145
|
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
146
146
|
|
|
147
147
|
type NodeFSSOptions = {
|
|
148
|
-
dir?: string;
|
|
148
|
+
dir?: string | string[];
|
|
149
149
|
maxAge?: number;
|
|
150
150
|
};
|
|
151
151
|
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
type UnstorageIPXStorageOptions = {
|
|
154
|
+
prefix?: string;
|
|
155
|
+
};
|
|
156
|
+
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
154
157
|
|
|
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 };
|
|
158
|
+
export { type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXOptions, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type NodeFSSOptions, type UnstorageIPXStorageOptions, createIPX, createIPXH3App, createIPXH3Handler, createIPXNodeServer, createIPXPlainServer, createIPXWebServer, ipxFSStorage, ipxHttpStorage, unstorageToIPXStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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.b027cc1c.mjs';
|
|
2
|
+
import { createError } from 'h3';
|
|
2
3
|
import 'defu';
|
|
3
4
|
import 'ufo';
|
|
4
|
-
import 'h3';
|
|
5
5
|
import 'image-meta';
|
|
6
6
|
import 'destr';
|
|
7
7
|
import '@fastify/accept-negotiator';
|
|
@@ -9,8 +9,9 @@ import 'etag';
|
|
|
9
9
|
import 'ofetch';
|
|
10
10
|
import 'pathe';
|
|
11
11
|
|
|
12
|
-
function unstorageToIPXStorage(storage,
|
|
13
|
-
const
|
|
12
|
+
function unstorageToIPXStorage(storage, _options = {}) {
|
|
13
|
+
const options = typeof _options === "string" ? { prefix: _options } : _options;
|
|
14
|
+
const resolveKey = (id) => options.prefix ? `${options.prefix}:${id}` : id;
|
|
14
15
|
return {
|
|
15
16
|
name: "ipx:" + (storage.name || "unstorage"),
|
|
16
17
|
async getMeta(id, opts = {}) {
|
|
@@ -26,8 +27,24 @@ function unstorageToIPXStorage(storage, prefix) {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
const storageKey = resolveKey(id);
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
let data = await storage.getItemRaw(storageKey, opts);
|
|
31
|
+
if (!data) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (data instanceof Blob) {
|
|
35
|
+
data = await data.arrayBuffer();
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
return Buffer.from(data);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw createError({
|
|
41
|
+
statusCode: 500,
|
|
42
|
+
statusText: `IPX_STORAGE_ERROR`,
|
|
43
|
+
message: `Failed to parse storage data to Buffer:
|
|
44
|
+
${error.message}`,
|
|
45
|
+
cause: error
|
|
46
|
+
});
|
|
47
|
+
}
|
|
31
48
|
}
|
|
32
49
|
};
|
|
33
50
|
}
|
|
@@ -325,7 +325,9 @@ function createIPX(userOptions) {
|
|
|
325
325
|
const options = defu.defu(userOptions, {
|
|
326
326
|
alias: getEnv("IPX_ALIAS") || {},
|
|
327
327
|
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
328
|
-
sharpOptions: {
|
|
328
|
+
sharpOptions: {
|
|
329
|
+
jpegProgressive: true
|
|
330
|
+
}
|
|
329
331
|
});
|
|
330
332
|
options.alias = Object.fromEntries(
|
|
331
333
|
Object.entries(options.alias || {}).map((e) => [
|
|
@@ -449,8 +451,7 @@ function createIPX(userOptions) {
|
|
|
449
451
|
}
|
|
450
452
|
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
451
453
|
sharp = sharp.toFormat(format, {
|
|
452
|
-
quality: handlerContext.quality
|
|
453
|
-
progressive: format === "jpeg"
|
|
454
|
+
quality: handlerContext.quality
|
|
454
455
|
});
|
|
455
456
|
}
|
|
456
457
|
const processedImage = await sharp.toBuffer();
|
|
@@ -518,13 +519,6 @@ function createIPXH3Handler(ipx) {
|
|
|
518
519
|
"content-security-policy",
|
|
519
520
|
"default-src 'none'"
|
|
520
521
|
);
|
|
521
|
-
if (typeof sourceMeta.maxAge === "number") {
|
|
522
|
-
sendResponseHeaderIfNotSet(
|
|
523
|
-
event,
|
|
524
|
-
"cache-control",
|
|
525
|
-
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
522
|
if (sourceMeta.mtime) {
|
|
529
523
|
sendResponseHeaderIfNotSet(
|
|
530
524
|
event,
|
|
@@ -538,6 +532,13 @@ function createIPXH3Handler(ipx) {
|
|
|
538
532
|
}
|
|
539
533
|
}
|
|
540
534
|
const { data, format } = await img.process();
|
|
535
|
+
if (typeof sourceMeta.maxAge === "number") {
|
|
536
|
+
sendResponseHeaderIfNotSet(
|
|
537
|
+
event,
|
|
538
|
+
"cache-control",
|
|
539
|
+
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
541
542
|
const etag = getEtag__default(data);
|
|
542
543
|
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
543
544
|
if (etag && h3.getRequestHeader(event, "if-none-match") === etag) {
|
|
@@ -606,7 +607,7 @@ const HTTP_RE = /^https?:\/\//;
|
|
|
606
607
|
function ipxHttpStorage(_options = {}) {
|
|
607
608
|
const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
608
609
|
let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
|
|
609
|
-
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE");
|
|
610
|
+
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
|
|
610
611
|
const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
|
|
611
612
|
if (typeof _domains === "string") {
|
|
612
613
|
_domains = _domains.split(",").map((s) => s.trim());
|
|
@@ -683,56 +684,66 @@ function ipxHttpStorage(_options = {}) {
|
|
|
683
684
|
}
|
|
684
685
|
|
|
685
686
|
function ipxFSStorage(_options = {}) {
|
|
686
|
-
const
|
|
687
|
+
const dirs = resolveDirs(_options.dir);
|
|
687
688
|
const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
689
|
+
const _getFS = cachedPromise(
|
|
690
|
+
() => import('node:fs/promises').catch(() => {
|
|
691
691
|
throw h3.createError({
|
|
692
|
-
statusCode:
|
|
693
|
-
statusText: `
|
|
694
|
-
message: `
|
|
692
|
+
statusCode: 500,
|
|
693
|
+
statusText: `IPX_FILESYSTEM_ERROR`,
|
|
694
|
+
message: `Failed to resolve filesystem module`
|
|
695
695
|
});
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
let stats;
|
|
705
|
-
try {
|
|
706
|
-
const fs = await _getFS();
|
|
707
|
-
stats = await fs.stat(fsPath);
|
|
708
|
-
} catch (error) {
|
|
709
|
-
throw error.code === "ENOENT" ? h3.createError({
|
|
710
|
-
statusCode: 404,
|
|
711
|
-
statusText: `IPX_FILE_NOT_FOUND`,
|
|
712
|
-
message: `File not found: ${id}`
|
|
713
|
-
}) : h3.createError({
|
|
696
|
+
})
|
|
697
|
+
);
|
|
698
|
+
const resolveFile = async (id) => {
|
|
699
|
+
const fs = await _getFS();
|
|
700
|
+
for (const dir of dirs) {
|
|
701
|
+
const filePath = pathe.join(dir, id);
|
|
702
|
+
if (!isValidPath(filePath) || !filePath.startsWith(dir)) {
|
|
703
|
+
throw h3.createError({
|
|
714
704
|
statusCode: 403,
|
|
715
|
-
statusText: `
|
|
716
|
-
message: `
|
|
705
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
706
|
+
message: `Forbidden path: ${id}`
|
|
717
707
|
});
|
|
718
708
|
}
|
|
719
|
-
|
|
709
|
+
try {
|
|
710
|
+
const stats = await fs.stat(filePath);
|
|
711
|
+
if (!stats.isFile()) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
stats,
|
|
716
|
+
read: () => fs.readFile(filePath)
|
|
717
|
+
};
|
|
718
|
+
} catch (error) {
|
|
719
|
+
if (error.code === "ENOENT") {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
720
722
|
throw h3.createError({
|
|
721
|
-
statusCode:
|
|
722
|
-
statusText: `
|
|
723
|
-
message: `
|
|
723
|
+
statusCode: 403,
|
|
724
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
725
|
+
message: `Cannot access file: ${id}`
|
|
724
726
|
});
|
|
725
727
|
}
|
|
728
|
+
}
|
|
729
|
+
throw h3.createError({
|
|
730
|
+
statusCode: 404,
|
|
731
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
732
|
+
message: `File not found: ${id}`
|
|
733
|
+
});
|
|
734
|
+
};
|
|
735
|
+
return {
|
|
736
|
+
name: "ipx:node-fs",
|
|
737
|
+
async getMeta(id) {
|
|
738
|
+
const { stats } = await resolveFile(id);
|
|
726
739
|
return {
|
|
727
740
|
mtime: stats.mtime,
|
|
728
741
|
maxAge
|
|
729
742
|
};
|
|
730
743
|
},
|
|
731
744
|
async getData(id) {
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
const contents = await fs.readFile(fsPath);
|
|
735
|
-
return contents;
|
|
745
|
+
const { read } = await resolveFile(id);
|
|
746
|
+
return read();
|
|
736
747
|
}
|
|
737
748
|
};
|
|
738
749
|
}
|
|
@@ -746,6 +757,13 @@ function isValidPath(fp) {
|
|
|
746
757
|
}
|
|
747
758
|
return true;
|
|
748
759
|
}
|
|
760
|
+
function resolveDirs(dirs) {
|
|
761
|
+
if (!dirs || !Array.isArray(dirs)) {
|
|
762
|
+
const dir = pathe.resolve(dirs || getEnv("IPX_FS_DIR") || ".");
|
|
763
|
+
return [dir];
|
|
764
|
+
}
|
|
765
|
+
return dirs.map((dirs2) => pathe.resolve(dirs2));
|
|
766
|
+
}
|
|
749
767
|
|
|
750
768
|
exports.createIPX = createIPX;
|
|
751
769
|
exports.createIPXH3App = createIPXH3App;
|
|
@@ -318,7 +318,9 @@ function createIPX(userOptions) {
|
|
|
318
318
|
const options = defu(userOptions, {
|
|
319
319
|
alias: getEnv("IPX_ALIAS") || {},
|
|
320
320
|
maxAge: getEnv("IPX_MAX_AGE") ?? 60,
|
|
321
|
-
sharpOptions: {
|
|
321
|
+
sharpOptions: {
|
|
322
|
+
jpegProgressive: true
|
|
323
|
+
}
|
|
322
324
|
});
|
|
323
325
|
options.alias = Object.fromEntries(
|
|
324
326
|
Object.entries(options.alias || {}).map((e) => [
|
|
@@ -442,8 +444,7 @@ function createIPX(userOptions) {
|
|
|
442
444
|
}
|
|
443
445
|
if (SUPPORTED_FORMATS.has(format || "")) {
|
|
444
446
|
sharp = sharp.toFormat(format, {
|
|
445
|
-
quality: handlerContext.quality
|
|
446
|
-
progressive: format === "jpeg"
|
|
447
|
+
quality: handlerContext.quality
|
|
447
448
|
});
|
|
448
449
|
}
|
|
449
450
|
const processedImage = await sharp.toBuffer();
|
|
@@ -511,13 +512,6 @@ function createIPXH3Handler(ipx) {
|
|
|
511
512
|
"content-security-policy",
|
|
512
513
|
"default-src 'none'"
|
|
513
514
|
);
|
|
514
|
-
if (typeof sourceMeta.maxAge === "number") {
|
|
515
|
-
sendResponseHeaderIfNotSet(
|
|
516
|
-
event,
|
|
517
|
-
"cache-control",
|
|
518
|
-
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
515
|
if (sourceMeta.mtime) {
|
|
522
516
|
sendResponseHeaderIfNotSet(
|
|
523
517
|
event,
|
|
@@ -531,6 +525,13 @@ function createIPXH3Handler(ipx) {
|
|
|
531
525
|
}
|
|
532
526
|
}
|
|
533
527
|
const { data, format } = await img.process();
|
|
528
|
+
if (typeof sourceMeta.maxAge === "number") {
|
|
529
|
+
sendResponseHeaderIfNotSet(
|
|
530
|
+
event,
|
|
531
|
+
"cache-control",
|
|
532
|
+
`max-age=${+sourceMeta.maxAge}, public, s-maxage=${+sourceMeta.maxAge}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
534
535
|
const etag = getEtag(data);
|
|
535
536
|
sendResponseHeaderIfNotSet(event, "etag", etag);
|
|
536
537
|
if (etag && getRequestHeader(event, "if-none-match") === etag) {
|
|
@@ -599,7 +600,7 @@ const HTTP_RE = /^https?:\/\//;
|
|
|
599
600
|
function ipxHttpStorage(_options = {}) {
|
|
600
601
|
const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
601
602
|
let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
|
|
602
|
-
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE");
|
|
603
|
+
const defaultMaxAge = _options.maxAge || getEnv("IPX_HTTP_MAX_AGE") || 300;
|
|
603
604
|
const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
|
|
604
605
|
if (typeof _domains === "string") {
|
|
605
606
|
_domains = _domains.split(",").map((s) => s.trim());
|
|
@@ -676,56 +677,66 @@ function ipxHttpStorage(_options = {}) {
|
|
|
676
677
|
}
|
|
677
678
|
|
|
678
679
|
function ipxFSStorage(_options = {}) {
|
|
679
|
-
const
|
|
680
|
+
const dirs = resolveDirs(_options.dir);
|
|
680
681
|
const maxAge = _options.maxAge || getEnv("IPX_FS_MAX_AGE");
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
if (!isValidPath(resolved) || !resolved.startsWith(rootDir)) {
|
|
682
|
+
const _getFS = cachedPromise(
|
|
683
|
+
() => import('node:fs/promises').catch(() => {
|
|
684
684
|
throw createError({
|
|
685
|
-
statusCode:
|
|
686
|
-
statusText: `
|
|
687
|
-
message: `
|
|
685
|
+
statusCode: 500,
|
|
686
|
+
statusText: `IPX_FILESYSTEM_ERROR`,
|
|
687
|
+
message: `Failed to resolve filesystem module`
|
|
688
688
|
});
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
let stats;
|
|
698
|
-
try {
|
|
699
|
-
const fs = await _getFS();
|
|
700
|
-
stats = await fs.stat(fsPath);
|
|
701
|
-
} catch (error) {
|
|
702
|
-
throw error.code === "ENOENT" ? createError({
|
|
703
|
-
statusCode: 404,
|
|
704
|
-
statusText: `IPX_FILE_NOT_FOUND`,
|
|
705
|
-
message: `File not found: ${id}`
|
|
706
|
-
}) : createError({
|
|
689
|
+
})
|
|
690
|
+
);
|
|
691
|
+
const resolveFile = async (id) => {
|
|
692
|
+
const fs = await _getFS();
|
|
693
|
+
for (const dir of dirs) {
|
|
694
|
+
const filePath = join(dir, id);
|
|
695
|
+
if (!isValidPath(filePath) || !filePath.startsWith(dir)) {
|
|
696
|
+
throw createError({
|
|
707
697
|
statusCode: 403,
|
|
708
|
-
statusText: `
|
|
709
|
-
message: `
|
|
698
|
+
statusText: `IPX_FORBIDDEN_PATH`,
|
|
699
|
+
message: `Forbidden path: ${id}`
|
|
710
700
|
});
|
|
711
701
|
}
|
|
712
|
-
|
|
702
|
+
try {
|
|
703
|
+
const stats = await fs.stat(filePath);
|
|
704
|
+
if (!stats.isFile()) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
return {
|
|
708
|
+
stats,
|
|
709
|
+
read: () => fs.readFile(filePath)
|
|
710
|
+
};
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (error.code === "ENOENT") {
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
713
715
|
throw createError({
|
|
714
|
-
statusCode:
|
|
715
|
-
statusText: `
|
|
716
|
-
message: `
|
|
716
|
+
statusCode: 403,
|
|
717
|
+
statusText: `IPX_FORBIDDEN_FILE`,
|
|
718
|
+
message: `Cannot access file: ${id}`
|
|
717
719
|
});
|
|
718
720
|
}
|
|
721
|
+
}
|
|
722
|
+
throw createError({
|
|
723
|
+
statusCode: 404,
|
|
724
|
+
statusText: `IPX_FILE_NOT_FOUND`,
|
|
725
|
+
message: `File not found: ${id}`
|
|
726
|
+
});
|
|
727
|
+
};
|
|
728
|
+
return {
|
|
729
|
+
name: "ipx:node-fs",
|
|
730
|
+
async getMeta(id) {
|
|
731
|
+
const { stats } = await resolveFile(id);
|
|
719
732
|
return {
|
|
720
733
|
mtime: stats.mtime,
|
|
721
734
|
maxAge
|
|
722
735
|
};
|
|
723
736
|
},
|
|
724
737
|
async getData(id) {
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
const contents = await fs.readFile(fsPath);
|
|
728
|
-
return contents;
|
|
738
|
+
const { read } = await resolveFile(id);
|
|
739
|
+
return read();
|
|
729
740
|
}
|
|
730
741
|
};
|
|
731
742
|
}
|
|
@@ -739,5 +750,12 @@ function isValidPath(fp) {
|
|
|
739
750
|
}
|
|
740
751
|
return true;
|
|
741
752
|
}
|
|
753
|
+
function resolveDirs(dirs) {
|
|
754
|
+
if (!dirs || !Array.isArray(dirs)) {
|
|
755
|
+
const dir = resolve(dirs || getEnv("IPX_FS_DIR") || ".");
|
|
756
|
+
return [dir];
|
|
757
|
+
}
|
|
758
|
+
return dirs.map((dirs2) => resolve(dirs2));
|
|
759
|
+
}
|
|
742
760
|
|
|
743
761
|
export { createIPXH3Handler as a, createIPXH3App as b, createIPX as c, createIPXWebServer as d, createIPXNodeServer as e, createIPXPlainServer as f, ipxFSStorage as g, ipxHttpStorage as i };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"repository": "unjs/ipx",
|
|
5
5
|
"description": "High performance, secure and easy-to-use image optimizer.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,35 +38,35 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@fastify/accept-negotiator": "^1.1.0",
|
|
41
|
-
"citty": "^0.1.
|
|
41
|
+
"citty": "^0.1.5",
|
|
42
42
|
"consola": "^3.2.3",
|
|
43
|
-
"defu": "^6.1.
|
|
43
|
+
"defu": "^6.1.4",
|
|
44
44
|
"destr": "^2.0.2",
|
|
45
45
|
"etag": "^1.8.1",
|
|
46
|
-
"h3": "^1.
|
|
46
|
+
"h3": "^1.10.0",
|
|
47
47
|
"image-meta": "^0.2.0",
|
|
48
|
-
"listhen": "^1.5.
|
|
48
|
+
"listhen": "^1.5.6",
|
|
49
49
|
"ofetch": "^1.3.3",
|
|
50
|
-
"pathe": "^1.1.
|
|
50
|
+
"pathe": "^1.1.2",
|
|
51
51
|
"sharp": "^0.32.6",
|
|
52
|
-
"svgo": "^3.0
|
|
53
|
-
"ufo": "^1.3.
|
|
54
|
-
"unstorage": "^1.
|
|
52
|
+
"svgo": "^3.2.0",
|
|
53
|
+
"ufo": "^1.3.2",
|
|
54
|
+
"unstorage": "^1.10.1",
|
|
55
55
|
"xss": "^1.0.14"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@types/etag": "^1.8.
|
|
59
|
-
"@types/is-valid-path": "^0.1.
|
|
60
|
-
"@vitest/coverage-v8": "^
|
|
58
|
+
"@types/etag": "^1.8.3",
|
|
59
|
+
"@types/is-valid-path": "^0.1.2",
|
|
60
|
+
"@vitest/coverage-v8": "^1.1.3",
|
|
61
61
|
"changelogen": "^0.5.5",
|
|
62
|
-
"eslint": "^8.
|
|
62
|
+
"eslint": "^8.56.0",
|
|
63
63
|
"eslint-config-unjs": "^0.2.1",
|
|
64
64
|
"jiti": "^1.21.0",
|
|
65
|
-
"prettier": "^3.
|
|
65
|
+
"prettier": "^3.1.1",
|
|
66
66
|
"serve-handler": "^6.1.5",
|
|
67
|
-
"typescript": "^5.
|
|
67
|
+
"typescript": "^5.3.3",
|
|
68
68
|
"unbuild": "^2.0.0",
|
|
69
|
-
"vitest": "^
|
|
69
|
+
"vitest": "^1.1.3"
|
|
70
70
|
},
|
|
71
|
-
"packageManager": "pnpm@8.10.
|
|
71
|
+
"packageManager": "pnpm@8.10.5"
|
|
72
72
|
}
|