ipx 4.0.0-beta.1 → 4.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/README.md +222 -209
- package/dist/THIRD-PARTY-LICENSES.md.gz +0 -0
- package/dist/_chunks/libs/@fastify/accept-negotiator.mjs +22 -23
- package/dist/_chunks/libs/css-select.mjs +1077 -937
- package/dist/_chunks/libs/css-tree.mjs +107 -191
- package/dist/_chunks/libs/csso.mjs +50 -82
- package/dist/_chunks/libs/h3.mjs +107 -106
- package/dist/_chunks/libs/image-meta.d.mts +2 -2
- package/dist/_chunks/libs/image-meta.mjs +464 -305
- package/dist/_chunks/libs/sax.mjs +61 -60
- package/dist/_chunks/libs/svgo.mjs +933 -1008
- package/dist/_chunks/node-fs.mjs +125 -49
- package/dist/_chunks/rolldown-runtime.mjs +2 -11
- package/dist/cli.mjs +4 -3
- package/dist/index.d.mts +64 -11
- package/package.json +30 -29
- package/dist/THIRD-PARTY-LICENSES.md +0 -593
package/dist/_chunks/node-fs.mjs
CHANGED
|
@@ -91,7 +91,7 @@ function parseArgs(arguments_, mappers) {
|
|
|
91
91
|
return mappers.map((v, index) => v(vargs[index]));
|
|
92
92
|
}
|
|
93
93
|
function getHandler(key) {
|
|
94
|
-
return handlers_exports[key];
|
|
94
|
+
return Object.hasOwn(handlers_exports, key) ? handlers_exports[key] : void 0;
|
|
95
95
|
}
|
|
96
96
|
function applyHandler(context, pipe, handler, argumentsString) {
|
|
97
97
|
const arguments_ = handler.args ? parseArgs(argumentsString, handler.args) : [];
|
|
@@ -187,21 +187,22 @@ function clampEdges(available, a, b) {
|
|
|
187
187
|
const clamp = (value) => value === void 0 ? void 0 : Math.floor(value * scale);
|
|
188
188
|
return [clamp(a), clamp(b)];
|
|
189
189
|
}
|
|
190
|
-
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
width = sourceDimensions.width;
|
|
195
|
-
height = Math.round(sourceDimensions.width / desiredAspectRatio);
|
|
196
|
-
}
|
|
197
|
-
if (sourceDimensions.height && height > sourceDimensions.height) {
|
|
198
|
-
height = sourceDimensions.height;
|
|
199
|
-
width = Math.round(sourceDimensions.height * desiredAspectRatio);
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
190
|
+
function clampDimensionsPreservingAspectRatio(sourceDimensions, desiredDimensions, fit) {
|
|
191
|
+
const { width, height } = desiredDimensions;
|
|
192
|
+
const scales = [sourceDimensions.width ? width / sourceDimensions.width : void 0, sourceDimensions.height ? height / sourceDimensions.height : void 0].filter((scale) => scale !== void 0);
|
|
193
|
+
if (scales.length === 0) return {
|
|
202
194
|
width,
|
|
203
195
|
height
|
|
204
196
|
};
|
|
197
|
+
const scale = fit === "contain" ? Math.min(...scales) : Math.max(...scales);
|
|
198
|
+
if (scale <= 1) return {
|
|
199
|
+
width,
|
|
200
|
+
height
|
|
201
|
+
};
|
|
202
|
+
return {
|
|
203
|
+
width: Math.max(1, Math.round(width / scale)),
|
|
204
|
+
height: Math.max(1, Math.round(height / scale))
|
|
205
|
+
};
|
|
205
206
|
}
|
|
206
207
|
var handlers_exports = /* @__PURE__ */ __exportAll({
|
|
207
208
|
autoorient: () => autoorient,
|
|
@@ -330,9 +331,9 @@ const background = {
|
|
|
330
331
|
}
|
|
331
332
|
};
|
|
332
333
|
const enlarge = {
|
|
333
|
-
args: [],
|
|
334
|
-
apply: (context) => {
|
|
335
|
-
context.enlarge =
|
|
334
|
+
args: [VBoolean("enlarge")],
|
|
335
|
+
apply: (context, _pipe, enabled) => {
|
|
336
|
+
context.enlarge = enabled !== false;
|
|
336
337
|
}
|
|
337
338
|
};
|
|
338
339
|
const kernel = {
|
|
@@ -379,11 +380,13 @@ const resize = {
|
|
|
379
380
|
apply: (context, pipe, size) => {
|
|
380
381
|
if (!size) return;
|
|
381
382
|
let { width, height } = size;
|
|
382
|
-
|
|
383
|
+
const keepsSourceAspectRatio = context.fit === "inside" || context.fit === "outside";
|
|
384
|
+
const withoutEnlargement = !context.enlarge && keepsSourceAspectRatio;
|
|
385
|
+
if (!context.enlarge && !keepsSourceAspectRatio) {
|
|
383
386
|
const clamped = clampDimensionsPreservingAspectRatio(context.meta, {
|
|
384
387
|
width,
|
|
385
388
|
height
|
|
386
|
-
});
|
|
389
|
+
}, context.fit);
|
|
387
390
|
width = clamped.width;
|
|
388
391
|
height = clamped.height;
|
|
389
392
|
}
|
|
@@ -391,12 +394,16 @@ const resize = {
|
|
|
391
394
|
width,
|
|
392
395
|
height
|
|
393
396
|
});
|
|
394
|
-
context.outputDimensions = resizeOutputDimensions(capped, context.meta, {
|
|
397
|
+
context.outputDimensions = resizeOutputDimensions(capped, context.meta, {
|
|
398
|
+
fit: context.fit,
|
|
399
|
+
withoutEnlargement
|
|
400
|
+
});
|
|
395
401
|
return pipe.resize(capped.width, capped.height, {
|
|
396
402
|
fit: context.fit,
|
|
397
403
|
position: context.position,
|
|
398
404
|
background: context.background,
|
|
399
|
-
kernel: context.kernel
|
|
405
|
+
kernel: context.kernel,
|
|
406
|
+
withoutEnlargement
|
|
400
407
|
});
|
|
401
408
|
}
|
|
402
409
|
};
|
|
@@ -832,13 +839,18 @@ const sanitizeSVGPlugin = {
|
|
|
832
839
|
};
|
|
833
840
|
function getEnv(name) {
|
|
834
841
|
const value = globalThis.process?.env?.[name];
|
|
835
|
-
if (value === void 0) return;
|
|
842
|
+
if (value === void 0 || value.trim() === "") return;
|
|
836
843
|
try {
|
|
837
844
|
return JSON.parse(value);
|
|
838
845
|
} catch {
|
|
839
846
|
return value;
|
|
840
847
|
}
|
|
841
848
|
}
|
|
849
|
+
function normalizeMaxAge(value) {
|
|
850
|
+
const n = typeof value === "string" && /^\d+$/.test(value) ? Number.parseInt(value, 10) : value;
|
|
851
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n < 0) return;
|
|
852
|
+
return Math.min(Math.floor(n), 2 ** 31);
|
|
853
|
+
}
|
|
842
854
|
function getBuiltinModule(id) {
|
|
843
855
|
return globalThis.process?.getBuiltinModule?.(id);
|
|
844
856
|
}
|
|
@@ -855,6 +867,25 @@ function cachedPromise(function_) {
|
|
|
855
867
|
};
|
|
856
868
|
}
|
|
857
869
|
const DEFAULT_MAX_OUTPUT_DIMENSION = 8192;
|
|
870
|
+
const NON_HANDLER_MODIFIERS = /* @__PURE__ */ new Map([
|
|
871
|
+
["format", "format"],
|
|
872
|
+
["f", "format"],
|
|
873
|
+
["animated", "animated"],
|
|
874
|
+
["a", "animated"]
|
|
875
|
+
]);
|
|
876
|
+
function getModifierKey(name) {
|
|
877
|
+
return NON_HANDLER_MODIFIERS.get(name) ?? getHandler(name);
|
|
878
|
+
}
|
|
879
|
+
function resolveAllowedModifiers(names) {
|
|
880
|
+
if (names === void 0) return;
|
|
881
|
+
if (typeof names === "string") names = names.split(",").map((s) => s.trim()).filter(Boolean);
|
|
882
|
+
if (!Array.isArray(names)) throw new TypeError(`[ipx] allowedModifiers must be an array or a comma-separated string, got: ${JSON.stringify(names)}`);
|
|
883
|
+
return new Set(names.map((name) => {
|
|
884
|
+
const key = getModifierKey(name);
|
|
885
|
+
if (!key) throw new Error(`[ipx] Unknown modifier in allowedModifiers: ${name}`);
|
|
886
|
+
return key;
|
|
887
|
+
}));
|
|
888
|
+
}
|
|
858
889
|
const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
|
|
859
890
|
"jpeg",
|
|
860
891
|
"png",
|
|
@@ -869,13 +900,14 @@ function createIPX(userOptions) {
|
|
|
869
900
|
const options = {
|
|
870
901
|
...userOptions,
|
|
871
902
|
alias: userOptions.alias || getEnv("IPX_ALIAS") || {},
|
|
872
|
-
maxAge: userOptions.maxAge ?? getEnv("IPX_MAX_AGE") ?? 60,
|
|
903
|
+
maxAge: normalizeMaxAge(userOptions.maxAge) ?? normalizeMaxAge(getEnv("IPX_MAX_AGE")) ?? 60,
|
|
873
904
|
maxOutputDimension: userOptions.maxOutputDimension ?? getEnv("IPX_MAX_OUTPUT_DIMENSION") ?? DEFAULT_MAX_OUTPUT_DIMENSION,
|
|
874
905
|
sharpOptions: {
|
|
875
906
|
jpegProgressive: true,
|
|
876
907
|
...userOptions.sharpOptions
|
|
877
908
|
}
|
|
878
909
|
};
|
|
910
|
+
const allowedModifiers = resolveAllowedModifiers(userOptions.allowedModifiers ?? getEnv("IPX_ALLOWED_MODIFIERS"));
|
|
879
911
|
options.alias = Object.fromEntries(Object.entries(options.alias || {}).map((e) => [withLeadingSlash(e[0]), e[1]]));
|
|
880
912
|
const getSharp = cachedPromise(async () => {
|
|
881
913
|
return await import("sharp").then((r) => r.default || r);
|
|
@@ -890,6 +922,14 @@ function createIPX(userOptions) {
|
|
|
890
922
|
statusText: `IPX_MISSING_ID`,
|
|
891
923
|
message: `Resource id is missing`
|
|
892
924
|
});
|
|
925
|
+
modifiers = Object.assign(Object.create(null), modifiers);
|
|
926
|
+
if (allowedModifiers) {
|
|
927
|
+
for (const name of Object.keys(modifiers)) if (!allowedModifiers.has(getModifierKey(name))) throw new HTTPError({
|
|
928
|
+
statusCode: 400,
|
|
929
|
+
statusText: `IPX_FORBIDDEN_MODIFIER`,
|
|
930
|
+
message: `Modifier is not allowed: ${name}`
|
|
931
|
+
});
|
|
932
|
+
}
|
|
893
933
|
id = hasProtocol(id) ? id : withLeadingSlash(id);
|
|
894
934
|
for (const base in options.alias) if (id.startsWith(base)) id = joinURL(options.alias[base], id.slice(base.length));
|
|
895
935
|
const storage = hasProtocol(id) ? options.httpStorage || options.storage : options.storage || options.httpStorage;
|
|
@@ -905,9 +945,8 @@ function createIPX(userOptions) {
|
|
|
905
945
|
statusText: `IPX_RESOURCE_NOT_FOUND`,
|
|
906
946
|
message: `Resource not found: ${id}`
|
|
907
947
|
});
|
|
908
|
-
const _maxAge = sourceMeta.maxAge ?? options.maxAge;
|
|
909
948
|
return {
|
|
910
|
-
maxAge:
|
|
949
|
+
maxAge: normalizeMaxAge(sourceMeta.maxAge) ?? options.maxAge,
|
|
911
950
|
mtime: sourceMeta.mtime ? new Date(sourceMeta.mtime) : void 0
|
|
912
951
|
};
|
|
913
952
|
});
|
|
@@ -977,11 +1016,14 @@ function createIPX(userOptions) {
|
|
|
977
1016
|
...options.sharpOptions
|
|
978
1017
|
});
|
|
979
1018
|
Object.assign(sharp.options, options.sharpOptions);
|
|
980
|
-
const handlers = Object.entries(modifiers).
|
|
981
|
-
handler
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1019
|
+
const handlers = Object.entries(modifiers).flatMap(([name, arguments_]) => {
|
|
1020
|
+
const handler = getHandler(name);
|
|
1021
|
+
return handler ? [{
|
|
1022
|
+
handler,
|
|
1023
|
+
name,
|
|
1024
|
+
args: arguments_
|
|
1025
|
+
}] : [];
|
|
1026
|
+
}).sort((a, b) => {
|
|
985
1027
|
const aOrder = a.handler.order || 0;
|
|
986
1028
|
const bOrder = b.handler.order || 0;
|
|
987
1029
|
return aOrder === bOrder ? (a.name || "").localeCompare(b.name || "") : aOrder - bOrder;
|
|
@@ -1028,8 +1070,11 @@ function createIPXNodeHandler(ipx, opts) {
|
|
|
1028
1070
|
}
|
|
1029
1071
|
function serveIPX(ipx, opts) {
|
|
1030
1072
|
const { serve } = requireModule("srvx");
|
|
1031
|
-
const { parseURL, ...serverOptions } = opts || {};
|
|
1032
|
-
const fetch = createIPXFetchHandler(ipx, {
|
|
1073
|
+
const { parseURL, autoFormats, ...serverOptions } = opts || {};
|
|
1074
|
+
const fetch = createIPXFetchHandler(ipx, {
|
|
1075
|
+
parseURL,
|
|
1076
|
+
autoFormats
|
|
1077
|
+
});
|
|
1033
1078
|
return serve({
|
|
1034
1079
|
...serverOptions,
|
|
1035
1080
|
fetch
|
|
@@ -1057,6 +1102,7 @@ function parseIPXURL(url) {
|
|
|
1057
1102
|
}
|
|
1058
1103
|
function createIPXHandler(ipx, opts = {}) {
|
|
1059
1104
|
const parseURL = opts.parseURL || parseIPXURL;
|
|
1105
|
+
const autoFormats = resolveAutoFormats(opts.autoFormats || getEnv("IPX_AUTO_FORMATS"));
|
|
1060
1106
|
return defineEventHandler(async (event) => {
|
|
1061
1107
|
const parsed = await parseURL(event.req.url) || {};
|
|
1062
1108
|
const id = safeString(parsed.id);
|
|
@@ -1070,7 +1116,7 @@ function createIPXHandler(ipx, opts = {}) {
|
|
|
1070
1116
|
if ((modifiers.f || modifiers.format) === "auto") {
|
|
1071
1117
|
const acceptHeader = event.req.headers.get("accept") || "";
|
|
1072
1118
|
const animated = modifiers.animated ?? modifiers.a;
|
|
1073
|
-
const autoFormat = autoDetectFormat(acceptHeader, !!animated || animated === "");
|
|
1119
|
+
const autoFormat = autoDetectFormat(acceptHeader, autoFormats, !!animated || animated === "");
|
|
1074
1120
|
delete modifiers.f;
|
|
1075
1121
|
delete modifiers.format;
|
|
1076
1122
|
if (autoFormat) {
|
|
@@ -1135,17 +1181,32 @@ function etagMatches(ifNoneMatch, etag) {
|
|
|
1135
1181
|
function opaqueTag(tag) {
|
|
1136
1182
|
return tag.startsWith("W/") ? tag.slice(2) : tag;
|
|
1137
1183
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1184
|
+
const DEFAULT_AUTO_FORMATS = [
|
|
1185
|
+
"avif",
|
|
1186
|
+
"webp",
|
|
1187
|
+
"jpeg",
|
|
1188
|
+
"png",
|
|
1189
|
+
"tiff",
|
|
1190
|
+
"heif",
|
|
1191
|
+
"gif"
|
|
1192
|
+
];
|
|
1193
|
+
const ANIMATED_FORMATS = /* @__PURE__ */ new Set(["webp", "gif"]);
|
|
1194
|
+
function resolveAutoFormats(input) {
|
|
1195
|
+
let list = [];
|
|
1196
|
+
if (typeof input === "string") list = input.split(",");
|
|
1197
|
+
else if (Array.isArray(input)) list = input;
|
|
1198
|
+
const formats = list.map((f) => String(f ?? "").trim().toLowerCase()).filter(Boolean).map((f) => f === "jpg" ? "jpeg" : f);
|
|
1199
|
+
const invalid = formats.filter((f) => !SUPPORTED_FORMATS.has(f));
|
|
1200
|
+
if (invalid.length > 0) throw new TypeError(`[ipx] Unsupported \`autoFormats\`: ${invalid.join(", ")} (supported: ${[...SUPPORTED_FORMATS].join(", ")})`);
|
|
1201
|
+
if (formats.length === 0) formats.push(...DEFAULT_AUTO_FORMATS);
|
|
1202
|
+
return {
|
|
1203
|
+
mimes: formats.map((f) => `image/${f}`),
|
|
1204
|
+
animatedMimes: formats.filter((f) => ANIMATED_FORMATS.has(f)).map((f) => `image/${f}`)
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function autoDetectFormat(acceptHeader, autoFormats, animated) {
|
|
1208
|
+
if (animated) return (0, import_accept_negotiator.negotiate)(acceptHeader, autoFormats.animatedMimes)?.split("/")[1] || "gif";
|
|
1209
|
+
return (0, import_accept_negotiator.negotiate)(acceptHeader, autoFormats.mimes)?.split("/")[1] || "jpeg";
|
|
1149
1210
|
}
|
|
1150
1211
|
function decode$1(input) {
|
|
1151
1212
|
try {
|
|
@@ -1246,10 +1307,28 @@ function decode(input) {
|
|
|
1246
1307
|
return input;
|
|
1247
1308
|
}
|
|
1248
1309
|
}
|
|
1310
|
+
function parseCacheControlMaxAge(header) {
|
|
1311
|
+
let start = 0;
|
|
1312
|
+
let quoted = false;
|
|
1313
|
+
for (let i = 0; i <= header.length; i++) {
|
|
1314
|
+
const char = header[i];
|
|
1315
|
+
if (quoted && char === "\\") {
|
|
1316
|
+
i++;
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (char === "\"") quoted = !quoted;
|
|
1320
|
+
if (!quoted && (char === "," || i === header.length)) {
|
|
1321
|
+
const m = header.slice(start, i).trim().match(/^max-age=(?:"(\d+)"|(\d+))$/i);
|
|
1322
|
+
const age = m?.[1] ?? m?.[2];
|
|
1323
|
+
if (age !== void 0) return normalizeMaxAge(age);
|
|
1324
|
+
start = i + 1;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1249
1328
|
function ipxHttpStorage(_options = {}) {
|
|
1250
1329
|
const allowAllDomains = _options.allowAllDomains ?? getEnv("IPX_HTTP_ALLOW_ALL_DOMAINS") ?? false;
|
|
1251
1330
|
let _domains = _options.domains || getEnv("IPX_HTTP_DOMAINS") || [];
|
|
1252
|
-
const defaultMaxAge = _options.maxAge
|
|
1331
|
+
const defaultMaxAge = normalizeMaxAge(_options.maxAge) ?? normalizeMaxAge(getEnv("IPX_HTTP_MAX_AGE")) ?? 300;
|
|
1253
1332
|
const fetchOptions = _options.fetchOptions || getEnv("IPX_HTTP_FETCH_OPTIONS") || {};
|
|
1254
1333
|
const blockPrivateIPs = _options.blockPrivateIPs ?? getEnv("IPX_HTTP_BLOCK_PRIVATE_IPS") ?? false;
|
|
1255
1334
|
if (typeof _domains === "string") _domains = _domains.split(",").map((s) => s.trim());
|
|
@@ -1371,10 +1450,7 @@ function ipxHttpStorage(_options = {}) {
|
|
|
1371
1450
|
let maxAge = defaultMaxAge;
|
|
1372
1451
|
if (_options.ignoreCacheControl !== true) {
|
|
1373
1452
|
const _cacheControl = response.headers.get("cache-control");
|
|
1374
|
-
if (_cacheControl)
|
|
1375
|
-
const m = _cacheControl.match(/max-age=(\d+)/);
|
|
1376
|
-
if (m && m[1]) maxAge = Number.parseInt(m[1]);
|
|
1377
|
-
}
|
|
1453
|
+
if (_cacheControl) maxAge = parseCacheControlMaxAge(_cacheControl) ?? defaultMaxAge;
|
|
1378
1454
|
}
|
|
1379
1455
|
let mtime;
|
|
1380
1456
|
const _lastModified = response.headers.get("last-modified");
|
|
@@ -1422,7 +1498,7 @@ function ipxFSStorage(_options = {}) {
|
|
|
1422
1498
|
const fs = getBuiltinModule("node:fs/promises");
|
|
1423
1499
|
if (!fs) throw new Error("ipxFSStorage requires the `node:fs/promises` builtin, which this runtime does not provide");
|
|
1424
1500
|
const dirs = resolveDirs(_options.dir);
|
|
1425
|
-
const maxAge = _options.maxAge ?? getEnv("IPX_FS_MAX_AGE");
|
|
1501
|
+
const maxAge = normalizeMaxAge(_options.maxAge) ?? normalizeMaxAge(getEnv("IPX_FS_MAX_AGE"));
|
|
1426
1502
|
const allowSymlinksOutsideDir = _options.allowSymlinksOutsideDir ?? getEnv("IPX_FS_ALLOW_SYMLINKS_OUTSIDE_DIR") ?? false;
|
|
1427
1503
|
const realDirCache = [];
|
|
1428
1504
|
const getRealDir = (index, dir) => {
|
|
@@ -5,14 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __esmMin = (fn, res, err) => () => {
|
|
9
|
-
if (err) throw err[0];
|
|
10
|
-
try {
|
|
11
|
-
return fn && (res = fn(fn = 0)), res;
|
|
12
|
-
} catch (e) {
|
|
13
|
-
throw err = [e], e;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
8
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
17
9
|
var __exportAll = (all, no_symbols) => {
|
|
18
10
|
let target = {};
|
|
@@ -33,10 +25,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
33
25
|
}
|
|
34
26
|
return to;
|
|
35
27
|
};
|
|
36
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
37
29
|
value: mod,
|
|
38
30
|
enumerable: true
|
|
39
31
|
}) : target, mod));
|
|
40
|
-
var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
41
32
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
42
|
-
export { __commonJSMin,
|
|
33
|
+
export { __commonJSMin, __exportAll, __require, __toESM };
|
package/dist/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createIPX, ipxFSStorage, ipxHttpStorage, serveIPX } from "./_chunks/node-fs.mjs";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
var name = "ipx";
|
|
5
|
-
var version = "4.0.0
|
|
5
|
+
var version = "4.0.0";
|
|
6
6
|
const { positionals, values } = parseArgs({
|
|
7
7
|
allowPositionals: true,
|
|
8
8
|
options: {
|
|
@@ -39,10 +39,11 @@ switch (command) {
|
|
|
39
39
|
process.exit(1);
|
|
40
40
|
}
|
|
41
41
|
async function runServe(args) {
|
|
42
|
-
|
|
42
|
+
const ipx = createIPX({
|
|
43
43
|
storage: ipxFSStorage({ dir: args.dir ?? process.env.IPX_FS_DIR ?? process.cwd() }),
|
|
44
44
|
httpStorage: ipxHttpStorage({ domains: args.domains ?? process.env.IPX_HTTP_DOMAINS })
|
|
45
|
-
})
|
|
45
|
+
});
|
|
46
|
+
await serveIPX(ipx, {
|
|
46
47
|
port: Number(args.port ?? process.env.PORT ?? 3e3),
|
|
47
48
|
hostname: args.host ?? process.env.HOST ?? "0.0.0.0"
|
|
48
49
|
}).ready();
|
package/dist/index.d.mts
CHANGED
|
@@ -145,7 +145,7 @@ interface IPXModifiers {
|
|
|
145
145
|
b: string;
|
|
146
146
|
position: string;
|
|
147
147
|
pos: string;
|
|
148
|
-
enlarge: FlagModifier;
|
|
148
|
+
enlarge: FlagModifier | false | "false";
|
|
149
149
|
kernel: "nearest" | "cubic" | "mitchell" | "lanczos2" | "lanczos3" | (string & {});
|
|
150
150
|
trim: number | string;
|
|
151
151
|
extend: string;
|
|
@@ -234,6 +234,25 @@ type IPXOptions = {
|
|
|
234
234
|
* @optional
|
|
235
235
|
*/
|
|
236
236
|
maxOutputDimension?: number | false;
|
|
237
|
+
/**
|
|
238
|
+
* Modifiers that requests are allowed to use. Any other modifier (including
|
|
239
|
+
* an unknown one) is rejected with a `400` before the source is fetched.
|
|
240
|
+
*
|
|
241
|
+
* Every modifier is allowed by default. Restricting them to the ones a site
|
|
242
|
+
* actually uses (for example `["width", "height", "format", "quality"]`)
|
|
243
|
+
* shrinks the attack surface of the public endpoint: expensive operations
|
|
244
|
+
* such as `blur` or `median` cannot be triggered.
|
|
245
|
+
*
|
|
246
|
+
* Aliases follow their modifier: allowing `width` also allows `w`.
|
|
247
|
+
*
|
|
248
|
+
* Only modifier names are checked, not their values: allowing `format`
|
|
249
|
+
* allows every supported output format, including the slow-to-encode `avif`.
|
|
250
|
+
* The same image also stays reachable under many URLs (`?cb=1`, `w_10` vs
|
|
251
|
+
* `w_1e1`, ...), so this does not prevent busting a CDN cache.
|
|
252
|
+
*
|
|
253
|
+
* @optional
|
|
254
|
+
*/
|
|
255
|
+
allowedModifiers?: (keyof IPXModifiers)[];
|
|
237
256
|
/**
|
|
238
257
|
* A mapping of URL aliases to their corresponding URLs, used to simplify resource identifiers.
|
|
239
258
|
* @optional
|
|
@@ -283,7 +302,7 @@ type IPXOptions = {
|
|
|
283
302
|
* @returns {IPX} An IPX processing function configured with the given options. See {@link IPX}.
|
|
284
303
|
* @throws {Error} If critical options such as storage are missing or incorrectly configured.
|
|
285
304
|
*/
|
|
286
|
-
declare function createIPX(userOptions: IPXOptions): IPX;
|
|
305
|
+
export declare function createIPX(userOptions: IPXOptions): IPX;
|
|
287
306
|
type FetchHandler = (request: Request | string | URL) => Response | Promise<Response>;
|
|
288
307
|
interface IPXHandlerOptions {
|
|
289
308
|
/**
|
|
@@ -303,10 +322,32 @@ interface IPXHandlerOptions {
|
|
|
303
322
|
* @optional
|
|
304
323
|
*/
|
|
305
324
|
parseURL?: IPXURLParser;
|
|
325
|
+
/**
|
|
326
|
+
* Output formats `f_auto` can pick from, in order of preference.
|
|
327
|
+
*
|
|
328
|
+
* Useful to leave out formats that are slow to encode, such as `avif`.
|
|
329
|
+
*
|
|
330
|
+
* The format the client's `Accept` header lists with the highest q-value wins,
|
|
331
|
+
* and equal q-values go to the earlier entry. Wildcards such as `image/*` do
|
|
332
|
+
* not match a format, so browsers only negotiate the formats they list
|
|
333
|
+
* explicitly (in practice `avif`, `webp` and `png`).
|
|
334
|
+
*
|
|
335
|
+
* Animated images only consider `webp` and `gif` from this list. When nothing
|
|
336
|
+
* matches, `jpeg` (or `gif` for animated images) is used.
|
|
337
|
+
*
|
|
338
|
+
* Unknown formats throw when the handler is created.
|
|
339
|
+
*
|
|
340
|
+
* Can also be set with the `IPX_AUTO_FORMATS` environment variable (JSON array
|
|
341
|
+
* or comma separated list).
|
|
342
|
+
*
|
|
343
|
+
* @default ["avif", "webp", "jpeg", "png", "tiff", "heif", "gif"]
|
|
344
|
+
*/
|
|
345
|
+
autoFormats?: IPXAutoFormat[];
|
|
306
346
|
}
|
|
307
|
-
|
|
308
|
-
declare function
|
|
309
|
-
declare function
|
|
347
|
+
type IPXAutoFormat = "avif" | "webp" | "jpeg" | "jpg" | "png" | "tiff" | "heif" | "heic" | "gif";
|
|
348
|
+
export declare function createIPXFetchHandler(ipx: IPX, opts?: IPXHandlerOptions): FetchHandler;
|
|
349
|
+
export declare function createIPXNodeHandler(ipx: IPX, opts?: IPXHandlerOptions): NodeHttpHandler;
|
|
350
|
+
export declare function serveIPX(ipx: IPX, opts?: Omit<ServerOptions, "fetch"> & IPXHandlerOptions): Server;
|
|
310
351
|
interface IPXParsedURL {
|
|
311
352
|
/**
|
|
312
353
|
* The identifier of the source image (path or URL, depending on the storage).
|
|
@@ -330,7 +371,7 @@ type IPXURLParser = (url: string) => IPXParsedURL | Promise<IPXParsedURL>;
|
|
|
330
371
|
* @returns {IPXParsedURL} The parsed resource id and modifiers. See {@link IPXParsedURL}.
|
|
331
372
|
* @throws {HTTPError} If the modifiers segment is missing.
|
|
332
373
|
*/
|
|
333
|
-
declare function parseIPXURL(url: string): IPXParsedURL;
|
|
374
|
+
export declare function parseIPXURL(url: string): IPXParsedURL;
|
|
334
375
|
type HTTPStorageOptions = {
|
|
335
376
|
/**
|
|
336
377
|
* Custom options for fetch operations, such as headers or method overrides.
|
|
@@ -345,7 +386,13 @@ type HTTPStorageOptions = {
|
|
|
345
386
|
*/
|
|
346
387
|
fetchOptions?: RequestInit;
|
|
347
388
|
/**
|
|
348
|
-
*
|
|
389
|
+
* Fallback `cache-control` max-age (in seconds), used when the upstream response has no
|
|
390
|
+
* `max-age` directive or {@link HTTPStorageOptions.ignoreCacheControl} is set. If not
|
|
391
|
+
* specified, defaults to `IPX_HTTP_MAX_AGE` or 300 seconds.
|
|
392
|
+
*
|
|
393
|
+
* `0` is kept rather than replaced by the default: caches may still store the response but
|
|
394
|
+
* must revalidate it on every request. It does not override an upstream `max-age` unless
|
|
395
|
+
* `ignoreCacheControl` is set. Invalid values (negative, `NaN`, non-numeric) count as unset.
|
|
349
396
|
* @optional
|
|
350
397
|
*/
|
|
351
398
|
maxAge?: number;
|
|
@@ -379,6 +426,12 @@ type HTTPStorageOptions = {
|
|
|
379
426
|
* (`169.254.169.254`, the cloud metadata service), RFC1918, CGNAT, unique-local or
|
|
380
427
|
* otherwise non-public space.
|
|
381
428
|
*
|
|
429
|
+
* Best effort: it does not protect against DNS rebinding. The hostname is validated, then
|
|
430
|
+
* resolved again when the socket is opened, so an attacker-controlled record with a short
|
|
431
|
+
* TTL can answer with a public address first and a private one second. When IPX must not
|
|
432
|
+
* reach internal services, enforce that at the network level (egress proxy, firewall or
|
|
433
|
+
* network policy) instead of relying on this option alone.
|
|
434
|
+
*
|
|
382
435
|
* Disabled by default because many legitimate deployments fetch from in-cluster origins
|
|
383
436
|
* (internal object storage, sidecars, `localhost` during development), which this blocks.
|
|
384
437
|
* @optional
|
|
@@ -394,7 +447,7 @@ type HTTPStorageOptions = {
|
|
|
394
447
|
* @returns {IPXStorage} An IPXStorage interface implementation for retrieving images over HTTP. See {@link IPXStorage}.
|
|
395
448
|
* @throws {H3Error} If validation of the requested URL fails due to a missing hostname or denied host access. See {@link H3Error}.
|
|
396
449
|
*/
|
|
397
|
-
declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
450
|
+
export declare function ipxHttpStorage(_options?: HTTPStorageOptions): IPXStorage;
|
|
398
451
|
type NodeFSSOptions = {
|
|
399
452
|
/**
|
|
400
453
|
* The directory or list of directories from which to serve files. If not specified, the current directory is used by default.
|
|
@@ -437,7 +490,7 @@ type NodeFSSOptions = {
|
|
|
437
490
|
* @returns {IPXStorage} An implementation of the IPXStorage interface for accessing images stored on the local file system. See {@link IPXStorage}.
|
|
438
491
|
* @throws {H3Error} If there is a problem accessing the file system module or resolving/reading files. See {@link H3Error}.
|
|
439
492
|
*/
|
|
440
|
-
declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
493
|
+
export declare function ipxFSStorage(_options?: NodeFSSOptions): IPXStorage;
|
|
441
494
|
type UnstorageIPXStorageOptions = {
|
|
442
495
|
/**
|
|
443
496
|
* Optional prefix to be placed in front of each storage key, which can help to name or categorise stored items.
|
|
@@ -454,5 +507,5 @@ type UnstorageIPXStorageOptions = {
|
|
|
454
507
|
* @returns {IPXStorage}. An IPXStorage compliant object that implements the necessary methods to interact with the provided unstorage driver or storage system. See {@link IPXStorage}.
|
|
455
508
|
* @throws {H3Error} If there is a problem retrieving or converting the storage data, detailed error information is thrown. See {@link H3Error}.
|
|
456
509
|
*/
|
|
457
|
-
declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
458
|
-
export { HTTPError, type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXHandlerOptions, type IPXModifiers, type IPXOptions, type IPXParsedURL, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type IPXURLParser, type NodeFSSOptions, type UnstorageIPXStorageOptions
|
|
510
|
+
export declare function unstorageToIPXStorage(storage: Storage | Driver, _options?: UnstorageIPXStorageOptions | string): IPXStorage;
|
|
511
|
+
export { HTTPError, type HTTPStorageOptions, type Handler, type HandlerContext, type IPX, type IPXAutoFormat, type IPXHandlerOptions, type IPXModifiers, type IPXOptions, type IPXParsedURL, type IPXStorage, type IPXStorageMeta, type IPXStorageOptions, type IPXURLParser, type NodeFSSOptions, type UnstorageIPXStorageOptions };
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ipx",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "High performance, secure and easy-to-use image optimizer.",
|
|
5
|
-
"repository":
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "unjs/ipx"
|
|
8
|
+
},
|
|
6
9
|
"license": "MIT",
|
|
7
10
|
"sideEffects": false,
|
|
8
11
|
"type": "module",
|
|
@@ -17,42 +20,30 @@
|
|
|
17
20
|
"files": [
|
|
18
21
|
"dist"
|
|
19
22
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "obuild",
|
|
22
|
-
"gen:operations": "node ./scripts/gen-operations.ts",
|
|
23
|
-
"ipx": "node ./src/cli.ts",
|
|
24
|
-
"lint": "eslint . && prettier -c src test scripts automd.config.ts",
|
|
25
|
-
"lint:fix": "automd && eslint . --fix && prettier -w src test scripts automd.config.ts",
|
|
26
|
-
"prepack": "pnpm build",
|
|
27
|
-
"release": "pnpm test && pnpm build && changelogen --release --prerelease --push && npm publish --tag latest",
|
|
28
|
-
"start": "node bin/ipx.js",
|
|
29
|
-
"test": "pnpm lint && pnpm test:types && vitest run --coverage",
|
|
30
|
-
"test:types": "tsc --noEmit"
|
|
31
|
-
},
|
|
32
23
|
"dependencies": {
|
|
33
|
-
"sharp": "^0.35.
|
|
34
|
-
"srvx": "^0.
|
|
24
|
+
"sharp": "^0.35.5",
|
|
25
|
+
"srvx": "^1.0.5"
|
|
35
26
|
},
|
|
36
27
|
"devDependencies": {
|
|
37
|
-
"@fastify/accept-negotiator": "^2.
|
|
28
|
+
"@fastify/accept-negotiator": "^2.1.1",
|
|
38
29
|
"@types/etag": "^1.8.4",
|
|
39
30
|
"@types/is-valid-path": "^0.1.2",
|
|
40
|
-
"@vitest/coverage-v8": "^
|
|
31
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
41
32
|
"automd": "^0.4.3",
|
|
42
33
|
"changelogen": "^0.6.2",
|
|
43
|
-
"eslint": "^10.
|
|
34
|
+
"eslint": "^10.12.0",
|
|
44
35
|
"eslint-config-unjs": "^0.6.2",
|
|
45
36
|
"etag": "^1.8.1",
|
|
46
|
-
"h3": "2.0.
|
|
47
|
-
"image-meta": "^0.
|
|
48
|
-
"obuild": "^0.4.
|
|
49
|
-
"oxc-minify": ">=0.
|
|
50
|
-
"prettier": "^3.9.
|
|
51
|
-
"svgo": "^4.0
|
|
52
|
-
"typescript": "^6",
|
|
37
|
+
"h3": "^2.0.2",
|
|
38
|
+
"image-meta": "^0.3.0",
|
|
39
|
+
"obuild": "^0.4.44",
|
|
40
|
+
"oxc-minify": ">=0.153.0",
|
|
41
|
+
"prettier": "^3.9.10",
|
|
42
|
+
"svgo": "^4.1.0",
|
|
43
|
+
"typescript": "^6.0.3",
|
|
53
44
|
"ufo": "^1.6.4",
|
|
54
45
|
"unstorage": "^1.17.5",
|
|
55
|
-
"vitest": "^
|
|
46
|
+
"vitest": "^5.0.3"
|
|
56
47
|
},
|
|
57
48
|
"peerDependencies": {
|
|
58
49
|
"unstorage": "*"
|
|
@@ -65,5 +56,15 @@
|
|
|
65
56
|
"engines": {
|
|
66
57
|
"node": "^20.16.0 || >=22.3.0"
|
|
67
58
|
},
|
|
68
|
-
"
|
|
69
|
-
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "obuild",
|
|
61
|
+
"gen:operations": "node ./scripts/gen-operations.ts",
|
|
62
|
+
"ipx": "node ./src/cli.ts",
|
|
63
|
+
"lint": "eslint . && prettier -c src test scripts automd.config.ts",
|
|
64
|
+
"lint:fix": "automd && eslint . --fix && prettier -w src test scripts automd.config.ts",
|
|
65
|
+
"release": "pnpm test && pnpm build && changelogen --release --push && pnpm publish --tag latest",
|
|
66
|
+
"start": "node bin/ipx.js",
|
|
67
|
+
"test": "pnpm lint && pnpm test:types && vitest run --coverage",
|
|
68
|
+
"test:types": "tsc --noEmit"
|
|
69
|
+
}
|
|
70
|
+
}
|