@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,131 @@
1
+ /**
2
+ * next.config `redirects` support (Next compat): a request whose pathname matches a redirect source is
3
+ * answered with an HTTP redirect to the destination before routing. Shares the
4
+ * source/condition/substitution machinery with rewrites; unlike rewrites, external (http/https)
5
+ * destinations are kept, since redirects commonly point off-site.
6
+ *
7
+ * Status: `permanent: true` is 308, `permanent: false` is 307, or an explicit numeric `statusCode`.
8
+ * Source params captured but not consumed by the destination are appended to the destination query,
9
+ * the same rule rewrites follow.
10
+ */
11
+ import { loadNextConfig } from '../actions/config';
12
+ import {
13
+ compileSource,
14
+ evaluateConditions,
15
+ parseConditions,
16
+ splitDestination,
17
+ substitute,
18
+ type RewriteCondition,
19
+ type RewriteRequestContext,
20
+ } from './rewrites';
21
+
22
+ export interface CompatRedirect {
23
+ regex: RegExp;
24
+ paramNames: string[];
25
+ destination: string;
26
+ has: RewriteCondition[];
27
+ missing: RewriteCondition[];
28
+ status: number;
29
+ /** `basePath: false`: the source matches the RAW path, outside the basePath. */
30
+ outsideBasePath: boolean;
31
+ }
32
+
33
+ interface RedirectEntry {
34
+ source?: string;
35
+ destination?: string;
36
+ permanent?: boolean;
37
+ statusCode?: number;
38
+ has?: unknown;
39
+ missing?: unknown;
40
+ basePath?: unknown;
41
+ }
42
+
43
+ export async function loadCompatRedirects(root: string): Promise<CompatRedirect[]> {
44
+ const config = await loadNextConfig(root);
45
+ const producer = config?.redirects;
46
+ if (typeof producer !== 'function') return [];
47
+ let value: unknown;
48
+ try {
49
+ value = await producer();
50
+ } catch {
51
+ return [];
52
+ }
53
+ if (!Array.isArray(value)) return [];
54
+
55
+ const redirects: CompatRedirect[] = [];
56
+ for (const entry of value as RedirectEntry[]) {
57
+ if (!entry?.source || !entry.destination) continue;
58
+ const has = parseConditions(entry.has);
59
+ const missing = parseConditions(entry.missing);
60
+ if (has === null || missing === null) continue; // unsupported condition shape
61
+ const compiled = compileSource(entry.source);
62
+ if (!compiled) continue;
63
+ const status =
64
+ typeof entry.statusCode === 'number' ? entry.statusCode : entry.permanent ? 308 : 307;
65
+ redirects.push({
66
+ ...compiled,
67
+ destination: entry.destination,
68
+ has,
69
+ missing,
70
+ status,
71
+ outsideBasePath: entry.basePath === false,
72
+ });
73
+ }
74
+ return redirects;
75
+ }
76
+
77
+ export interface RedirectResult {
78
+ location: string;
79
+ status: number;
80
+ }
81
+
82
+ /** First matching redirect for the request, or undefined. */
83
+ export function resolveCompatRedirect(
84
+ redirects: readonly CompatRedirect[],
85
+ pathname: string,
86
+ ctx?: RewriteRequestContext,
87
+ ): RedirectResult | undefined {
88
+ const context: RewriteRequestContext = ctx ?? {
89
+ host: '',
90
+ headers: new Headers(),
91
+ cookies: {},
92
+ query: new URLSearchParams(),
93
+ };
94
+ for (const redirect of redirects) {
95
+ const match = redirect.regex.exec(pathname);
96
+ if (!match) continue;
97
+
98
+ const params: Record<string, string> = {};
99
+ redirect.paramNames.forEach((name, index) => {
100
+ params[name] = match[index + 1] ?? '';
101
+ });
102
+
103
+ const conditionParams = evaluateConditions(redirect, context);
104
+ if (conditionParams === null) continue; // has/missing gate failed
105
+ Object.assign(params, conditionParams);
106
+
107
+ const [destPath, destQuery] = splitDestination(redirect.destination);
108
+ const { pathname: resolvedPath, used: usedInPath } = substitute(destPath, params);
109
+
110
+ const query = new URLSearchParams();
111
+ let usedInQuery = false;
112
+ if (destQuery) {
113
+ for (const [key, rawValue] of new URLSearchParams(destQuery).entries()) {
114
+ const { pathname: value, used } = substitute(rawValue, params);
115
+ if (used) usedInQuery = true;
116
+ query.set(key, value);
117
+ }
118
+ }
119
+ // Next appends source params not consumed by the destination to the query.
120
+ if (usedInPath.size === 0 && !usedInQuery) {
121
+ for (const [name, value] of Object.entries(params)) {
122
+ if (!query.has(name)) query.append(name, value);
123
+ }
124
+ }
125
+
126
+ const search = query.toString();
127
+ const location = search ? `${resolvedPath}${resolvedPath.includes('?') ? '&' : '?'}${search}` : resolvedPath;
128
+ return { location, status: redirect.status };
129
+ }
130
+ return undefined;
131
+ }
@@ -0,0 +1,136 @@
1
+ import type { MetadataLink } from '../../types';
2
+ import { getWorkUnit } from '../../request/context';
3
+ import { registerResponseFinalizers } from '../../extensions';
4
+ import type { ResponseFinalizerContext } from '../../extensions';
5
+ import { setResourceHintListener } from '../../render/resource-hints';
6
+ import { getNextConfig } from './config-loader';
7
+
8
+ const RESOURCE_HINTS_KEY = Symbol.for('pnext.compat.resourceHints');
9
+ const DEFAULT_REACT_MAX_HEADERS_LENGTH = 6000;
10
+ // Next emits react-dom preload()/preconnect() hints as a `Link` response
11
+ // header by default in production; nothing in the compat runtime ever called
12
+ // enableResourceHintHeaders, so the finalizer was dead code. The finalizer is
13
+ // only registered under next-compat, so defaulting on is scoped already.
14
+ let resourceHintHeadersEnabled = true;
15
+
16
+ export type ResourceHint = MetadataLink & {
17
+ rel: string;
18
+ url: string;
19
+ };
20
+
21
+ export function enableResourceHintHeaders(enabled: boolean): void {
22
+ resourceHintHeadersEnabled = enabled;
23
+ }
24
+
25
+ export function registerResourceHintFinalizer(): void {
26
+ registerResponseFinalizers(resourceHintResponseFinalizer);
27
+ // Every react-dom preload()/preconnect()/preinit() during a render lands on
28
+ // the active work unit so the finalizer (and the build prerender) can emit
29
+ // Next's `Link` response header alongside the in-document <link> tags.
30
+ setResourceHintListener(hint => recordResourceHint(hint as ResourceHint));
31
+ }
32
+
33
+ export function recordResourceHint(hint: ResourceHint): void {
34
+ const unit = getWorkUnit();
35
+ if (!unit) return;
36
+
37
+ const existing = (unit.compat?.[RESOURCE_HINTS_KEY] as ResourceHint[] | undefined) ?? [];
38
+ if (!unit.compat) unit.compat = {};
39
+ unit.compat[RESOURCE_HINTS_KEY] = dedupeResourceHints([...existing, hint]);
40
+ }
41
+
42
+ export function takeResourceHintsForResponse(): ResourceHint[] {
43
+ const unit = getWorkUnit();
44
+ const compat = unit?.compat;
45
+ if (!compat) return [];
46
+ const hints = (compat[RESOURCE_HINTS_KEY] as ResourceHint[] | undefined) ?? [];
47
+ compat[RESOURCE_HINTS_KEY] = undefined;
48
+ return hints;
49
+ }
50
+
51
+ const ALLOWED_ROUTE_KINDS = new Set<ResponseFinalizerContext['routeKind']>([
52
+ 'html',
53
+ 'data',
54
+ ]);
55
+
56
+ function resourceHintResponseFinalizer(ctx: ResponseFinalizerContext): void {
57
+ if (!shouldEmitResourceHintsHeader()) return;
58
+ if (ctx.hints?.runtime === 'edge') return;
59
+ if (!ALLOWED_ROUTE_KINDS.has(ctx.routeKind)) return;
60
+
61
+ const value = takeResourceHintHeader();
62
+ if (value === undefined) return;
63
+ const existing = ctx.headers.get('link');
64
+ ctx.headers.set('link', existing ? `${existing}, ${value}` : value);
65
+ }
66
+
67
+ function shouldEmitResourceHintsHeader(): boolean {
68
+ return resourceHintHeadersEnabled;
69
+ }
70
+
71
+ function getReactMaxHeadersLength(): number {
72
+ const configValue = getNextConfig().reactMaxHeadersLength;
73
+ if (typeof configValue === 'number' && Number.isFinite(configValue) && configValue >= 0) {
74
+ return Math.floor(configValue);
75
+ }
76
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
77
+ const envValue = process.env.TEST_REACT_MAX_HEADERS_LENGTH;
78
+ if (typeof envValue === 'string' && envValue.trim() !== '') {
79
+ const parsed = Number.parseInt(envValue, 10);
80
+ if (Number.isFinite(parsed) && parsed >= 0) return Math.floor(parsed);
81
+ }
82
+ return DEFAULT_REACT_MAX_HEADERS_LENGTH;
83
+ }
84
+
85
+ export function takeResourceHintHeader(): string | undefined {
86
+ const hints = takeResourceHintsForResponse();
87
+ if (hints.length === 0) return undefined;
88
+
89
+ const maxLength = getReactMaxHeadersLength();
90
+ if (!(maxLength > 0)) return undefined;
91
+
92
+ const entries = hints.map(hint => formatLinkHeaderEntry(hint)).filter(Boolean);
93
+ if (entries.length === 0) return undefined;
94
+
95
+ const values: string[] = [];
96
+ for (const entry of entries) {
97
+ const candidate = values.length > 0 ? `${values.join(', ')}, ${entry}` : entry;
98
+ if (candidate.length > maxLength) break;
99
+ values.push(entry);
100
+ }
101
+
102
+ return values.length === 0 ? undefined : values.join(', ');
103
+ }
104
+
105
+ // React's Link-header entry shape (ReactFizz getPreloadAsHeader): the rel is
106
+ // unquoted, every other param is quoted, and crossorigin normalizes
107
+ // 'anonymous' to the empty string. The react-max-headers-length suite computes
108
+ // expected header lengths from React's exact per-entry size, so the format
109
+ // must match byte-for-byte.
110
+ function formatLinkHeaderEntry(hint: ResourceHint): string {
111
+ const parts = [`<${hint.url}>`, `rel=${hint.rel}`];
112
+ if (hint.as) parts.push(`as="${hint.as}"`);
113
+ if (hint.crossOrigin !== undefined)
114
+ parts.push(`crossorigin="${hint.crossOrigin === 'anonymous' ? '' : hint.crossOrigin}"`);
115
+ if (hint.type) parts.push(`type="${hint.type}"`);
116
+ if (hint.media) parts.push(`media="${hint.media}"`);
117
+ if (hint.sizes) parts.push(`sizes="${hint.sizes}"`);
118
+ if (hint.title) parts.push(`title="${hint.title}"`);
119
+ if (hint.hrefLang) parts.push(`hreflang="${hint.hrefLang}"`);
120
+ if (hint.fetchPriority) parts.push(`fetchpriority="${hint.fetchPriority}"`);
121
+ if (hint.nonce) parts.push(`nonce="${hint.nonce}"`);
122
+ return parts.join('; ');
123
+ }
124
+
125
+ function dedupeResourceHints(hints: ResourceHint[]): ResourceHint[] {
126
+ if (hints.length <= 1) return hints;
127
+ const seen = new Set<string>();
128
+ const next: ResourceHint[] = [];
129
+ for (const hint of hints) {
130
+ const key = formatLinkHeaderEntry(hint);
131
+ if (seen.has(key)) continue;
132
+ seen.add(key);
133
+ next.push(hint);
134
+ }
135
+ return next;
136
+ }
@@ -0,0 +1,350 @@
1
+ /**
2
+ * next.config `rewrites` support (Next compat): request pathnames are rewritten to their destination
3
+ * before static-file lookup and routing, so prebuilt output and revalidatePath marks work against the
4
+ * destination path.
5
+ *
6
+ * Supported: array form and the { beforeFiles, afterFiles, fallback } object form (fallback entries
7
+ * apply only to requests that would otherwise 404); `:param`, `:param*`/`:param+`/`:param?` and
8
+ * `:param(regex)` tokens in the source; `:param` / `:param*` substitution in the destination path AND
9
+ * query; has/missing conditions of type host/header/query/cookie, including named-capture value regexes
10
+ * whose captures feed destination params.
11
+ *
12
+ * Rewrite parameters follow Next's rule: params captured from the source/has that are NOT consumed by
13
+ * the destination are appended to the query - but only when NONE of the params are used in the
14
+ * destination. If any param is used there, no params are auto-appended and the incoming query is
15
+ * preserved. This is what keeps rewrite params from leaking into searchParams.
16
+ *
17
+ * Divergence: the render sees the destination URL (Next keeps the requested URL as canonical for
18
+ * usePathname; recordCanonicalUrl handles that separately).
19
+ */
20
+ import { loadNextConfig, loadResolvedNextConfig } from '../actions/config';
21
+
22
+ export interface CompatRewrite {
23
+ regex: RegExp;
24
+ paramNames: string[];
25
+ destination: string;
26
+ has: RewriteCondition[];
27
+ missing: RewriteCondition[];
28
+ /**
29
+ * A `fallback` rewrite: Next applies it only after filesystem AND dynamic
30
+ * routes have all missed, i.e. only on a request that would otherwise 404.
31
+ * `beforeFiles`/`afterFiles` (and the array form, which is `afterFiles`)
32
+ * apply ahead of routing and carry this false.
33
+ */
34
+ fallback: boolean;
35
+ /**
36
+ * `basePath: false` on the entry: the source is matched against the RAW
37
+ * request path, outside the configured basePath. Core 404s such paths before
38
+ * routing, so these rules get their turn from the outside-basePath pass
39
+ * instead of the normal one (and never both).
40
+ */
41
+ outsideBasePath: boolean;
42
+ }
43
+
44
+ export interface RewriteCondition {
45
+ type: 'host' | 'header' | 'query' | 'cookie';
46
+ key?: string;
47
+ value?: string;
48
+ }
49
+
50
+ interface RewriteEntry {
51
+ source?: string;
52
+ destination?: string;
53
+ has?: unknown;
54
+ missing?: unknown;
55
+ basePath?: unknown;
56
+ }
57
+
58
+ /** Request facts a rewrite's `has`/`missing` conditions are evaluated against. */
59
+ export interface RewriteRequestContext {
60
+ host: string;
61
+ headers: Headers;
62
+ cookies: Record<string, string>;
63
+ query: URLSearchParams;
64
+ }
65
+
66
+ const VALID_CONDITION_TYPES = new Set(['host', 'header', 'query', 'cookie']);
67
+
68
+ export async function loadCompatRewrites(root: string): Promise<CompatRewrite[]> {
69
+ let config = await loadResolvedNextConfig(root);
70
+ if (typeof config?.rewrites !== 'function') config = await loadNextConfig(root);
71
+ const producer = config?.rewrites;
72
+ if (typeof producer !== 'function') return [];
73
+ let value: unknown;
74
+ try {
75
+ value = await producer();
76
+ } catch {
77
+ return [];
78
+ }
79
+ const entries: (RewriteEntry & { fallback: boolean })[] = Array.isArray(value)
80
+ ? (value as RewriteEntry[]).map(entry => ({ ...entry, fallback: false }))
81
+ : value && typeof value === 'object'
82
+ ? (['beforeFiles', 'afterFiles', 'fallback'] as const).flatMap(key => {
83
+ const list = (value as Record<string, unknown>)[key];
84
+ return Array.isArray(list)
85
+ ? (list as RewriteEntry[]).map(entry => ({ ...entry, fallback: key === 'fallback' }))
86
+ : [];
87
+ })
88
+ : [];
89
+
90
+ const rewrites: CompatRewrite[] = [];
91
+ for (const entry of entries) {
92
+ if (!entry?.source || !entry.destination) continue;
93
+ const has = parseConditions(entry.has);
94
+ const missing = parseConditions(entry.missing);
95
+ if (has === null || missing === null) continue; // unsupported condition shape
96
+ const compiled = compileSource(entry.source);
97
+ if (compiled) {
98
+ rewrites.push({
99
+ ...compiled,
100
+ destination: entry.destination,
101
+ has,
102
+ missing,
103
+ fallback: entry.fallback,
104
+ outsideBasePath: entry.basePath === false,
105
+ });
106
+ }
107
+ }
108
+ return rewrites;
109
+ }
110
+
111
+ /** Parse a has/missing list into typed conditions; null if any entry is unsupported. */
112
+ export function parseConditions(value: unknown): RewriteCondition[] | null {
113
+ if (value === undefined) return [];
114
+ if (!Array.isArray(value)) return null;
115
+ const conditions: RewriteCondition[] = [];
116
+ for (const raw of value as unknown[]) {
117
+ if (!raw || typeof raw !== 'object') return null;
118
+ const { type, key, value: condValue } = raw as Record<string, unknown>;
119
+ if (typeof type !== 'string' || !VALID_CONDITION_TYPES.has(type)) return null;
120
+ if (key !== undefined && typeof key !== 'string') return null;
121
+ if (condValue !== undefined && typeof condValue !== 'string') return null;
122
+ conditions.push({
123
+ type: type as RewriteCondition['type'],
124
+ ...(typeof key === 'string' ? { key } : {}),
125
+ ...(typeof condValue === 'string' ? { value: condValue } : {}),
126
+ });
127
+ }
128
+ return conditions;
129
+ }
130
+
131
+ export function compileSource(source: string): { regex: RegExp; paramNames: string[] } | null {
132
+ const paramNames: string[] = [];
133
+ // Match against the pathname. Note: the source is a path-to-regexp pattern
134
+ // (query matching is done via `has` conditions), so it never carries a
135
+ // literal `?query`. We must NOT split on `?` — a `?` may legitimately appear
136
+ // inside a regex token such as `:path((?!sitemap.xml$).*)` or `(?<name>...)`.
137
+ const segments = source.split('/').filter(Boolean);
138
+ const parts: string[] = [];
139
+ for (const segment of segments) {
140
+ // :param(customRegex) — a named param with an explicit regex body (may span
141
+ // the whole segment, e.g. `:path((?!sitemap.xml$).*)`).
142
+ const custom = /^:([A-Za-z_$][\w$]*)\((.*)\)$/.exec(segment);
143
+ if (custom?.[1] !== undefined && custom[2] !== undefined) {
144
+ paramNames.push(custom[1]);
145
+ parts.push(`(${custom[2]})`);
146
+ continue;
147
+ }
148
+ const catchAll = /^:([A-Za-z_$][\w$]*)[*+]$/.exec(segment);
149
+ if (catchAll?.[1]) {
150
+ paramNames.push(catchAll[1]);
151
+ parts.push('(.*)');
152
+ continue;
153
+ }
154
+ const optional = /^:([A-Za-z_$][\w$]*)\?$/.exec(segment);
155
+ if (optional?.[1]) {
156
+ paramNames.push(optional[1]);
157
+ parts.push('([^/]*)');
158
+ continue;
159
+ }
160
+ const param = /^:([A-Za-z_$][\w$]*)$/.exec(segment);
161
+ if (param?.[1]) {
162
+ paramNames.push(param[1]);
163
+ parts.push('([^/]+)');
164
+ continue;
165
+ }
166
+ // Plain literal segments may carry regex-significant filename characters
167
+ // (`favicon.ico`, `sitemap.xml`) — escape them. Anything with actual
168
+ // pattern syntax beyond that is unsupported.
169
+ if (/^[\w.\- ]+$/.test(segment)) {
170
+ parts.push(segment.replace(/\./g, '\\.'));
171
+ continue;
172
+ }
173
+ if (/[.*+?^${}()|[\]\\]/.test(segment)) return null; // unsupported literal syntax
174
+ parts.push(segment.replace(/[/\\]/g, ''));
175
+ }
176
+ return { regex: new RegExp(`^/${parts.join('/')}/?$`), paramNames };
177
+ }
178
+
179
+ /** Evaluate a single has/missing condition, returning captured params or null (no match). */
180
+ function evaluateCondition(
181
+ condition: RewriteCondition,
182
+ ctx: RewriteRequestContext,
183
+ ): Record<string, string> | null {
184
+ const target = conditionTarget(condition, ctx);
185
+ if (target === undefined) return null;
186
+ if (condition.value === undefined) return {}; // presence-only match
187
+ // Anchor the value regex the way Next does (full-value match).
188
+ const regex = new RegExp(`^(?:${condition.value})$`);
189
+ const match = regex.exec(target);
190
+ if (!match) return null;
191
+ return match.groups ? { ...match.groups } : {};
192
+ }
193
+
194
+ function conditionTarget(
195
+ condition: RewriteCondition,
196
+ ctx: RewriteRequestContext,
197
+ ): string | undefined {
198
+ switch (condition.type) {
199
+ case 'host':
200
+ return ctx.host;
201
+ case 'header':
202
+ return condition.key ? (ctx.headers.get(condition.key) ?? undefined) : undefined;
203
+ case 'cookie':
204
+ return condition.key ? ctx.cookies[condition.key] : undefined;
205
+ case 'query':
206
+ return condition.key ? (ctx.query.get(condition.key) ?? undefined) : undefined;
207
+ default:
208
+ return undefined;
209
+ }
210
+ }
211
+
212
+ export interface RewriteResult {
213
+ pathname: string;
214
+ /** Search params after the rewrite (original query + any auto-appended params). */
215
+ search: URLSearchParams;
216
+ /** Fully-qualified destination for external rewrites (`https://...`). */
217
+ destination?: string;
218
+ }
219
+
220
+ /**
221
+ * Back-compat wrapper: the destination pathname of the first matching rewrite,
222
+ * or undefined. Prefer resolveCompatRewrite (returns the resolved search params
223
+ * too, so rewrite params never leak into searchParams and `has` conditions can
224
+ * be evaluated against the request).
225
+ */
226
+ export function applyCompatRewrites(
227
+ rewrites: readonly CompatRewrite[],
228
+ pathname: string,
229
+ ctx?: RewriteRequestContext,
230
+ ): string | undefined {
231
+ return resolveCompatRewrite(rewrites, pathname, ctx)?.pathname;
232
+ }
233
+
234
+ /**
235
+ * First matching rewrite applied to the request, or undefined. Returns the
236
+ * destination pathname plus the resolved search params (rewrite params only
237
+ * leak into the query when none are used in the destination).
238
+ */
239
+ export function resolveCompatRewrite(
240
+ rewrites: readonly CompatRewrite[],
241
+ pathname: string,
242
+ ctx?: RewriteRequestContext,
243
+ ): RewriteResult | undefined {
244
+ const context: RewriteRequestContext = ctx ?? {
245
+ host: '',
246
+ headers: new Headers(),
247
+ cookies: {},
248
+ query: new URLSearchParams(),
249
+ };
250
+ for (const rewrite of rewrites) {
251
+ const match = rewrite.regex.exec(pathname);
252
+ if (!match) continue;
253
+
254
+ // Collect all params: source captures first, then has/missing captures.
255
+ const params: Record<string, string> = {};
256
+ rewrite.paramNames.forEach((name, index) => {
257
+ params[name] = match[index + 1] ?? '';
258
+ });
259
+
260
+ const conditionParams = evaluateConditions(rewrite, context);
261
+ if (conditionParams === null) continue; // has/missing gate failed
262
+ Object.assign(params, conditionParams);
263
+
264
+ const [destPath, destQuery] = splitDestination(rewrite.destination);
265
+ const { pathname: newPath, used: usedInPath } = substitute(destPath, params);
266
+ const search = new URLSearchParams(context.query);
267
+
268
+ // Destination-declared query params (?key=:val) are always applied.
269
+ let usedInQuery = false;
270
+ if (destQuery) {
271
+ const destParams = new URLSearchParams(destQuery);
272
+ for (const [key, rawValue] of destParams.entries()) {
273
+ const { pathname: value, used } = substitute(rawValue, params);
274
+ if (used) usedInQuery = true;
275
+ search.set(key, value);
276
+ }
277
+ }
278
+
279
+ // Next rule: only auto-append unused params to the query when NONE of the
280
+ // params were used in the destination. If any param is used, nothing leaks.
281
+ const anyUsed = usedInPath.size > 0 || usedInQuery;
282
+ if (!anyUsed) {
283
+ for (const [name, value] of Object.entries(params)) {
284
+ if (!search.has(name)) search.append(name, value);
285
+ }
286
+ }
287
+
288
+ return {
289
+ pathname: newPath,
290
+ search,
291
+ destination: /^https?:/.test(newPath) ? normalizeDestination(newPath, search) : undefined,
292
+ };
293
+ }
294
+ return undefined;
295
+ }
296
+
297
+ function normalizeDestination(
298
+ pathname: string,
299
+ search: URLSearchParams,
300
+ ): string | undefined {
301
+ try {
302
+ const destination = new URL(pathname);
303
+ for (const [key, value] of search.entries()) {
304
+ destination.searchParams.set(key, value);
305
+ }
306
+ return destination.toString();
307
+ } catch {
308
+ return undefined;
309
+ }
310
+ }
311
+
312
+ /** Apply all has (must all match) + missing (must all fail); returns merged captures or null. */
313
+ export function evaluateConditions(
314
+ rule: { has: RewriteCondition[]; missing: RewriteCondition[] },
315
+ ctx: RewriteRequestContext,
316
+ ): Record<string, string> | null {
317
+ const params: Record<string, string> = {};
318
+ for (const condition of rule.has) {
319
+ const captured = evaluateCondition(condition, ctx);
320
+ if (captured === null) return null;
321
+ Object.assign(params, captured);
322
+ }
323
+ for (const condition of rule.missing) {
324
+ if (evaluateCondition(condition, ctx) !== null) return null;
325
+ }
326
+ return params;
327
+ }
328
+
329
+ export function splitDestination(destination: string): [string, string | undefined] {
330
+ const index = destination.indexOf('?');
331
+ if (index === -1) return [destination, undefined];
332
+ return [destination.slice(0, index), destination.slice(index + 1)];
333
+ }
334
+
335
+ /**
336
+ * Substitute `:param*` / `:param` tokens in a destination string. Returns the
337
+ * result and the set of param names actually consumed (drives the leak rule).
338
+ */
339
+ export function substitute(
340
+ template: string,
341
+ params: Record<string, string>,
342
+ ): { pathname: string; used: Set<string> } {
343
+ const used = new Set<string>();
344
+ const result = template.replace(/:([A-Za-z_$][\w$]*)(\*|\+|\?)?/g, (whole, name: string) => {
345
+ if (!(name in params)) return whole;
346
+ used.add(name);
347
+ return params[name] ?? '';
348
+ });
349
+ return { pathname: result, used };
350
+ }