@wular/pnext 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pnext +9 -2
- package/dist/server/_chunks/aliases-j47a10ce.js +44 -0
- package/dist/server/_chunks/analyze-arswqe6s.js +445 -0
- package/dist/server/_chunks/boot-af0mv3dy.js +36 -0
- package/dist/server/_chunks/build-spy3dxp4.js +84 -0
- package/dist/server/_chunks/build-t9h0hec3.js +3550 -0
- package/dist/server/_chunks/build-tier-m7yvqz61.js +2472 -0
- package/dist/server/_chunks/compile-53j02j27.js +22 -0
- package/dist/server/_chunks/config-1yzwexq6.js +27 -0
- package/dist/server/_chunks/config-n2z7yxps.js +22 -0
- package/dist/server/_chunks/create-meec9hs6.js +134 -0
- package/dist/server/_chunks/dev-fjmqg6en.js +220 -0
- package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
- package/dist/server/_chunks/generate-n9sh6hzt.js +21 -0
- package/dist/server/_chunks/index-0374j2b9.js +66 -0
- package/dist/server/_chunks/index-06nyqntx.js +213 -0
- package/dist/server/_chunks/index-1011cscr.js +5263 -0
- package/dist/server/_chunks/index-11sfk4hb.js +170 -0
- package/dist/server/_chunks/index-20ppawv7.js +297 -0
- package/dist/server/_chunks/index-2dv4y4wd.js +1065 -0
- package/dist/server/_chunks/index-2vz0wzdn.js +59 -0
- package/dist/server/_chunks/index-2wfv4kvx.js +182 -0
- package/dist/server/_chunks/index-357na4a8.js +109 -0
- package/dist/server/_chunks/index-3b3at80h.js +109 -0
- package/dist/server/_chunks/index-3tf1xjsp.js +6166 -0
- package/dist/server/_chunks/index-3xbr4eyv.js +105 -0
- package/dist/server/_chunks/index-4399bvd2.js +18 -0
- package/dist/server/_chunks/index-4hgjrqh7.js +851 -0
- package/dist/server/_chunks/index-4q2gbfwk.js +2530 -0
- package/dist/server/_chunks/index-4t92hdxd.js +172 -0
- package/dist/server/_chunks/index-4zzebszd.js +10 -0
- package/dist/server/_chunks/index-5gyr4emz.js +107 -0
- package/dist/server/_chunks/index-5hr4062s.js +70 -0
- package/dist/server/_chunks/index-68m3mwbd.js +84 -0
- package/dist/server/_chunks/index-6b009mt0.js +56 -0
- package/dist/server/_chunks/index-6e4fqv30.js +588 -0
- package/dist/server/_chunks/index-6gex3pfa.js +15 -0
- package/dist/server/_chunks/index-6kp9r0d9.js +11 -0
- package/dist/server/_chunks/index-6q7zvrap.js +134 -0
- package/dist/server/_chunks/index-73z4esab.js +38 -0
- package/dist/server/_chunks/index-79ngvam8.js +60 -0
- package/dist/server/_chunks/index-7wc9n8be.js +25 -0
- package/dist/server/_chunks/index-8k7pfejn.js +1599 -0
- package/dist/server/_chunks/index-8kcxdfya.js +206 -0
- package/dist/server/_chunks/index-91x12ykq.js +274 -0
- package/dist/server/_chunks/index-9ch50vtq.js +18 -0
- package/dist/server/_chunks/index-9hx2641q.js +28 -0
- package/dist/server/_chunks/index-9n2914ww.js +535 -0
- package/dist/server/_chunks/index-9ych9ma8.js +207 -0
- package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
- package/dist/server/_chunks/index-bcws5eh2.js +35 -0
- package/dist/server/_chunks/index-c0g2wf3z.js +92 -0
- package/dist/server/_chunks/index-cf4rvxv6.js +95 -0
- package/dist/server/_chunks/index-cjgqs13g.js +65 -0
- package/dist/server/_chunks/index-ckkdpht5.js +41 -0
- package/dist/server/_chunks/index-cyfednwb.js +87 -0
- package/dist/server/_chunks/index-danncfhy.js +311 -0
- package/dist/server/_chunks/index-dg0f05zs.js +199 -0
- package/dist/server/_chunks/index-e3a6yx84.js +18 -0
- package/dist/server/_chunks/index-e88tyv35.js +45 -0
- package/dist/server/_chunks/index-eajaspjk.js +1253 -0
- package/dist/server/_chunks/index-eeaanp2e.js +483 -0
- package/dist/server/_chunks/index-egykcqg3.js +101 -0
- package/dist/server/_chunks/index-en7n9f7m.js +22 -0
- package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
- package/dist/server/_chunks/index-fkp000p1.js +590 -0
- package/dist/server/_chunks/index-fn1chkkd.js +214 -0
- package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
- package/dist/server/_chunks/index-ggk2j965.js +9432 -0
- package/dist/server/_chunks/index-grta8ap3.js +238 -0
- package/dist/server/_chunks/index-h2a70tg4.js +7 -0
- package/dist/server/_chunks/index-h4xy648y.js +51 -0
- package/dist/server/_chunks/index-hkdfj58q.js +92 -0
- package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
- package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
- package/dist/server/_chunks/index-jepjvcrf.js +198 -0
- package/dist/server/_chunks/index-jks0x0a1.js +14 -0
- package/dist/server/_chunks/index-jx7bqvw1.js +57 -0
- package/dist/server/_chunks/index-k1g3epw0.js +1299 -0
- package/dist/server/_chunks/index-knp0s912.js +7 -0
- package/dist/server/_chunks/index-kq8kpzye.js +472 -0
- package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
- package/dist/server/_chunks/index-kresp2n8.js +445 -0
- package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
- package/dist/server/_chunks/index-n4hxpng2.js +11 -0
- package/dist/server/_chunks/index-ncnes8c6.js +17 -0
- package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
- package/dist/server/_chunks/index-nyrba2kr.js +4130 -0
- package/dist/server/_chunks/index-p77b4kcx.js +296 -0
- package/dist/server/_chunks/index-pym1cezq.js +39 -0
- package/dist/server/_chunks/index-q738kkhj.js +100 -0
- package/dist/server/_chunks/index-qjj6gddn.js +54 -0
- package/dist/server/_chunks/index-qm6kmhqs.js +110 -0
- package/dist/server/_chunks/index-qn4z5s3c.js +819 -0
- package/dist/server/_chunks/index-qpwsgt3a.js +158 -0
- package/dist/server/_chunks/index-r97ptz1q.js +97 -0
- package/dist/server/_chunks/index-ras0sdab.js +26 -0
- package/dist/server/_chunks/index-rcs322t1.js +19 -0
- package/dist/server/_chunks/index-rn8dyr1f.js +126 -0
- package/dist/server/_chunks/index-rt9qq1va.js +4 -0
- package/dist/server/_chunks/index-rthrsed5.js +356 -0
- package/dist/server/_chunks/index-sctcgyvv.js +185 -0
- package/dist/server/_chunks/index-sjpckxqn.js +213 -0
- package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
- package/dist/server/_chunks/index-stbk3jhn.js +31 -0
- package/dist/server/_chunks/index-sv8m6789.js +266 -0
- package/dist/server/_chunks/index-tgcakdxc.js +125 -0
- package/dist/server/_chunks/index-tgsfjmep.js +74 -0
- package/dist/server/_chunks/index-tq0pc7eg.js +154 -0
- package/dist/server/_chunks/index-tq1f96v7.js +51 -0
- package/dist/server/_chunks/index-tqn2qnnn.js +241 -0
- package/dist/server/_chunks/index-tvqkq6e6.js +128 -0
- package/dist/server/_chunks/index-v1ags1na.js +132 -0
- package/dist/server/_chunks/index-vvmmknm1.js +1431 -0
- package/dist/server/_chunks/index-vvq7rqtn.js +329 -0
- package/dist/server/_chunks/index-vz0km5qk.js +33 -0
- package/dist/server/_chunks/index-w6h8m6vp.js +138 -0
- package/dist/server/_chunks/index-web361b8.js +138 -0
- package/dist/server/_chunks/index-wpfhfbs4.js +194 -0
- package/dist/server/_chunks/index-x0y005nh.js +283 -0
- package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
- package/dist/server/_chunks/index-x93hv0sc.js +271 -0
- package/dist/server/_chunks/index-xvj80c3a.js +30 -0
- package/dist/server/_chunks/index-xywjg2vc.js +100 -0
- package/dist/server/_chunks/index-y2jr2n72.js +152 -0
- package/dist/server/_chunks/index-y36a0ybw.js +57 -0
- package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
- package/dist/server/_chunks/index-yhxkxghe.js +56 -0
- package/dist/server/_chunks/index-ym67zqxp.js +130 -0
- package/dist/server/_chunks/index-ytjvpbm2.js +736 -0
- package/dist/server/_chunks/index-yz54pxrh.js +960 -0
- package/dist/server/_chunks/index-z4z9zv3b.js +733 -0
- package/dist/server/_chunks/index-z84ab1w0.js +1270 -0
- package/dist/server/_chunks/index-z88qv16t.js +11 -0
- package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
- package/dist/server/_chunks/loader-7yw0j98z.js +78 -0
- package/dist/server/_chunks/modules-jn8bmbn8.js +74 -0
- package/dist/server/_chunks/pipeline-yy9jhydf.js +832 -0
- package/dist/server/_chunks/production-jar9ztm3.js +44 -0
- package/dist/server/_chunks/proxy-config-j84dgksn.js +21 -0
- package/dist/server/_chunks/proxy-w1d0wmsn.js +33 -0
- package/dist/server/_chunks/router-k1reahfn.js +498 -0
- package/dist/server/_chunks/routes-2s277fc6.js +64 -0
- package/dist/server/_chunks/run-pvd8bs1t.js +696 -0
- package/dist/server/_chunks/server-mt4a994c.js +1835 -0
- package/dist/server/_chunks/tsconfig-defaults-1nb3308g.js +20 -0
- package/dist/server/_chunks/typegen-0qr0459b.js +35 -0
- package/dist/server/api/client-cache.js +14 -0
- package/dist/server/cli/index.js +378 -0
- package/dist/server/cli/start.js +137 -0
- package/dist/server/client/reference.js +16 -0
- package/dist/server/compat/next/cache.js +56 -0
- package/dist/server/compat/next/canonical-url.js +13 -0
- package/dist/server/compat/next/client-cache.js +57 -0
- package/dist/server/compat/next/client-navigation.js +232 -0
- package/dist/server/compat/next/client-only.js +1 -0
- package/dist/server/compat/next/client-script.js +178 -0
- package/dist/server/compat/next/client-server.js +34 -0
- package/dist/server/compat/next/config-headers.js +32 -0
- package/dist/server/compat/next/config-loader.js +25 -0
- package/dist/server/compat/next/config.js +10 -0
- package/dist/server/compat/next/constants.js +18 -0
- package/dist/server/compat/next/custom-server.js +10 -0
- package/dist/server/compat/next/dist/client/components/app-router-headers.js +48 -0
- package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +27 -0
- package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +25 -0
- package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +25 -0
- package/dist/server/compat/next/dynamic.js +32 -0
- package/dist/server/compat/next/error.js +112 -0
- package/dist/server/compat/next/font/cache.js +14 -0
- package/dist/server/compat/next/font/google.js +3 -0
- package/dist/server/compat/next/font/index.js +1 -0
- package/dist/server/compat/next/font/local.js +22 -0
- package/dist/server/compat/next/font/runtime-client.js +33 -0
- package/dist/server/compat/next/font/runtime.js +41 -0
- package/dist/server/compat/next/font/shared.js +22 -0
- package/dist/server/compat/next/form.js +129 -0
- package/dist/server/compat/next/head.js +10 -0
- package/dist/server/compat/next/headers.js +30 -0
- package/dist/server/compat/next/image/client.js +11 -0
- package/dist/server/compat/next/image/config.js +18 -0
- package/dist/server/compat/next/image/optimizer.js +25 -0
- package/dist/server/compat/next/image/patterns.js +12 -0
- package/dist/server/compat/next/image/props.js +16 -0
- package/dist/server/compat/next/image/shared.js +86 -0
- package/dist/server/compat/next/image/static-metadata.js +32 -0
- package/dist/server/compat/next/image/validate.js +8 -0
- package/dist/server/compat/next/image.js +59 -0
- package/dist/server/compat/next/index.js +1 -0
- package/dist/server/compat/next/legacy-image.js +78 -0
- package/dist/server/compat/next/legacy-request-apis.js +24 -0
- package/dist/server/compat/next/link-transform.js +14 -0
- package/dist/server/compat/next/link.js +14 -0
- package/dist/server/compat/next/navigation.js +190 -0
- package/dist/server/compat/next/offline.js +45 -0
- package/dist/server/compat/next/og.js +8 -0
- package/dist/server/compat/next/preferred-region.js +8 -0
- package/dist/server/compat/next/redirects.js +30 -0
- package/dist/server/compat/next/resource-hints.js +30 -0
- package/dist/server/compat/next/rewrites.js +41 -0
- package/dist/server/compat/next/root-params.js +121 -0
- package/dist/server/compat/next/route-state.js +23 -0
- package/dist/server/compat/next/router.js +103 -0
- package/dist/server/compat/next/script.js +37 -0
- package/dist/server/compat/next/server-only.js +1 -0
- package/dist/server/compat/next/server.js +131 -0
- package/dist/server/compat/next/svgr.js +14 -0
- package/dist/server/compat/next/telemetry.js +10 -0
- package/dist/server/compat/next/usage.js +13 -0
- package/dist/server/compat/next/user-agent.js +12 -0
- package/dist/server/compat/next/web-vitals.js +35 -0
- package/dist/server/compat/react/action-state.js +11 -0
- package/dist/server/compat/react/client-lite.js +83 -0
- package/dist/server/compat/react/client.js +131 -0
- package/dist/server/compat/react/compiler-runtime.js +42 -0
- package/dist/server/compat/react/dom-client.js +106 -0
- package/dist/server/compat/react/dom-react-server.js +37 -0
- package/dist/server/compat/react/dom-server.js +40 -0
- package/dist/server/compat/react/dom.js +52 -0
- package/dist/server/compat/react/hooks-extra.js +12 -0
- package/dist/server/compat/react/internals.js +14 -0
- package/dist/server/compat/react/parity.js +11 -0
- package/dist/server/compat/react/preact.js +113 -0
- package/dist/server/compat/react/react-server.js +133 -0
- package/dist/server/compat/react/router-shim.js +8 -0
- package/dist/server/compat/react/server-component-use.js +14 -0
- package/dist/server/compat/react/server-inserted-html.js +14 -0
- package/dist/server/compat/react/server.js +194 -0
- package/dist/server/compat/react/style-values.js +10 -0
- package/dist/server/compat/react/use.js +10 -0
- package/dist/server/compat/react/view-transition.js +12 -0
- package/dist/server/generation.json +1 -0
- package/package.json +4 -1
- package/src/api/dynamic.tsx +2 -0
- package/src/api/server.ts +24 -6
- package/src/cli/adapters/vercel-warm.ts +315 -61
- package/src/cli/adapters/vercel.ts +348 -71
- package/src/cli/build.ts +58 -33
- package/src/cli/index.ts +23 -10
- package/src/cli/serve/entry.ts +21 -159
- package/src/cli/serve/immutable.ts +31 -0
- package/src/cli/serve/pipeline.ts +82 -84
- package/src/cli/start.ts +3 -1
- package/src/client/chunk-fold.ts +19 -6
- package/src/client/react-tier.ts +3 -2
- package/src/client/reference-stub.ts +21 -2
- package/src/compat/actions/client-plugin.ts +4 -2
- package/src/compat/actions/config.ts +6 -5
- package/src/compat/actions/extensions.ts +2 -3
- package/src/compat/aliases.ts +8 -1
- package/src/compat/bundler/config.ts +88 -0
- package/src/compat/bundler/optimize-package-imports.ts +86 -26
- package/src/compat/bundler/relay-transform.ts +15 -3
- package/src/compat/cache-control.ts +16 -8
- package/src/compat/css/modules.ts +18 -5
- package/src/compat/lifecycle/error-funnel.ts +1 -1
- package/src/compat/lifecycle/instrumentation.ts +117 -24
- package/src/compat/lifecycle/testmode.ts +3 -2
- package/src/compat/next/config-loader.ts +137 -13
- package/src/compat/next/font/runtime.ts +22 -2
- package/src/compat/next/og.ts +3 -3
- package/src/compat/next/usage.ts +3 -2
- package/src/compat/pages/api.ts +83 -14
- package/src/compat/pages/router.ts +30 -14
- package/src/compat/register/bundler.ts +3 -1
- package/src/compat/register/middleware.ts +1 -1
- package/src/compat/register/otel.ts +5 -9
- package/src/compat/register/render.ts +3 -1
- package/src/compat/register/use-cache.ts +3 -1
- package/src/compat/release.ts +46 -0
- package/src/compat/segment/serve.ts +1 -1
- package/src/compat/validation/validate.ts +10 -7
- package/src/config.ts +51 -7
- package/src/css/assets.ts +213 -0
- package/src/css/build.ts +174 -238
- package/src/dev/server.ts +27 -8
- package/src/extensions.ts +4 -0
- package/src/render/hooks.ts +13 -0
- package/src/render/renderer.ts +52 -32
- package/src/request/context.ts +9 -0
- package/src/resolve/dynamic.ts +3 -1
- package/src/resolve/imports.ts +12 -52
- package/src/resolve/package-json.ts +56 -0
- package/src/resolve/scan-facts.ts +65 -0
- package/src/routing/href.ts +10 -3
- package/src/routing/match.ts +287 -0
- package/src/routing/metadata-files.ts +19 -5
- package/src/routing/proxy-config.ts +137 -0
- package/src/routing/proxy.ts +35 -134
- package/src/routing/routes.ts +22 -281
- package/src/runtime/fingerprint.ts +23 -8
- package/src/runtime/load.ts +170 -0
- package/src/runtime/loader.ts +215 -43
- package/src/runtime/module-cache.ts +13 -0
- package/src/runtime/modules.ts +370 -81
- package/src/runtime/prebundle.ts +81 -0
- package/src/runtime/production.ts +391 -0
- package/src/runtime/release-graph.ts +334 -0
- package/src/runtime/vendor-build.ts +724 -13
- package/src/runtime/vendor.ts +16 -3
- package/src/types.ts +2 -0
- package/src/utils/fs.ts +89 -3
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
hasLocalMatch,
|
|
4
|
+
hasRemoteMatch
|
|
5
|
+
} from "./index-tgsfjmep.js";
|
|
6
|
+
import {
|
|
7
|
+
getImagesConfig
|
|
8
|
+
} from "./index-357na4a8.js";
|
|
9
|
+
import {
|
|
10
|
+
cacheHandlerGet,
|
|
11
|
+
cacheHandlerSet,
|
|
12
|
+
hasCacheHandler
|
|
13
|
+
} from "./index-bcws5eh2.js";
|
|
14
|
+
import {
|
|
15
|
+
getNextConfig
|
|
16
|
+
} from "./index-6e4fqv30.js";
|
|
17
|
+
import {
|
|
18
|
+
__require
|
|
19
|
+
} from "./index-rt9qq1va.js";
|
|
20
|
+
|
|
21
|
+
// src/compat/image-optimizer/optimize.ts
|
|
22
|
+
import path2 from "path";
|
|
23
|
+
|
|
24
|
+
// src/compat/image-optimizer/detect.ts
|
|
25
|
+
var AVIF = "image/avif";
|
|
26
|
+
var WEBP = "image/webp";
|
|
27
|
+
var PNG = "image/png";
|
|
28
|
+
var JPEG = "image/jpeg";
|
|
29
|
+
var JXL = "image/jxl";
|
|
30
|
+
var JP2 = "image/jp2";
|
|
31
|
+
var HEIC = "image/heic";
|
|
32
|
+
var GIF = "image/gif";
|
|
33
|
+
var SVG = "image/svg+xml";
|
|
34
|
+
var ICO = "image/x-icon";
|
|
35
|
+
var ICNS = "image/x-icns";
|
|
36
|
+
var TIFF = "image/tiff";
|
|
37
|
+
var BMP = "image/bmp";
|
|
38
|
+
var PDF = "application/pdf";
|
|
39
|
+
var ANIMATABLE_TYPES = [WEBP, PNG, GIF];
|
|
40
|
+
var BYPASS_TYPES = [SVG, ICO, ICNS, BMP, JXL, HEIC];
|
|
41
|
+
var EXTENSION_BY_TYPE = {
|
|
42
|
+
[AVIF]: "avif",
|
|
43
|
+
[WEBP]: "webp",
|
|
44
|
+
[PNG]: "png",
|
|
45
|
+
[JPEG]: "jpeg",
|
|
46
|
+
[JXL]: "jxl",
|
|
47
|
+
[JP2]: "jp2",
|
|
48
|
+
[HEIC]: "heic",
|
|
49
|
+
[GIF]: "gif",
|
|
50
|
+
[SVG]: "svg",
|
|
51
|
+
[ICO]: "ico",
|
|
52
|
+
[ICNS]: "icns",
|
|
53
|
+
[TIFF]: "tiff",
|
|
54
|
+
[BMP]: "bmp"
|
|
55
|
+
};
|
|
56
|
+
var TYPE_BY_EXTENSION = Object.fromEntries(Object.entries(EXTENSION_BY_TYPE).map(([type, ext]) => [ext, type]));
|
|
57
|
+
function extensionForType(contentType) {
|
|
58
|
+
return EXTENSION_BY_TYPE[contentType];
|
|
59
|
+
}
|
|
60
|
+
function typeForExtension(extension) {
|
|
61
|
+
return TYPE_BY_EXTENSION[extension];
|
|
62
|
+
}
|
|
63
|
+
function startsWithBytes(buffer, bytes) {
|
|
64
|
+
return bytes.every((b, i) => !b || buffer[i] === b);
|
|
65
|
+
}
|
|
66
|
+
function detectContentType(buffer) {
|
|
67
|
+
if (buffer.byteLength === 0)
|
|
68
|
+
return null;
|
|
69
|
+
if ([255, 216, 255].every((b, i) => buffer[i] === b))
|
|
70
|
+
return JPEG;
|
|
71
|
+
if ([137, 80, 78, 71, 13, 10, 26, 10].every((b, i) => buffer[i] === b)) {
|
|
72
|
+
return PNG;
|
|
73
|
+
}
|
|
74
|
+
if ([71, 73, 70, 56].every((b, i) => buffer[i] === b))
|
|
75
|
+
return GIF;
|
|
76
|
+
if (startsWithBytes(buffer, [82, 73, 70, 70, 0, 0, 0, 0, 87, 69, 66, 80])) {
|
|
77
|
+
return WEBP;
|
|
78
|
+
}
|
|
79
|
+
if ([60, 63, 120, 109, 108].every((b, i) => buffer[i] === b))
|
|
80
|
+
return SVG;
|
|
81
|
+
if ([60, 115, 118, 103].every((b, i) => buffer[i] === b))
|
|
82
|
+
return SVG;
|
|
83
|
+
if (startsWithBytes(buffer, [0, 0, 0, 0, 102, 116, 121, 112, 97, 118, 105, 102])) {
|
|
84
|
+
return AVIF;
|
|
85
|
+
}
|
|
86
|
+
if ([0, 0, 1, 0].every((b, i) => buffer[i] === b))
|
|
87
|
+
return ICO;
|
|
88
|
+
if ([105, 99, 110, 115].every((b, i) => buffer[i] === b))
|
|
89
|
+
return ICNS;
|
|
90
|
+
if ([73, 73, 42, 0].every((b, i) => buffer[i] === b))
|
|
91
|
+
return TIFF;
|
|
92
|
+
if ([77, 77, 0, 42].every((b, i) => buffer[i] === b))
|
|
93
|
+
return TIFF;
|
|
94
|
+
if ([66, 77].every((b, i) => buffer[i] === b))
|
|
95
|
+
return BMP;
|
|
96
|
+
if ([255, 10].every((b, i) => buffer[i] === b))
|
|
97
|
+
return JXL;
|
|
98
|
+
if ([0, 0, 0, 12, 74, 88, 76, 32, 13, 10, 135, 10].every((b, i) => buffer[i] === b)) {
|
|
99
|
+
return JXL;
|
|
100
|
+
}
|
|
101
|
+
if (startsWithBytes(buffer, [0, 0, 0, 0, 102, 116, 121, 112, 104, 101, 105, 99])) {
|
|
102
|
+
return HEIC;
|
|
103
|
+
}
|
|
104
|
+
if ([37, 80, 68, 70, 45].every((b, i) => buffer[i] === b))
|
|
105
|
+
return PDF;
|
|
106
|
+
if ([0, 0, 0, 12, 106, 80, 32, 32, 13, 10, 135, 10].every((b, i) => buffer[i] === b)) {
|
|
107
|
+
return JP2;
|
|
108
|
+
}
|
|
109
|
+
const head = buffer.toString("utf8", 0, Math.min(buffer.length, 512)).trimStart();
|
|
110
|
+
if (/^(<\?xml|<!doctype svg|<svg)/i.test(head))
|
|
111
|
+
return SVG;
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
function isAnimated(buffer) {
|
|
115
|
+
if (buffer.length > 6 && buffer.toString("ascii", 0, 3) === "GIF") {
|
|
116
|
+
let frames = 0;
|
|
117
|
+
for (let i = 0;i < buffer.length - 2; i++) {
|
|
118
|
+
if (buffer[i] === 33 && buffer[i + 1] === 249 && buffer[i + 2] === 4) {
|
|
119
|
+
frames++;
|
|
120
|
+
if (frames > 1)
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (buffer.length > 8 && [137, 80, 78, 71, 13, 10, 26, 10].every((b, i) => buffer[i] === b)) {
|
|
127
|
+
const actl = buffer.indexOf("acTL");
|
|
128
|
+
if (actl === -1)
|
|
129
|
+
return false;
|
|
130
|
+
const idat = buffer.indexOf("IDAT");
|
|
131
|
+
return idat === -1 || actl < idat;
|
|
132
|
+
}
|
|
133
|
+
if (buffer.length > 16 && buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WEBP") {
|
|
134
|
+
return buffer.includes("ANIM");
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/compat/image-optimizer/cache.ts
|
|
140
|
+
import { createHash } from "crypto";
|
|
141
|
+
import { promises as fs } from "fs";
|
|
142
|
+
import path from "path";
|
|
143
|
+
var IMAGE_CACHE_VERSION = 4;
|
|
144
|
+
function getHash(items) {
|
|
145
|
+
const hash = createHash("sha256");
|
|
146
|
+
for (const item of items) {
|
|
147
|
+
hash.update(typeof item === "number" ? String(item) : item);
|
|
148
|
+
}
|
|
149
|
+
return hash.digest("base64url");
|
|
150
|
+
}
|
|
151
|
+
function imageCacheDir(root) {
|
|
152
|
+
return path.join(root, ".next", "cache", "images");
|
|
153
|
+
}
|
|
154
|
+
async function readCacheEntry(cacheDir, cacheKey) {
|
|
155
|
+
try {
|
|
156
|
+
const dir = path.join(cacheDir, cacheKey);
|
|
157
|
+
const files = await fs.readdir(dir);
|
|
158
|
+
const file = files[0];
|
|
159
|
+
if (!file)
|
|
160
|
+
return;
|
|
161
|
+
const [maxAgeSt, expireAtSt, etag, upstreamEtag, extension] = file.split(".", 5);
|
|
162
|
+
if (!etag || !upstreamEtag || !extension)
|
|
163
|
+
return;
|
|
164
|
+
const buffer = await fs.readFile(path.join(dir, file));
|
|
165
|
+
return {
|
|
166
|
+
buffer,
|
|
167
|
+
etag,
|
|
168
|
+
upstreamEtag,
|
|
169
|
+
extension,
|
|
170
|
+
maxAge: Number(maxAgeSt),
|
|
171
|
+
expireAt: Number(expireAtSt)
|
|
172
|
+
};
|
|
173
|
+
} catch {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function writeCacheEntry(cacheDir, cacheKey, entry, maximumDiskCacheSize) {
|
|
178
|
+
if (maximumDiskCacheSize === 0)
|
|
179
|
+
return;
|
|
180
|
+
if (maximumDiskCacheSize !== undefined && entry.buffer.byteLength > maximumDiskCacheSize) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const dir = path.join(cacheDir, cacheKey);
|
|
185
|
+
const filename = path.join(dir, `${entry.maxAge}.${entry.expireAt}.${entry.etag}.${entry.upstreamEtag}.${entry.extension}`);
|
|
186
|
+
await fs.rm(dir, { recursive: true, force: true }).catch(() => {
|
|
187
|
+
return;
|
|
188
|
+
});
|
|
189
|
+
await fs.mkdir(dir, { recursive: true });
|
|
190
|
+
await fs.writeFile(filename, entry.buffer);
|
|
191
|
+
if (maximumDiskCacheSize !== undefined) {
|
|
192
|
+
await evictToBudget(cacheDir, cacheKey, maximumDiskCacheSize);
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
console.error(`Failed to write image to cache ${cacheKey}`, err);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function evictToBudget(cacheDir, keepKey, maximumDiskCacheSize) {
|
|
199
|
+
const keys = await fs.readdir(cacheDir).catch(() => []);
|
|
200
|
+
const entries = [];
|
|
201
|
+
let total = 0;
|
|
202
|
+
for (const key of keys) {
|
|
203
|
+
const dir = path.join(cacheDir, key);
|
|
204
|
+
const files = await fs.readdir(dir).catch(() => []);
|
|
205
|
+
const file = files[0];
|
|
206
|
+
if (!file)
|
|
207
|
+
continue;
|
|
208
|
+
const stat = await fs.stat(path.join(dir, file)).catch(() => null);
|
|
209
|
+
if (!stat)
|
|
210
|
+
continue;
|
|
211
|
+
total += stat.size;
|
|
212
|
+
entries.push({ key, size: stat.size, expireAt: Number(file.split(".", 2)[1]) || 0 });
|
|
213
|
+
}
|
|
214
|
+
if (total <= maximumDiskCacheSize)
|
|
215
|
+
return;
|
|
216
|
+
entries.sort((a, b) => a.expireAt - b.expireAt);
|
|
217
|
+
for (const entry of entries) {
|
|
218
|
+
if (total <= maximumDiskCacheSize)
|
|
219
|
+
break;
|
|
220
|
+
if (entry.key === keepKey)
|
|
221
|
+
continue;
|
|
222
|
+
await fs.rm(path.join(cacheDir, entry.key), { recursive: true, force: true }).catch(() => {
|
|
223
|
+
return;
|
|
224
|
+
});
|
|
225
|
+
total -= entry.size;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/compat/image-optimizer/source.ts
|
|
230
|
+
import { lookup } from "dns/promises";
|
|
231
|
+
import { isIP } from "net";
|
|
232
|
+
class ImageError extends Error {
|
|
233
|
+
statusCode;
|
|
234
|
+
constructor(statusCode, message) {
|
|
235
|
+
super(message);
|
|
236
|
+
this.statusCode = statusCode >= 400 ? statusCode : 500;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function extractEtag(etag, buffer) {
|
|
240
|
+
if (etag)
|
|
241
|
+
return Buffer.from(etag).toString("base64url");
|
|
242
|
+
return getHash([buffer]);
|
|
243
|
+
}
|
|
244
|
+
function isPrivateIp(ip) {
|
|
245
|
+
if (isIP(ip) === 4) {
|
|
246
|
+
const parts = ip.split(".").map(Number);
|
|
247
|
+
const [a = -1, b = -1] = parts;
|
|
248
|
+
return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
249
|
+
}
|
|
250
|
+
const lower = ip.toLowerCase();
|
|
251
|
+
return lower === "::" || lower === "::1" || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe8") || lower.startsWith("fe9") || lower.startsWith("fea") || lower.startsWith("feb") || lower.startsWith("::ffff:127.") || lower === "localhost";
|
|
252
|
+
}
|
|
253
|
+
function isRedirect(status) {
|
|
254
|
+
return [301, 302, 303, 307, 308].includes(status);
|
|
255
|
+
}
|
|
256
|
+
async function readBodyWithLimit(body, maximumResponseBody, onExceeded) {
|
|
257
|
+
const chunks = [];
|
|
258
|
+
let totalSize = 0;
|
|
259
|
+
const reader = body.getReader();
|
|
260
|
+
for (;; ) {
|
|
261
|
+
const { done, value } = await reader.read();
|
|
262
|
+
if (done)
|
|
263
|
+
break;
|
|
264
|
+
const chunk = Buffer.from(value);
|
|
265
|
+
totalSize += chunk.byteLength;
|
|
266
|
+
if (totalSize > maximumResponseBody) {
|
|
267
|
+
await reader.cancel().catch(() => {
|
|
268
|
+
return;
|
|
269
|
+
});
|
|
270
|
+
onExceeded();
|
|
271
|
+
}
|
|
272
|
+
chunks.push(chunk);
|
|
273
|
+
}
|
|
274
|
+
return Buffer.concat(chunks);
|
|
275
|
+
}
|
|
276
|
+
async function fetchExternalImage(href, dangerouslyAllowLocalIP, maximumResponseBody, count) {
|
|
277
|
+
if (!dangerouslyAllowLocalIP) {
|
|
278
|
+
const { hostname } = new URL(href);
|
|
279
|
+
let ips = [hostname];
|
|
280
|
+
if (!isIP(hostname)) {
|
|
281
|
+
const records = await lookup(hostname, { family: 0, all: true }).catch(() => [
|
|
282
|
+
{ address: hostname }
|
|
283
|
+
]);
|
|
284
|
+
ips = records.map((record) => record.address);
|
|
285
|
+
}
|
|
286
|
+
const privateIps = ips.filter((ip) => isPrivateIp(ip));
|
|
287
|
+
if (privateIps.length > 0) {
|
|
288
|
+
console.error("upstream image", href, "hostname resolved to private IP", JSON.stringify(privateIps), "If this is expected and you understand SSRF risk, use images.dangerouslyAllowLocalIP = true to continue.");
|
|
289
|
+
throw new ImageError(400, '"url" parameter is not allowed');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const res = await fetch(href, {
|
|
293
|
+
signal: AbortSignal.timeout(7000),
|
|
294
|
+
redirect: "manual"
|
|
295
|
+
}).catch((err) => err);
|
|
296
|
+
if (res instanceof Error) {
|
|
297
|
+
const err = res;
|
|
298
|
+
if (err.name === "TimeoutError" || err.name === "AbortError") {
|
|
299
|
+
console.error("upstream image response timed out for", href);
|
|
300
|
+
throw new ImageError(504, '"url" parameter is valid but upstream response timed out');
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
const locationHeader = res.headers.get("Location");
|
|
305
|
+
if (isRedirect(res.status) && locationHeader && URL.canParse(locationHeader, href)) {
|
|
306
|
+
await res.body?.cancel().catch(() => {
|
|
307
|
+
return;
|
|
308
|
+
});
|
|
309
|
+
if (count === 0) {
|
|
310
|
+
console.error("upstream image response had too many redirects", href);
|
|
311
|
+
throw new ImageError(508, '"url" parameter is valid but upstream response is invalid');
|
|
312
|
+
}
|
|
313
|
+
const redirect = new URL(locationHeader, href).href;
|
|
314
|
+
return fetchExternalImage(redirect, dangerouslyAllowLocalIP, maximumResponseBody, count - 1);
|
|
315
|
+
}
|
|
316
|
+
if (!res.ok) {
|
|
317
|
+
console.error("upstream image response failed for", href, res.status);
|
|
318
|
+
throw new ImageError(res.status, '"url" parameter is valid but upstream response is invalid');
|
|
319
|
+
}
|
|
320
|
+
if (!res.body) {
|
|
321
|
+
console.error("upstream image response is empty for", href);
|
|
322
|
+
throw new ImageError(400, '"url" parameter is valid but upstream response is invalid');
|
|
323
|
+
}
|
|
324
|
+
const buffer = await readBodyWithLimit(res.body, maximumResponseBody, () => {
|
|
325
|
+
console.error("upstream image response exceeded maximum size for", href);
|
|
326
|
+
throw new ImageError(413, '"url" parameter is valid but upstream response is invalid');
|
|
327
|
+
});
|
|
328
|
+
return {
|
|
329
|
+
buffer,
|
|
330
|
+
contentType: res.headers.get("Content-Type"),
|
|
331
|
+
cacheControl: res.headers.get("Cache-Control"),
|
|
332
|
+
etag: extractEtag(res.headers.get("ETag"), buffer)
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async function readPublicFile(publicDirs, href) {
|
|
336
|
+
let decoded;
|
|
337
|
+
try {
|
|
338
|
+
decoded = decodeURIComponent(href.split("?", 1)[0] ?? href);
|
|
339
|
+
} catch {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (decoded.includes("..") || decoded.includes("\x00"))
|
|
343
|
+
return;
|
|
344
|
+
const fs2 = await import("fs/promises");
|
|
345
|
+
const path2 = await import("path");
|
|
346
|
+
for (const dir of publicDirs) {
|
|
347
|
+
for (const form of ["NFC", "NFD"]) {
|
|
348
|
+
const candidate = path2.join(dir, ...decoded.normalize(form).split("/").filter(Boolean));
|
|
349
|
+
if (!candidate.startsWith(dir))
|
|
350
|
+
continue;
|
|
351
|
+
try {
|
|
352
|
+
return await fs2.readFile(candidate);
|
|
353
|
+
} catch {}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
async function fetchInternalImage(href, origin, maximumResponseBody) {
|
|
359
|
+
try {
|
|
360
|
+
const res = await fetch(new URL(href, origin), {
|
|
361
|
+
redirect: "follow",
|
|
362
|
+
headers: { "x-pnext-internal-image": "1" }
|
|
363
|
+
});
|
|
364
|
+
if (!res.body) {
|
|
365
|
+
console.error("internal image response is empty for", href);
|
|
366
|
+
throw new ImageError(400, '"url" parameter is valid but internal response is invalid');
|
|
367
|
+
}
|
|
368
|
+
const buffer = await readBodyWithLimit(res.body, maximumResponseBody, () => {
|
|
369
|
+
console.error("internal image response exceeded maximum size for", href);
|
|
370
|
+
throw new ImageError(413, '"url" parameter is valid but internal response is invalid');
|
|
371
|
+
});
|
|
372
|
+
if (buffer.byteLength === 0) {
|
|
373
|
+
console.error("internal image response is empty for", href);
|
|
374
|
+
throw new ImageError(400, '"url" parameter is valid but internal response is invalid');
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
buffer,
|
|
378
|
+
contentType: res.headers.get("Content-Type"),
|
|
379
|
+
cacheControl: res.headers.get("Cache-Control"),
|
|
380
|
+
etag: extractEtag(res.headers.get("ETag"), buffer)
|
|
381
|
+
};
|
|
382
|
+
} catch (err) {
|
|
383
|
+
if (err instanceof ImageError)
|
|
384
|
+
throw err;
|
|
385
|
+
console.error("upstream image response failed for", href, err);
|
|
386
|
+
throw new ImageError(500, '"url" parameter is valid but upstream response is invalid');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/compat/image-optimizer/optimize.ts
|
|
391
|
+
var sharpPromise;
|
|
392
|
+
function loadSharp() {
|
|
393
|
+
if (!sharpPromise) {
|
|
394
|
+
sharpPromise = import("sharp").then((m) => m.default ?? m).catch(() => {
|
|
395
|
+
return;
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return sharpPromise;
|
|
399
|
+
}
|
|
400
|
+
var warnedOnce = new Set;
|
|
401
|
+
function warnOnce(message) {
|
|
402
|
+
if (warnedOnce.has(message))
|
|
403
|
+
return;
|
|
404
|
+
warnedOnce.add(message);
|
|
405
|
+
console.warn(message);
|
|
406
|
+
}
|
|
407
|
+
function badRequest(message) {
|
|
408
|
+
return new Response(message, { status: 400, headers: { "content-type": "text/plain" } });
|
|
409
|
+
}
|
|
410
|
+
function errorResponse(error) {
|
|
411
|
+
if (error instanceof ImageError) {
|
|
412
|
+
return new Response(error.message, {
|
|
413
|
+
status: error.statusCode,
|
|
414
|
+
headers: { "content-type": "text/plain" }
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
console.error("image optimizer failed", error);
|
|
418
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
419
|
+
}
|
|
420
|
+
function getMaxAge(cacheControl) {
|
|
421
|
+
if (!cacheControl)
|
|
422
|
+
return 0;
|
|
423
|
+
for (const directive of cacheControl.split(",")) {
|
|
424
|
+
const [key = "", rawValue] = directive.trim().split("=", 2);
|
|
425
|
+
if (key.toLowerCase() !== "s-maxage" && key.toLowerCase() !== "max-age")
|
|
426
|
+
continue;
|
|
427
|
+
let value = (rawValue ?? "").toLowerCase();
|
|
428
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
429
|
+
value = value.slice(1, -1);
|
|
430
|
+
const n = parseInt(value, 10);
|
|
431
|
+
if (!isNaN(n))
|
|
432
|
+
return n;
|
|
433
|
+
}
|
|
434
|
+
return 0;
|
|
435
|
+
}
|
|
436
|
+
function getSupportedMimeType(formats, accept) {
|
|
437
|
+
for (const format of formats) {
|
|
438
|
+
if (accept.includes(format))
|
|
439
|
+
return format;
|
|
440
|
+
}
|
|
441
|
+
return "";
|
|
442
|
+
}
|
|
443
|
+
function getFileNameWithExtension(url, contentType) {
|
|
444
|
+
const [urlWithoutQueryParams = url] = url.split("?", 1);
|
|
445
|
+
const fileNameWithExtension = urlWithoutQueryParams.split("/").pop();
|
|
446
|
+
if (!contentType || !fileNameWithExtension)
|
|
447
|
+
return "image.bin";
|
|
448
|
+
const [fileName = "image"] = fileNameWithExtension.split(".", 1);
|
|
449
|
+
const extension = extensionForType(contentType) ?? "bin";
|
|
450
|
+
return `${fileName}.${extension}`;
|
|
451
|
+
}
|
|
452
|
+
function contentDispositionHeader(type, fileName) {
|
|
453
|
+
if (/^[\x20-\x7e]*$/.test(fileName) && !fileName.includes('"')) {
|
|
454
|
+
return `${type}; filename="${fileName}"`;
|
|
455
|
+
}
|
|
456
|
+
return `${type}; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
|
457
|
+
}
|
|
458
|
+
function validateParams(request, searchParams, config, basePath) {
|
|
459
|
+
const url = searchParams.get("url");
|
|
460
|
+
const w = searchParams.get("w");
|
|
461
|
+
const q = searchParams.get("q");
|
|
462
|
+
if (!url)
|
|
463
|
+
return { errorMessage: '"url" parameter is required' };
|
|
464
|
+
if (url.length > 3072)
|
|
465
|
+
return { errorMessage: '"url" parameter is too long' };
|
|
466
|
+
if (url.startsWith("//")) {
|
|
467
|
+
return { errorMessage: '"url" parameter cannot be a protocol-relative URL (//)' };
|
|
468
|
+
}
|
|
469
|
+
let href;
|
|
470
|
+
let isAbsolute;
|
|
471
|
+
if (url.startsWith("/")) {
|
|
472
|
+
href = url;
|
|
473
|
+
isAbsolute = false;
|
|
474
|
+
let pathname = url.split("?", 1)[0] ?? url;
|
|
475
|
+
try {
|
|
476
|
+
pathname = decodeURIComponent(pathname);
|
|
477
|
+
} catch {}
|
|
478
|
+
if (/\/_next\/image($|\/)/.test(pathname)) {
|
|
479
|
+
return { errorMessage: '"url" parameter cannot be recursive' };
|
|
480
|
+
}
|
|
481
|
+
if (!hasLocalMatch(config.localPatterns, new URL(url, "http://n"))) {
|
|
482
|
+
return { errorMessage: '"url" parameter is not allowed' };
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
let parsed;
|
|
486
|
+
try {
|
|
487
|
+
parsed = new URL(url);
|
|
488
|
+
} catch {
|
|
489
|
+
return { errorMessage: '"url" parameter is invalid' };
|
|
490
|
+
}
|
|
491
|
+
href = parsed.toString();
|
|
492
|
+
isAbsolute = true;
|
|
493
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
494
|
+
return { errorMessage: '"url" parameter is invalid' };
|
|
495
|
+
}
|
|
496
|
+
if (!hasRemoteMatch(config.domains, config.remotePatterns, parsed)) {
|
|
497
|
+
return { errorMessage: '"url" parameter is not allowed' };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (!w)
|
|
501
|
+
return { errorMessage: '"w" parameter (width) is required' };
|
|
502
|
+
if (!/^[0-9]+$/.test(w)) {
|
|
503
|
+
return { errorMessage: '"w" parameter (width) must be an integer greater than 0' };
|
|
504
|
+
}
|
|
505
|
+
if (!q)
|
|
506
|
+
return { errorMessage: '"q" parameter (quality) is required' };
|
|
507
|
+
if (!/^[0-9]+$/.test(q)) {
|
|
508
|
+
return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' };
|
|
509
|
+
}
|
|
510
|
+
const width = parseInt(w, 10);
|
|
511
|
+
if (width <= 0 || isNaN(width)) {
|
|
512
|
+
return { errorMessage: '"w" parameter (width) must be an integer greater than 0' };
|
|
513
|
+
}
|
|
514
|
+
const sizes = [...config.deviceSizes, ...config.imageSizes];
|
|
515
|
+
if (!sizes.includes(width)) {
|
|
516
|
+
return { errorMessage: `"w" parameter (width) of ${width} is not allowed` };
|
|
517
|
+
}
|
|
518
|
+
const quality = parseInt(q, 10);
|
|
519
|
+
if (isNaN(quality) || quality < 1 || quality > 100) {
|
|
520
|
+
return { errorMessage: '"q" parameter (quality) must be an integer between 1 and 100' };
|
|
521
|
+
}
|
|
522
|
+
if (config.qualities && !config.qualities.includes(quality)) {
|
|
523
|
+
return { errorMessage: `"q" parameter (quality) of ${q} is not allowed` };
|
|
524
|
+
}
|
|
525
|
+
const mimeType = getSupportedMimeType(config.formats, request.headers.get("accept") ?? "");
|
|
526
|
+
const isStatic = url.startsWith(`${basePath}/_next/static/media`) || url.startsWith(`${basePath}/_next/static/immutable/media`);
|
|
527
|
+
return { href, isAbsolute, isStatic, width, quality, mimeType };
|
|
528
|
+
}
|
|
529
|
+
async function optimizeImage(buffer, contentType, width, quality) {
|
|
530
|
+
const sharp = await loadSharp();
|
|
531
|
+
if (!sharp)
|
|
532
|
+
throw new Error("sharp is not available");
|
|
533
|
+
let transformer = sharp(buffer).timeout({ seconds: 7 }).rotate().resize(width, undefined, { withoutEnlargement: true });
|
|
534
|
+
if (contentType === AVIF) {
|
|
535
|
+
transformer = transformer.avif({ quality: Math.max(quality - 20, 1), effort: 3 });
|
|
536
|
+
} else if (contentType === WEBP) {
|
|
537
|
+
transformer = transformer.webp({ quality });
|
|
538
|
+
} else if (contentType === PNG) {
|
|
539
|
+
transformer = transformer.png({ quality });
|
|
540
|
+
} else if (contentType === JPEG) {
|
|
541
|
+
transformer = transformer.jpeg({ quality, mozjpeg: true });
|
|
542
|
+
}
|
|
543
|
+
return transformer.toBuffer();
|
|
544
|
+
}
|
|
545
|
+
async function generateImage(upstream, params, config) {
|
|
546
|
+
const { href, width, quality, mimeType } = params;
|
|
547
|
+
const upstreamBuffer = upstream.buffer;
|
|
548
|
+
const upstreamEtag = upstream.etag;
|
|
549
|
+
const maxAge = Math.max(config.minimumCacheTTL, getMaxAge(upstream.cacheControl));
|
|
550
|
+
const upstreamType = detectContentType(upstreamBuffer);
|
|
551
|
+
if (!upstreamType || !upstreamType.startsWith("image/") || upstreamType.includes(",")) {
|
|
552
|
+
console.error("The requested resource isn't a valid image for", href, "received", upstreamType);
|
|
553
|
+
throw new ImageError(400, "The requested resource isn't a valid image.");
|
|
554
|
+
}
|
|
555
|
+
if (upstreamType.startsWith("image/svg") && !config.dangerouslyAllowSVG) {
|
|
556
|
+
console.error(`The requested resource "${href}" has type "${upstreamType}" but dangerouslyAllowSVG is disabled. Consider adding the "unoptimized" property to the <Image>.`);
|
|
557
|
+
throw new ImageError(400, '"url" parameter is valid but image type is not allowed');
|
|
558
|
+
}
|
|
559
|
+
if (ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer)) {
|
|
560
|
+
warnOnce(`The requested resource "${href}" is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the <Image>.`);
|
|
561
|
+
return {
|
|
562
|
+
buffer: upstreamBuffer,
|
|
563
|
+
contentType: upstreamType,
|
|
564
|
+
maxAge,
|
|
565
|
+
etag: upstreamEtag,
|
|
566
|
+
upstreamEtag
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (BYPASS_TYPES.includes(upstreamType)) {
|
|
570
|
+
return {
|
|
571
|
+
buffer: upstreamBuffer,
|
|
572
|
+
contentType: upstreamType,
|
|
573
|
+
maxAge,
|
|
574
|
+
etag: upstreamEtag,
|
|
575
|
+
upstreamEtag
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
let contentType;
|
|
579
|
+
if (mimeType) {
|
|
580
|
+
contentType = mimeType;
|
|
581
|
+
} else if (extensionForType(upstreamType) && upstreamType !== WEBP && upstreamType !== AVIF) {
|
|
582
|
+
contentType = upstreamType;
|
|
583
|
+
} else {
|
|
584
|
+
contentType = JPEG;
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const optimizedBuffer = await optimizeImage(upstreamBuffer, contentType, width, quality);
|
|
588
|
+
return {
|
|
589
|
+
buffer: optimizedBuffer,
|
|
590
|
+
contentType,
|
|
591
|
+
maxAge,
|
|
592
|
+
etag: getHash([optimizedBuffer]),
|
|
593
|
+
upstreamEtag
|
|
594
|
+
};
|
|
595
|
+
} catch {
|
|
596
|
+
return {
|
|
597
|
+
buffer: upstreamBuffer,
|
|
598
|
+
contentType: upstreamType,
|
|
599
|
+
maxAge: config.minimumCacheTTL,
|
|
600
|
+
etag: upstreamEtag,
|
|
601
|
+
upstreamEtag
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function resolveOptimizerConfig() {
|
|
606
|
+
const base = getImagesConfig();
|
|
607
|
+
const raw = getNextConfig().images ?? {};
|
|
608
|
+
return {
|
|
609
|
+
...base,
|
|
610
|
+
maximumDiskCacheSize: typeof raw.maximumDiskCacheSize === "number" ? raw.maximumDiskCacheSize : undefined
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
function diskCacheEnabled(config) {
|
|
614
|
+
if (config.customCacheHandler && hasCacheHandler())
|
|
615
|
+
return false;
|
|
616
|
+
if (config.maximumDiskCacheSize === 0)
|
|
617
|
+
return false;
|
|
618
|
+
const experimental = getNextConfig().experimental ?? {};
|
|
619
|
+
return experimental.isrFlushToDisk !== false;
|
|
620
|
+
}
|
|
621
|
+
async function fetchUpstream(params, config, coreConfig, origin) {
|
|
622
|
+
if (params.isAbsolute) {
|
|
623
|
+
return fetchExternalImage(params.href, config.dangerouslyAllowLocalIP, config.maximumResponseBody, config.maximumRedirects);
|
|
624
|
+
}
|
|
625
|
+
const publicFile = await readPublicFile([
|
|
626
|
+
path2.join(coreConfig.outPath, "public"),
|
|
627
|
+
coreConfig.publicPath,
|
|
628
|
+
path2.join(coreConfig.root, "public")
|
|
629
|
+
], params.href);
|
|
630
|
+
if (publicFile) {
|
|
631
|
+
return {
|
|
632
|
+
buffer: publicFile,
|
|
633
|
+
contentType: null,
|
|
634
|
+
cacheControl: null,
|
|
635
|
+
etag: getHash([publicFile])
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
return fetchInternalImage(params.href, origin, config.maximumResponseBody);
|
|
639
|
+
}
|
|
640
|
+
async function generateAndStore(cacheKey, params, config, coreConfig, origin) {
|
|
641
|
+
const upstream = await fetchUpstream(params, config, coreConfig, origin);
|
|
642
|
+
const image = await generateImage(upstream, params, config);
|
|
643
|
+
const entry = {
|
|
644
|
+
buffer: image.buffer,
|
|
645
|
+
etag: image.etag,
|
|
646
|
+
upstreamEtag: image.upstreamEtag,
|
|
647
|
+
extension: extensionForType(image.contentType) ?? "bin",
|
|
648
|
+
maxAge: image.maxAge,
|
|
649
|
+
expireAt: Math.max(image.maxAge, config.minimumCacheTTL) * 1000 + Date.now(),
|
|
650
|
+
contentType: image.contentType
|
|
651
|
+
};
|
|
652
|
+
if (config.customCacheHandler && hasCacheHandler()) {
|
|
653
|
+
const revalidate = Math.max(image.maxAge, config.minimumCacheTTL);
|
|
654
|
+
await cacheHandlerSet(cacheKey, {
|
|
655
|
+
kind: "IMAGE",
|
|
656
|
+
buffer: entry.buffer.toString("base64"),
|
|
657
|
+
etag: entry.etag,
|
|
658
|
+
upstreamEtag: entry.upstreamEtag,
|
|
659
|
+
extension: entry.extension,
|
|
660
|
+
revalidate
|
|
661
|
+
}, { kind: "IMAGE" });
|
|
662
|
+
} else if (diskCacheEnabled(config)) {
|
|
663
|
+
await writeCacheEntry(imageCacheDir(coreConfig.root), cacheKey, entry, config.maximumDiskCacheSize);
|
|
664
|
+
}
|
|
665
|
+
return entry;
|
|
666
|
+
}
|
|
667
|
+
var inFlight = new Map;
|
|
668
|
+
var refreshing = new Set;
|
|
669
|
+
async function readCache(cacheKey, config, coreConfig) {
|
|
670
|
+
if (config.customCacheHandler && hasCacheHandler()) {
|
|
671
|
+
const cached = await cacheHandlerGet(cacheKey, { kind: "IMAGE" });
|
|
672
|
+
const value = cached?.value;
|
|
673
|
+
if (value?.kind !== "IMAGE" || typeof value.buffer !== "string")
|
|
674
|
+
return;
|
|
675
|
+
const revalidate = typeof value.revalidate === "number" ? value.revalidate : config.minimumCacheTTL;
|
|
676
|
+
const lastModified = cached?.lastModified ?? Date.now();
|
|
677
|
+
const expireAt = Math.max(revalidate, config.minimumCacheTTL) * 1000 + lastModified;
|
|
678
|
+
return {
|
|
679
|
+
buffer: Buffer.from(value.buffer, "base64"),
|
|
680
|
+
etag: typeof value.etag === "string" ? value.etag : getHash([value.buffer]),
|
|
681
|
+
upstreamEtag: typeof value.upstreamEtag === "string" ? value.upstreamEtag : "",
|
|
682
|
+
extension: typeof value.extension === "string" ? value.extension : "bin",
|
|
683
|
+
maxAge: revalidate,
|
|
684
|
+
expireAt,
|
|
685
|
+
isStale: expireAt < Date.now()
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (!diskCacheEnabled(config))
|
|
689
|
+
return;
|
|
690
|
+
const entry = await readCacheEntry(imageCacheDir(coreConfig.root), cacheKey);
|
|
691
|
+
if (!entry)
|
|
692
|
+
return;
|
|
693
|
+
return { ...entry, isStale: Date.now() > entry.expireAt };
|
|
694
|
+
}
|
|
695
|
+
function buildResponse(request, params, config, buffer, contentType, etag, maxAge, xCache) {
|
|
696
|
+
const headers = new Headers;
|
|
697
|
+
headers.set("Vary", "Accept");
|
|
698
|
+
headers.set("Cache-Control", params.isStatic ? "public, max-age=315360000, immutable" : `public, max-age=${maxAge}, must-revalidate`);
|
|
699
|
+
headers.set("ETag", etag);
|
|
700
|
+
const ifNoneMatch = request.headers.get("if-none-match");
|
|
701
|
+
if (ifNoneMatch && (ifNoneMatch === etag || ifNoneMatch === `"${etag}"`)) {
|
|
702
|
+
headers.set("X-Nextjs-Cache", xCache);
|
|
703
|
+
return new Response(null, { status: 304, headers });
|
|
704
|
+
}
|
|
705
|
+
headers.set("Content-Type", contentType);
|
|
706
|
+
const fileName = getFileNameWithExtension(params.href, contentType);
|
|
707
|
+
headers.set("Content-Disposition", contentDispositionHeader(config.contentDispositionType, fileName));
|
|
708
|
+
headers.set("Content-Security-Policy", config.contentSecurityPolicy);
|
|
709
|
+
headers.set("X-Nextjs-Cache", xCache);
|
|
710
|
+
headers.set("Content-Length", String(buffer.byteLength));
|
|
711
|
+
if (request.method === "HEAD")
|
|
712
|
+
return new Response(null, { status: 200, headers });
|
|
713
|
+
return new Response(new Uint8Array(buffer), { status: 200, headers });
|
|
714
|
+
}
|
|
715
|
+
async function serveImageOptimizer(request, coreConfig) {
|
|
716
|
+
const config = resolveOptimizerConfig();
|
|
717
|
+
const url = new URL(request.url);
|
|
718
|
+
const basePath = "";
|
|
719
|
+
const paramsResult = validateParams(request, url.searchParams, config, basePath);
|
|
720
|
+
if ("errorMessage" in paramsResult)
|
|
721
|
+
return badRequest(paramsResult.errorMessage);
|
|
722
|
+
const params = paramsResult;
|
|
723
|
+
const cacheKey = getHash([
|
|
724
|
+
IMAGE_CACHE_VERSION,
|
|
725
|
+
params.href,
|
|
726
|
+
params.width,
|
|
727
|
+
params.quality,
|
|
728
|
+
params.mimeType
|
|
729
|
+
]);
|
|
730
|
+
const origin = url.origin;
|
|
731
|
+
try {
|
|
732
|
+
const cached = await readCache(cacheKey, config, coreConfig);
|
|
733
|
+
if (cached && !cached.isStale) {
|
|
734
|
+
return buildResponse(request, params, config, cached.buffer, typeForExtension(cached.extension) ?? "application/octet-stream", cached.etag, cached.maxAge, "HIT");
|
|
735
|
+
}
|
|
736
|
+
if (cached) {
|
|
737
|
+
if (!refreshing.has(cacheKey)) {
|
|
738
|
+
refreshing.add(cacheKey);
|
|
739
|
+
generateAndStore(cacheKey, params, config, coreConfig, origin).catch(() => {
|
|
740
|
+
return;
|
|
741
|
+
}).finally(() => refreshing.delete(cacheKey));
|
|
742
|
+
}
|
|
743
|
+
return buildResponse(request, params, config, cached.buffer, typeForExtension(cached.extension) ?? "application/octet-stream", cached.etag, cached.maxAge, "STALE");
|
|
744
|
+
}
|
|
745
|
+
let pending = inFlight.get(cacheKey);
|
|
746
|
+
if (!pending) {
|
|
747
|
+
pending = generateAndStore(cacheKey, params, config, coreConfig, origin);
|
|
748
|
+
inFlight.set(cacheKey, pending);
|
|
749
|
+
pending.catch(() => {
|
|
750
|
+
return;
|
|
751
|
+
}).finally(() => inFlight.delete(cacheKey));
|
|
752
|
+
}
|
|
753
|
+
const entry = await pending;
|
|
754
|
+
return buildResponse(request, params, config, entry.buffer, entry.contentType, entry.etag, entry.maxAge, "MISS");
|
|
755
|
+
} catch (error) {
|
|
756
|
+
return errorResponse(error);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/compat/next/image/optimizer.ts
|
|
761
|
+
var BLUR_QUALITY = 70;
|
|
762
|
+
var sharpPromise2;
|
|
763
|
+
function loadSharp2() {
|
|
764
|
+
if (!sharpPromise2) {
|
|
765
|
+
sharpPromise2 = import("sharp").then((m) => m.default ?? m).catch(() => {
|
|
766
|
+
return;
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
return sharpPromise2;
|
|
770
|
+
}
|
|
771
|
+
function isImageOptimizerRequest(url, config) {
|
|
772
|
+
return url.pathname === config.path || url.pathname === `${config.path}/`;
|
|
773
|
+
}
|
|
774
|
+
var VALID_BLUR_EXT = new Set(["jpeg", "jpg", "png", "webp", "avif"]);
|
|
775
|
+
function isAnimated2(buffer) {
|
|
776
|
+
if (buffer.length > 16 && buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WEBP") {
|
|
777
|
+
return buffer.includes(Buffer.from("ANIM", "ascii"));
|
|
778
|
+
}
|
|
779
|
+
if (buffer.length > 6 && buffer.toString("ascii", 0, 3) === "GIF") {
|
|
780
|
+
let frames = 0;
|
|
781
|
+
for (let i = 6;i < buffer.length; i++) {
|
|
782
|
+
if (buffer[i] === 44)
|
|
783
|
+
frames++;
|
|
784
|
+
if (frames > 1)
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
if (buffer.length > 16 && buffer.toString("ascii", 4, 8) === "ftyp") {
|
|
790
|
+
return buffer.toString("ascii", 8, 200).includes("avis");
|
|
791
|
+
}
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
async function generateBlurDataURL(buffer, ext, blurWidth, blurHeight) {
|
|
795
|
+
const extension = ext === "jpg" ? "jpeg" : ext;
|
|
796
|
+
if (!VALID_BLUR_EXT.has(extension) || isAnimated2(buffer))
|
|
797
|
+
return;
|
|
798
|
+
const sharp = await loadSharp2();
|
|
799
|
+
if (!sharp)
|
|
800
|
+
return;
|
|
801
|
+
try {
|
|
802
|
+
const pipeline = sharp(buffer).resize({ width: blurWidth, height: blurHeight });
|
|
803
|
+
let out;
|
|
804
|
+
if (extension === "png")
|
|
805
|
+
out = pipeline.png({ quality: BLUR_QUALITY });
|
|
806
|
+
else if (extension === "webp")
|
|
807
|
+
out = pipeline.webp({ quality: BLUR_QUALITY });
|
|
808
|
+
else if (extension === "avif")
|
|
809
|
+
out = pipeline.avif({ quality: BLUR_QUALITY });
|
|
810
|
+
else
|
|
811
|
+
out = pipeline.jpeg({ quality: BLUR_QUALITY, mozjpeg: true });
|
|
812
|
+
const resized = await out.toBuffer();
|
|
813
|
+
return `data:image/${extension};base64,${resized.toString("base64")}`;
|
|
814
|
+
} catch {
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export { serveImageOptimizer, isImageOptimizerRequest, generateBlurDataURL };
|