@wular/pnext 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,283 @@
1
+ // next/image static-import metadata (COMPAT - may import core freely).
2
+ //
3
+ // Parses intrinsic width/height straight out of the image file header for the common raster formats
4
+ // (PNG / JPEG / GIF / WebP / BMP / AVIF) plus SVG's width/height/viewBox attributes, derives a tiny
5
+ // blurDataURL placeholder, and emits the file under `/_next/static/media/<name>.<hash>.<ext>` - the
6
+ // exact `StaticImageData` shape next/image expects for `import img from './pic.png'`.
7
+ //
8
+ // Registered into core's `staticAssetModule` seam so the server module pipeline picks it up. A
9
+ // pure-core app keeps core's generic asset behavior instead.
10
+
11
+ import { createHash } from 'node:crypto';
12
+ import path from 'node:path';
13
+ import type { StaticAssetModuleContext } from '../../../extensions';
14
+ import { isSvgAsComponentEnabled, svgComponentModule } from '../svgr';
15
+ import { generateBlurDataURL } from './optimizer';
16
+
17
+ export interface ImageDimensions {
18
+ width: number;
19
+ height: number;
20
+ }
21
+
22
+ /** The `{src,width,height,blurDataURL,blurWidth,blurHeight}` shape next/image consumes. */
23
+ export interface StaticImageData {
24
+ src: string;
25
+ width: number;
26
+ height: number;
27
+ blurDataURL: string;
28
+ blurWidth: number;
29
+ blurHeight: number;
30
+ }
31
+
32
+ const STATIC_IMAGE_EXTENSIONS = new Set([
33
+ '.png',
34
+ '.jpg',
35
+ '.jpeg',
36
+ '.gif',
37
+ '.webp',
38
+ '.avif',
39
+ '.svg',
40
+ '.ico',
41
+ '.bmp',
42
+ ]);
43
+
44
+ /**
45
+ * Core `staticAssetModule` seam implementation for next/image. Given the source file and bytes plus
46
+ * core's `emit` (which writes under public/ and returns the public URL), returns the ESM module text a
47
+ * static image import evaluates to - or undefined for non-image assets, so core keeps generic handling.
48
+ */
49
+ export async function staticImageModule(
50
+ context: StaticAssetModuleContext,
51
+ ): Promise<string | undefined> {
52
+ const ext = path.extname(context.sourcePath).toLowerCase();
53
+ if (ext === '.svg' && isSvgAsComponentEnabled()) return svgComponentModule(context.bytes);
54
+ if (!STATIC_IMAGE_EXTENSIONS.has(ext)) return undefined;
55
+ const src = context.emit(staticMediaRelative(context.sourcePath, context.bytes), context.bytes);
56
+ const data = await staticImageData(src, context.sourcePath, context.bytes);
57
+ return staticImageModuleText(data);
58
+ }
59
+
60
+ /** POSIX-relative `_next/static/media/<name>.<hash>.<ext>` path for an imported image. */
61
+ export function staticMediaRelative(sourcePath: string, bytes: Uint8Array): string {
62
+ const ext = path.extname(sourcePath).toLowerCase() || '.bin';
63
+ const hash = createHash('sha256')
64
+ .update(bytes)
65
+ .digest('hex')
66
+ .slice(0, 8);
67
+ const base = path.basename(sourcePath, path.extname(sourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-');
68
+ return path.posix.join('_next', 'static', 'media', `${base}.${hash}${ext}`);
69
+ }
70
+
71
+ /** Build the full static-image descriptor (real dimensions + blur placeholder). */
72
+ export async function staticImageData(
73
+ src: string,
74
+ sourcePath: string,
75
+ bytes: Uint8Array,
76
+ ): Promise<StaticImageData> {
77
+ const ext = path.extname(sourcePath).toLowerCase() || '.bin';
78
+ const dimensions = imageDimensions(bytes, ext);
79
+ const { blurWidth, blurHeight } = blurDimensions(dimensions);
80
+ const blurDataURL =
81
+ (await generateBlurDataURL(Buffer.from(bytes), ext.slice(1), blurWidth, blurHeight)) ?? '';
82
+ return {
83
+ src,
84
+ width: dimensions?.width ?? 0,
85
+ height: dimensions?.height ?? 0,
86
+ blurDataURL,
87
+ blurWidth: blurDataURL ? blurWidth : 0,
88
+ blurHeight: blurDataURL ? blurHeight : 0,
89
+ };
90
+ }
91
+
92
+ /** ESM module text for a static-image import (default + named field exports). */
93
+ export function staticImageModuleText(image: StaticImageData): string {
94
+ return `const image = ${JSON.stringify(image)};
95
+ export default image;
96
+ export const src = image.src;
97
+ export const width = image.width;
98
+ export const height = image.height;
99
+ export const blurDataURL = image.blurDataURL;
100
+ export const blurWidth = image.blurWidth;
101
+ export const blurHeight = image.blurHeight;
102
+ `;
103
+ }
104
+
105
+ const textDecoder = new TextDecoder();
106
+
107
+ /** Parse intrinsic pixel dimensions from an image file's header bytes. */
108
+ export function imageDimensions(bytes: Uint8Array, ext: string): ImageDimensions | undefined {
109
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
110
+ switch (ext) {
111
+ case '.png':
112
+ return pngDimensions(view);
113
+ case '.jpg':
114
+ case '.jpeg':
115
+ return jpegDimensions(view);
116
+ case '.gif':
117
+ return gifDimensions(view);
118
+ case '.webp':
119
+ return webpDimensions(view, bytes);
120
+ case '.bmp':
121
+ return bmpDimensions(view);
122
+ case '.avif':
123
+ return avifDimensions(bytes);
124
+ case '.svg':
125
+ return svgDimensions(bytes);
126
+ case '.ico':
127
+ return icoDimensions(view);
128
+ default:
129
+ return undefined;
130
+ }
131
+ }
132
+
133
+ function pngDimensions(view: DataView): ImageDimensions | undefined {
134
+ // PNG signature (8 bytes) + IHDR chunk: length(4) + 'IHDR'(4) + width(4) + height(4).
135
+ if (view.byteLength < 24) return undefined;
136
+ if (view.getUint32(0) !== 0x89504e47) return undefined;
137
+ return { width: view.getUint32(16), height: view.getUint32(20) };
138
+ }
139
+
140
+ function jpegDimensions(view: DataView): ImageDimensions | undefined {
141
+ if (view.byteLength < 4 || view.getUint16(0) !== 0xffd8) return undefined;
142
+ let offset = 2;
143
+ while (offset + 9 < view.byteLength) {
144
+ if (view.getUint8(offset) !== 0xff) {
145
+ offset += 1;
146
+ continue;
147
+ }
148
+ const marker = view.getUint8(offset + 1);
149
+ // SOF0..SOF15 (baseline / progressive) carry the frame dimensions; skip the
150
+ // RST/standalone markers that have no length field.
151
+ if (
152
+ marker >= 0xc0 &&
153
+ marker <= 0xcf &&
154
+ marker !== 0xc4 &&
155
+ marker !== 0xc8 &&
156
+ marker !== 0xcc
157
+ ) {
158
+ return { height: view.getUint16(offset + 5), width: view.getUint16(offset + 7) };
159
+ }
160
+ const segmentLength = view.getUint16(offset + 2);
161
+ if (segmentLength < 2) return undefined;
162
+ offset += 2 + segmentLength;
163
+ }
164
+ return undefined;
165
+ }
166
+
167
+ function gifDimensions(view: DataView): ImageDimensions | undefined {
168
+ if (view.byteLength < 10) return undefined;
169
+ // 'GIF8' magic; logical screen width/height are little-endian at offset 6/8.
170
+ if (view.getUint32(0) !== 0x47494638) return undefined;
171
+ return { width: view.getUint16(6, true), height: view.getUint16(8, true) };
172
+ }
173
+
174
+ function webpDimensions(view: DataView, bytes: Uint8Array): ImageDimensions | undefined {
175
+ if (view.byteLength < 30) return undefined;
176
+ // 'RIFF' .... 'WEBP'
177
+ if (view.getUint32(0) !== 0x52494646 || view.getUint32(8) !== 0x57454250) return undefined;
178
+ const format = textDecoder.decode(bytes.subarray(12, 16));
179
+ if (format === 'VP8 ') {
180
+ // Lossy: 16-bit width/height (14 low bits) at offset 26/28, little-endian.
181
+ return {
182
+ width: view.getUint16(26, true) & 0x3fff,
183
+ height: view.getUint16(28, true) & 0x3fff,
184
+ };
185
+ }
186
+ if (format === 'VP8L') {
187
+ // Lossless: 14-bit width-1 / height-1 packed after the 0x2f signature byte.
188
+ const bits = view.getUint32(21, true);
189
+ return {
190
+ width: (bits & 0x3fff) + 1,
191
+ height: ((bits >> 14) & 0x3fff) + 1,
192
+ };
193
+ }
194
+ if (format === 'VP8X') {
195
+ // Extended: 24-bit width-1 / height-1 at offset 24/27, little-endian.
196
+ const width = (bytes[24]! | (bytes[25]! << 8) | (bytes[26]! << 16)) + 1;
197
+ const height = (bytes[27]! | (bytes[28]! << 8) | (bytes[29]! << 16)) + 1;
198
+ return { width, height };
199
+ }
200
+ return undefined;
201
+ }
202
+
203
+ function bmpDimensions(view: DataView): ImageDimensions | undefined {
204
+ if (view.byteLength < 26 || view.getUint16(0) !== 0x424d) return undefined;
205
+ return { width: view.getInt32(18, true), height: Math.abs(view.getInt32(22, true)) };
206
+ }
207
+
208
+ function icoDimensions(view: DataView): ImageDimensions | undefined {
209
+ if (view.byteLength < 8 || view.getUint16(0, true) !== 0 || view.getUint16(2, true) !== 1) {
210
+ return undefined;
211
+ }
212
+ // First directory entry: width/height bytes (0 means 256).
213
+ const width = view.getUint8(6) || 256;
214
+ const height = view.getUint8(7) || 256;
215
+ return { width, height };
216
+ }
217
+
218
+ function avifDimensions(bytes: Uint8Array): ImageDimensions | undefined {
219
+ // ISOBMFF: find the 'ispe' box (ImageSpatialExtents) → width(4)/height(4).
220
+ const marker = [0x69, 0x73, 0x70, 0x65]; // 'ispe'
221
+ for (let i = 0; i + 12 <= bytes.length; i += 1) {
222
+ if (
223
+ bytes[i] === marker[0] &&
224
+ bytes[i + 1] === marker[1] &&
225
+ bytes[i + 2] === marker[2] &&
226
+ bytes[i + 3] === marker[3]
227
+ ) {
228
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
229
+ // 'ispe' + version/flags(4) then width/height.
230
+ return { width: view.getUint32(i + 8), height: view.getUint32(i + 12) };
231
+ }
232
+ }
233
+ return undefined;
234
+ }
235
+
236
+ function svgDimensions(bytes: Uint8Array): ImageDimensions | undefined {
237
+ const head = textDecoder.decode(bytes.subarray(0, Math.min(bytes.length, 4096)));
238
+ const svgTag = /<svg[^>]*>/i.exec(head)?.[0];
239
+ if (!svgTag) return undefined;
240
+ const width = svgLength(svgTag, 'width');
241
+ const height = svgLength(svgTag, 'height');
242
+ if (width && height) return { width, height };
243
+ const viewBox = /viewBox\s*=\s*["']?\s*[\d.]+[ ,]+[\d.]+[ ,]+([\d.]+)[ ,]+([\d.]+)/i.exec(svgTag);
244
+ if (viewBox?.[1] && viewBox[2]) {
245
+ return { width: Math.round(Number(viewBox[1])), height: Math.round(Number(viewBox[2])) };
246
+ }
247
+ return undefined;
248
+ }
249
+
250
+ function svgLength(tag: string, attr: string): number | undefined {
251
+ const match = new RegExp(`${attr}\\s*=\\s*["']?\\s*([\\d.]+)`, 'i').exec(tag);
252
+ if (!match?.[1]) return undefined;
253
+ const value = Number(match[1]);
254
+ return Number.isFinite(value) && value > 0 ? Math.round(value) : undefined;
255
+ }
256
+
257
+ const BLUR_IMG_SIZE = 8;
258
+
259
+ /**
260
+ * Blur thumbnail dimensions, matching Next's `getBlurImage`: shrink the
261
+ * image's *largest* dimension to 8px and scale the other proportionally
262
+ * (rather than always fixing the width), so portrait images get a
263
+ * `blurWidth < 8` thumbnail instead of a squashed one.
264
+ */
265
+ export function blurDimensions(dimensions: ImageDimensions | undefined): {
266
+ blurWidth: number;
267
+ blurHeight: number;
268
+ } {
269
+ if (!dimensions || dimensions.width <= 0 || dimensions.height <= 0) {
270
+ return { blurWidth: BLUR_IMG_SIZE, blurHeight: BLUR_IMG_SIZE };
271
+ }
272
+ const { width, height } = dimensions;
273
+ if (width >= height) {
274
+ return {
275
+ blurWidth: BLUR_IMG_SIZE,
276
+ blurHeight: Math.max(Math.round((height / width) * BLUR_IMG_SIZE), 1),
277
+ };
278
+ }
279
+ return {
280
+ blurWidth: Math.max(Math.round((width / height) * BLUR_IMG_SIZE), 1),
281
+ blurHeight: BLUR_IMG_SIZE,
282
+ };
283
+ }
@@ -0,0 +1,269 @@
1
+ // next.config images validation (COMPAT). Mirrors Next's zod config-schema messages for the `images`
2
+ // block (and the assetPrefix URL check) - the image-optimizer e2e suite asserts these exact strings in
3
+ // the build output of an invalid config. Only the rules Next enforces are checked.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+
8
+ const LOADERS = ['default', 'imgix', 'cloudinary', 'akamai', 'custom'] as const;
9
+ const FORMATS = ['image/avif', 'image/webp'] as const;
10
+ const DISPOSITIONS = ['inline', 'attachment'] as const;
11
+ const REMOTE_PATTERN_KEYS = new Set(['protocol', 'hostname', 'port', 'pathname', 'search']);
12
+ const LOCAL_PATTERN_KEYS = new Set(['pathname', 'search']);
13
+
14
+ function hasDefaultExport(file: string): boolean {
15
+ let source: string;
16
+ try {
17
+ source = fs.readFileSync(file, 'utf8');
18
+ } catch {
19
+ return false;
20
+ }
21
+ return /\bexport\s+default\b/.test(source) || /\bmodule\.exports\s*=/.test(source);
22
+ }
23
+
24
+ function received(value: unknown): string {
25
+ if (value === null) return 'null';
26
+ if (Array.isArray(value)) return 'array';
27
+ return typeof value;
28
+ }
29
+
30
+ function quotedUnion(options: readonly string[]): string {
31
+ return options.map(option => `'${option}'`).join(' | ');
32
+ }
33
+
34
+ /**
35
+ * Validate the images/assetPrefix portions of a next.config object. Returns
36
+ * Next's exact error strings (empty array when valid).
37
+ */
38
+ export function collectImagesConfigErrors(
39
+ config: Record<string, unknown>,
40
+ root: string,
41
+ ): string[] {
42
+ const errors: string[] = [];
43
+
44
+ const assetPrefix = config.assetPrefix;
45
+ if (typeof assetPrefix === 'string' && assetPrefix !== '' && !assetPrefix.startsWith('/')) {
46
+ try {
47
+ new URL(assetPrefix);
48
+ } catch {
49
+ // Bun's URL error text differs from Node's; emit Node's exact phrasing.
50
+ errors.push('Invalid assetPrefix provided. Original error: TypeError: Invalid URL');
51
+ }
52
+ }
53
+
54
+ const images = config.images;
55
+ if (images === undefined || images === null) return errors;
56
+ if (typeof images !== 'object' || Array.isArray(images)) {
57
+ errors.push(`Expected object, received ${received(images)} at "images"`);
58
+ return errors;
59
+ }
60
+ const img = images as Record<string, unknown>;
61
+
62
+ validateSizeArray(errors, img.deviceSizes, 'images.deviceSizes');
63
+ validateSizeArray(errors, img.imageSizes, 'images.imageSizes');
64
+
65
+ if (img.domains !== undefined) {
66
+ if (!Array.isArray(img.domains)) {
67
+ errors.push(`Expected array, received ${received(img.domains)} at "images.domains"`);
68
+ } else if (img.domains.length > 50) {
69
+ errors.push('Array must contain at most 50 element(s) at "images.domains"');
70
+ }
71
+ }
72
+
73
+ if (img.qualities !== undefined) {
74
+ if (!Array.isArray(img.qualities)) {
75
+ errors.push(`Expected array, received ${received(img.qualities)} at "images.qualities"`);
76
+ } else {
77
+ if (img.qualities.length < 1) {
78
+ errors.push('Array must contain at least 1 element(s) at "images.qualities"');
79
+ }
80
+ if (img.qualities.length > 20) {
81
+ errors.push('Array must contain at most 20 element(s) at "images.qualities"');
82
+ }
83
+ img.qualities.forEach((quality, index) => {
84
+ if (typeof quality !== 'number') {
85
+ errors.push(
86
+ `Expected number, received ${received(quality)} at "images.qualities[${index}]"`,
87
+ );
88
+ } else if (!Number.isInteger(quality)) {
89
+ errors.push(`Expected integer, received float at "images.qualities[${index}]"`);
90
+ } else if (quality < 1) {
91
+ errors.push(
92
+ `Number must be greater than or equal to 1 at "images.qualities[${index}]"`,
93
+ );
94
+ } else if (quality > 100) {
95
+ errors.push(
96
+ `Number must be less than or equal to 100 at "images.qualities[${index}]"`,
97
+ );
98
+ }
99
+ });
100
+ }
101
+ }
102
+
103
+ if (img.localPatterns !== undefined) {
104
+ if (!Array.isArray(img.localPatterns)) {
105
+ errors.push(
106
+ `Expected array, received ${received(img.localPatterns)} at "images.localPatterns"`,
107
+ );
108
+ } else {
109
+ if (img.localPatterns.length > 25) {
110
+ errors.push('Array must contain at most 25 element(s) at "images.localPatterns"');
111
+ }
112
+ img.localPatterns.forEach((pattern, index) => {
113
+ if (!pattern || typeof pattern !== 'object') return;
114
+ const unknown = Object.keys(pattern as Record<string, unknown>).filter(
115
+ key => !LOCAL_PATTERN_KEYS.has(key),
116
+ );
117
+ if (unknown.length > 0) {
118
+ errors.push(
119
+ `Unrecognized key(s) in object: ${unknown.map(key => `'${key}'`).join(', ')} at "images.localPatterns[${index}]"`,
120
+ );
121
+ }
122
+ });
123
+ }
124
+ }
125
+
126
+ if (img.remotePatterns !== undefined) {
127
+ if (!Array.isArray(img.remotePatterns)) {
128
+ errors.push(
129
+ `Expected array, received ${received(img.remotePatterns)} at "images.remotePatterns"`,
130
+ );
131
+ } else {
132
+ if (img.remotePatterns.length > 50) {
133
+ errors.push('Array must contain at most 50 element(s) at "images.remotePatterns"');
134
+ }
135
+ img.remotePatterns.forEach((pattern, index) => {
136
+ if (pattern instanceof URL) {
137
+ const protocol = pattern.protocol.replace(/:$/, '');
138
+ if (protocol !== 'http' && protocol !== 'https') {
139
+ errors.push(
140
+ `Specified images.remotePatterns must have protocol "http" or "https" received "${protocol}"`,
141
+ );
142
+ }
143
+ return;
144
+ }
145
+ if (!pattern || typeof pattern !== 'object') {
146
+ errors.push(
147
+ `Expected object, received ${received(pattern)} at "images.remotePatterns[${index}]"`,
148
+ );
149
+ return;
150
+ }
151
+ const obj = pattern as Record<string, unknown>;
152
+ const unknown = Object.keys(obj).filter(key => !REMOTE_PATTERN_KEYS.has(key));
153
+ if (unknown.length > 0) {
154
+ errors.push(
155
+ `Unrecognized key(s) in object: ${unknown.map(key => `'${key}'`).join(', ')} at "images.remotePatterns[${index}]"`,
156
+ );
157
+ }
158
+ if (typeof obj.hostname !== 'string') {
159
+ errors.push(
160
+ `"images.remotePatterns[${index}].hostname" is missing, expected string`,
161
+ );
162
+ }
163
+ });
164
+ }
165
+ }
166
+
167
+ if (img.loader !== undefined) {
168
+ if (typeof img.loader !== 'string' || !LOADERS.includes(img.loader as never)) {
169
+ errors.push(
170
+ `Expected ${quotedUnion(LOADERS)}, received '${received(img.loader) === 'string' ? (img.loader as string) : received(img.loader)}' at "images.loader"`,
171
+ );
172
+ } else if (img.loader !== 'default' && img.loader !== 'custom') {
173
+ const loader = img.loader;
174
+ if (typeof img.path !== 'string' || img.path === '' || img.path === '/_next/image') {
175
+ errors.push(
176
+ `Specified images.loader property (${loader}) also requires images.path property to be assigned to a URL prefix.`,
177
+ );
178
+ }
179
+ if (typeof img.loaderFile === 'string' && img.loaderFile !== '') {
180
+ errors.push(
181
+ `Specified images.loader property (${loader}) cannot be used with images.loaderFile property. Please set images.loader to "custom".`,
182
+ );
183
+ }
184
+ }
185
+ }
186
+
187
+ if (typeof img.loaderFile === 'string' && img.loaderFile !== '') {
188
+ // A leading-'/' loaderFile is project-root-relative in Next, not an
189
+ // absolute filesystem path — resolve it against the config root.
190
+ const absolute = img.loaderFile.startsWith('/')
191
+ ? path.join(root, img.loaderFile)
192
+ : path.resolve(root, img.loaderFile);
193
+ if (!fs.existsSync(absolute)) {
194
+ errors.push(`Specified images.loaderFile does not exist at "${absolute}".`);
195
+ } else if (!hasDefaultExport(absolute)) {
196
+ errors.push(
197
+ 'images.loaderFile detected but the file is missing default export.\n' +
198
+ 'Read more: https://nextjs.org/docs/messages/invalid-images-config',
199
+ );
200
+ }
201
+ }
202
+
203
+ if (img.formats !== undefined) {
204
+ if (!Array.isArray(img.formats)) {
205
+ errors.push(`Expected array, received ${received(img.formats)} at "images.formats"`);
206
+ } else {
207
+ img.formats.forEach((format, index) => {
208
+ if (!FORMATS.includes(format as never)) {
209
+ errors.push(
210
+ `Expected ${quotedUnion(FORMATS)}, received '${String(format)}' at "images.formats[${index}]"`,
211
+ );
212
+ }
213
+ });
214
+ }
215
+ }
216
+
217
+ if (
218
+ img.contentDispositionType !== undefined &&
219
+ !DISPOSITIONS.includes(img.contentDispositionType as never)
220
+ ) {
221
+ errors.push(
222
+ `Expected ${quotedUnion(DISPOSITIONS)}, received '${typeof img.contentDispositionType === 'string' ? img.contentDispositionType : received(img.contentDispositionType)}' at "images.contentDispositionType"`,
223
+ );
224
+ }
225
+
226
+ if (img.contentSecurityPolicy !== undefined && typeof img.contentSecurityPolicy !== 'string') {
227
+ errors.push(
228
+ `Expected string, received ${received(img.contentSecurityPolicy)} at "images.contentSecurityPolicy"`,
229
+ );
230
+ }
231
+
232
+ if (img.minimumCacheTTL !== undefined) {
233
+ if (typeof img.minimumCacheTTL !== 'number') {
234
+ errors.push(
235
+ `Expected number, received ${received(img.minimumCacheTTL)} at "images.minimumCacheTTL"`,
236
+ );
237
+ } else if (img.minimumCacheTTL < 0) {
238
+ errors.push('Number must be greater than or equal to 0 at "images.minimumCacheTTL"');
239
+ }
240
+ }
241
+
242
+ for (const key of ['dangerouslyAllowSVG', 'unoptimized', 'disableStaticImages'] as const) {
243
+ if (img[key] !== undefined && typeof img[key] !== 'boolean') {
244
+ errors.push(`Expected boolean, received ${received(img[key])} at "images.${key}"`);
245
+ }
246
+ }
247
+
248
+ return errors;
249
+ }
250
+
251
+ function validateSizeArray(errors: string[], value: unknown, label: string): void {
252
+ if (value === undefined) return;
253
+ if (!Array.isArray(value)) {
254
+ errors.push(`Expected array, received ${received(value)} at "${label}"`);
255
+ return;
256
+ }
257
+ if (value.length > 25) {
258
+ errors.push(`Array must contain at most 25 element(s) at "${label}"`);
259
+ }
260
+ value.forEach((size, index) => {
261
+ if (typeof size !== 'number') {
262
+ errors.push(`Expected number, received ${received(size)} at "${label}[${index}]"`);
263
+ } else if (size < 1) {
264
+ errors.push(`Number must be greater than or equal to 1 at "${label}[${index}]"`);
265
+ } else if (size > 10000) {
266
+ errors.push(`Number must be less than or equal to 10000 at "${label}[${index}]"`);
267
+ }
268
+ });
269
+ }