@wular/pnext 0.1.6 → 0.1.8

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 (337) hide show
  1. package/bin/pnext +9 -2
  2. package/dist/server/_chunks/aliases-pycbtjt1.js +46 -0
  3. package/dist/server/_chunks/analyze-4d9e00g9.js +449 -0
  4. package/dist/server/_chunks/boot-bhemypxz.js +38 -0
  5. package/dist/server/_chunks/build-c991dv65.js +86 -0
  6. package/dist/server/_chunks/build-kh5e3x48.js +3341 -0
  7. package/dist/server/_chunks/build-tier-0a96kmx3.js +2456 -0
  8. package/dist/server/_chunks/compile-040r79gj.js +22 -0
  9. package/dist/server/_chunks/config-0tvz03a6.js +24 -0
  10. package/dist/server/_chunks/config-27rj7830.js +36 -0
  11. package/dist/server/_chunks/create-9rvwshsp.js +135 -0
  12. package/dist/server/_chunks/dev-ex9exynt.js +220 -0
  13. package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
  14. package/dist/server/_chunks/generate-myw9hef7.js +22 -0
  15. package/dist/server/_chunks/index-0r5eqfc7.js +279 -0
  16. package/dist/server/_chunks/index-0yjv5v47.js +105 -0
  17. package/dist/server/_chunks/index-11xpbfej.js +17 -0
  18. package/dist/server/_chunks/index-141vcwk6.js +109 -0
  19. package/dist/server/_chunks/index-1pzm1hdy.js +4130 -0
  20. package/dist/server/_chunks/index-29pq0kax.js +590 -0
  21. package/dist/server/_chunks/index-2vz0wzdn.js +59 -0
  22. package/dist/server/_chunks/index-2wfv4kvx.js +182 -0
  23. package/dist/server/_chunks/index-38xpdxwd.js +194 -0
  24. package/dist/server/_chunks/index-3e075kny.js +128 -0
  25. package/dist/server/_chunks/index-3ycf5ckv.js +472 -0
  26. package/dist/server/_chunks/index-4399bvd2.js +18 -0
  27. package/dist/server/_chunks/index-4da76t62.js +297 -0
  28. package/dist/server/_chunks/index-4t92hdxd.js +172 -0
  29. package/dist/server/_chunks/index-4zzebszd.js +10 -0
  30. package/dist/server/_chunks/index-52k54ykj.js +95 -0
  31. package/dist/server/_chunks/index-5hr4062s.js +70 -0
  32. package/dist/server/_chunks/index-68m3mwbd.js +84 -0
  33. package/dist/server/_chunks/index-6b009mt0.js +56 -0
  34. package/dist/server/_chunks/index-6gex3pfa.js +15 -0
  35. package/dist/server/_chunks/index-6kp9r0d9.js +11 -0
  36. package/dist/server/_chunks/index-6q7zvrap.js +134 -0
  37. package/dist/server/_chunks/index-71885zaq.js +284 -0
  38. package/dist/server/_chunks/index-73z4esab.js +38 -0
  39. package/dist/server/_chunks/index-79ngvam8.js +60 -0
  40. package/dist/server/_chunks/index-7wc9n8be.js +25 -0
  41. package/dist/server/_chunks/index-8ct7rqy3.js +198 -0
  42. package/dist/server/_chunks/index-8kcxdfya.js +206 -0
  43. package/dist/server/_chunks/index-9ch50vtq.js +18 -0
  44. package/dist/server/_chunks/index-9f49w76j.js +213 -0
  45. package/dist/server/_chunks/index-9hz3s0m8.js +851 -0
  46. package/dist/server/_chunks/index-akkrvjn6.js +535 -0
  47. package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
  48. package/dist/server/_chunks/index-b7j671dj.js +207 -0
  49. package/dist/server/_chunks/index-bcws5eh2.js +35 -0
  50. package/dist/server/_chunks/index-bzd2ef7t.js +10 -0
  51. package/dist/server/_chunks/index-cjgqs13g.js +65 -0
  52. package/dist/server/_chunks/index-ckkdpht5.js +41 -0
  53. package/dist/server/_chunks/index-cn86z041.js +296 -0
  54. package/dist/server/_chunks/index-cvr4z22m.js +92 -0
  55. package/dist/server/_chunks/index-cyfednwb.js +87 -0
  56. package/dist/server/_chunks/index-d158y67j.js +152 -0
  57. package/dist/server/_chunks/index-d1jsmhhk.js +955 -0
  58. package/dist/server/_chunks/index-dg0f05zs.js +199 -0
  59. package/dist/server/_chunks/index-dgr390q7.js +9438 -0
  60. package/dist/server/_chunks/index-drwv61cc.js +185 -0
  61. package/dist/server/_chunks/index-e3a6yx84.js +18 -0
  62. package/dist/server/_chunks/index-e88tyv35.js +45 -0
  63. package/dist/server/_chunks/index-en7n9f7m.js +22 -0
  64. package/dist/server/_chunks/index-entm8bmz.js +28 -0
  65. package/dist/server/_chunks/index-evhd413j.js +57 -0
  66. package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
  67. package/dist/server/_chunks/index-fjv96re1.js +5295 -0
  68. package/dist/server/_chunks/index-fn1chkkd.js +214 -0
  69. package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
  70. package/dist/server/_chunks/index-gcmj16xy.js +92 -0
  71. package/dist/server/_chunks/index-gkndxkxf.js +736 -0
  72. package/dist/server/_chunks/index-grta8ap3.js +238 -0
  73. package/dist/server/_chunks/index-h2a70tg4.js +7 -0
  74. package/dist/server/_chunks/index-h4xy648y.js +51 -0
  75. package/dist/server/_chunks/index-hdbwtdv2.js +352 -0
  76. package/dist/server/_chunks/index-he6n2bvp.js +1270 -0
  77. package/dist/server/_chunks/index-j0m1jxqa.js +1253 -0
  78. package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
  79. package/dist/server/_chunks/index-j681c48a.js +2530 -0
  80. package/dist/server/_chunks/index-jbj7ehef.js +50 -0
  81. package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
  82. package/dist/server/_chunks/index-jks0x0a1.js +14 -0
  83. package/dist/server/_chunks/index-jn9rbhrx.js +1599 -0
  84. package/dist/server/_chunks/index-jt8e1pje.js +12 -0
  85. package/dist/server/_chunks/index-kdhxpwyq.js +483 -0
  86. package/dist/server/_chunks/index-knp0s912.js +7 -0
  87. package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
  88. package/dist/server/_chunks/index-kresp2n8.js +445 -0
  89. package/dist/server/_chunks/index-m63czgq5.js +590 -0
  90. package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
  91. package/dist/server/_chunks/index-n4hxpng2.js +11 -0
  92. package/dist/server/_chunks/index-n629dy38.js +1071 -0
  93. package/dist/server/_chunks/index-nf3df4jy.js +266 -0
  94. package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
  95. package/dist/server/_chunks/index-pym1cezq.js +39 -0
  96. package/dist/server/_chunks/index-q3r9z8mq.js +213 -0
  97. package/dist/server/_chunks/index-q738kkhj.js +100 -0
  98. package/dist/server/_chunks/index-qcrvt6er.js +112 -0
  99. package/dist/server/_chunks/index-qjj6gddn.js +54 -0
  100. package/dist/server/_chunks/index-qpwsgt3a.js +158 -0
  101. package/dist/server/_chunks/index-r97ptz1q.js +97 -0
  102. package/dist/server/_chunks/index-ras0sdab.js +26 -0
  103. package/dist/server/_chunks/index-rcs322t1.js +19 -0
  104. package/dist/server/_chunks/index-rgj5mx84.js +101 -0
  105. package/dist/server/_chunks/index-rjegyfx4.js +1435 -0
  106. package/dist/server/_chunks/index-rt9qq1va.js +4 -0
  107. package/dist/server/_chunks/index-rthrsed5.js +356 -0
  108. package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
  109. package/dist/server/_chunks/index-stbk3jhn.js +31 -0
  110. package/dist/server/_chunks/index-tf4j19ge.js +130 -0
  111. package/dist/server/_chunks/index-tgcakdxc.js +125 -0
  112. package/dist/server/_chunks/index-tgsfjmep.js +74 -0
  113. package/dist/server/_chunks/index-tq1f96v7.js +51 -0
  114. package/dist/server/_chunks/index-tq3b39a5.js +107 -0
  115. package/dist/server/_chunks/index-typcbcsv.js +819 -0
  116. package/dist/server/_chunks/index-v1ags1na.js +132 -0
  117. package/dist/server/_chunks/index-vmr35jjw.js +170 -0
  118. package/dist/server/_chunks/index-vvq7rqtn.js +329 -0
  119. package/dist/server/_chunks/index-vz0km5qk.js +33 -0
  120. package/dist/server/_chunks/index-w6h8m6vp.js +138 -0
  121. package/dist/server/_chunks/index-web361b8.js +138 -0
  122. package/dist/server/_chunks/index-wm36rj4v.js +311 -0
  123. package/dist/server/_chunks/index-x0y005nh.js +283 -0
  124. package/dist/server/_chunks/index-x2m17v9c.js +6166 -0
  125. package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
  126. package/dist/server/_chunks/index-x9c55pzy.js +871 -0
  127. package/dist/server/_chunks/index-xh1nbk46.js +1299 -0
  128. package/dist/server/_chunks/index-xvj80c3a.js +30 -0
  129. package/dist/server/_chunks/index-xywjg2vc.js +100 -0
  130. package/dist/server/_chunks/index-y4d02znn.js +66 -0
  131. package/dist/server/_chunks/index-y7mzvq2q.js +110 -0
  132. package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
  133. package/dist/server/_chunks/index-yhxkxghe.js +56 -0
  134. package/dist/server/_chunks/index-yrjkqtfj.js +86 -0
  135. package/dist/server/_chunks/index-z88qv16t.js +11 -0
  136. package/dist/server/_chunks/index-zfrwq472.js +154 -0
  137. package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
  138. package/dist/server/_chunks/loader-5en7wpt5.js +80 -0
  139. package/dist/server/_chunks/modules-t92favpj.js +76 -0
  140. package/dist/server/_chunks/pipeline-4t9q43ht.js +836 -0
  141. package/dist/server/_chunks/production-385bw8df.js +55 -0
  142. package/dist/server/_chunks/proxy-config-60gprphe.js +23 -0
  143. package/dist/server/_chunks/proxy-gfhmktbk.js +35 -0
  144. package/dist/server/_chunks/router-xnrva921.js +501 -0
  145. package/dist/server/_chunks/routes-ht868pdc.js +66 -0
  146. package/dist/server/_chunks/run-d40d63ag.js +697 -0
  147. package/dist/server/_chunks/server-2p8fgqq2.js +1839 -0
  148. package/dist/server/_chunks/tsconfig-defaults-bcn34eqb.js +21 -0
  149. package/dist/server/_chunks/typegen-dvtcjtaw.js +37 -0
  150. package/dist/server/api/client-cache.js +14 -0
  151. package/dist/server/cli/index.js +375 -0
  152. package/dist/server/cli/start.js +158 -0
  153. package/dist/server/client/reference.js +16 -0
  154. package/dist/server/compat/next/cache.js +56 -0
  155. package/dist/server/compat/next/canonical-url.js +13 -0
  156. package/dist/server/compat/next/client-cache.js +57 -0
  157. package/dist/server/compat/next/client-navigation.js +232 -0
  158. package/dist/server/compat/next/client-only.js +1 -0
  159. package/dist/server/compat/next/client-script.js +178 -0
  160. package/dist/server/compat/next/client-server.js +34 -0
  161. package/dist/server/compat/next/config-headers.js +34 -0
  162. package/dist/server/compat/next/config-loader.js +25 -0
  163. package/dist/server/compat/next/config.js +10 -0
  164. package/dist/server/compat/next/constants.js +18 -0
  165. package/dist/server/compat/next/custom-server.js +10 -0
  166. package/dist/server/compat/next/dist/client/components/app-router-headers.js +48 -0
  167. package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +27 -0
  168. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +25 -0
  169. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +25 -0
  170. package/dist/server/compat/next/dynamic.js +21 -0
  171. package/dist/server/compat/next/error.js +112 -0
  172. package/dist/server/compat/next/font/cache.js +16 -0
  173. package/dist/server/compat/next/font/google.js +3 -0
  174. package/dist/server/compat/next/font/index.js +1 -0
  175. package/dist/server/compat/next/font/local.js +24 -0
  176. package/dist/server/compat/next/font/runtime-client.js +33 -0
  177. package/dist/server/compat/next/font/runtime.js +43 -0
  178. package/dist/server/compat/next/font/shared.js +22 -0
  179. package/dist/server/compat/next/form.js +129 -0
  180. package/dist/server/compat/next/head.js +10 -0
  181. package/dist/server/compat/next/headers.js +30 -0
  182. package/dist/server/compat/next/image/client.js +11 -0
  183. package/dist/server/compat/next/image/config.js +18 -0
  184. package/dist/server/compat/next/image/optimizer.js +25 -0
  185. package/dist/server/compat/next/image/patterns.js +12 -0
  186. package/dist/server/compat/next/image/props.js +16 -0
  187. package/dist/server/compat/next/image/shared.js +86 -0
  188. package/dist/server/compat/next/image/static-metadata.js +32 -0
  189. package/dist/server/compat/next/image/validate.js +8 -0
  190. package/dist/server/compat/next/image.js +59 -0
  191. package/dist/server/compat/next/index.js +1 -0
  192. package/dist/server/compat/next/legacy-image.js +78 -0
  193. package/dist/server/compat/next/legacy-request-apis.js +24 -0
  194. package/dist/server/compat/next/link-transform.js +15 -0
  195. package/dist/server/compat/next/link.js +14 -0
  196. package/dist/server/compat/next/navigation.js +191 -0
  197. package/dist/server/compat/next/offline.js +45 -0
  198. package/dist/server/compat/next/og.js +8 -0
  199. package/dist/server/compat/next/preferred-region.js +8 -0
  200. package/dist/server/compat/next/redirects.js +32 -0
  201. package/dist/server/compat/next/resource-hints.js +30 -0
  202. package/dist/server/compat/next/rewrites.js +43 -0
  203. package/dist/server/compat/next/root-params.js +121 -0
  204. package/dist/server/compat/next/route-state.js +23 -0
  205. package/dist/server/compat/next/router.js +103 -0
  206. package/dist/server/compat/next/script.js +37 -0
  207. package/dist/server/compat/next/server-only.js +1 -0
  208. package/dist/server/compat/next/server.js +131 -0
  209. package/dist/server/compat/next/svgr.js +14 -0
  210. package/dist/server/compat/next/telemetry.js +10 -0
  211. package/dist/server/compat/next/usage.js +13 -0
  212. package/dist/server/compat/next/user-agent.js +12 -0
  213. package/dist/server/compat/next/web-vitals.js +35 -0
  214. package/dist/server/compat/react/action-state.js +11 -0
  215. package/dist/server/compat/react/client-lite.js +83 -0
  216. package/dist/server/compat/react/client.js +131 -0
  217. package/dist/server/compat/react/compiler-runtime.js +42 -0
  218. package/dist/server/compat/react/dom-client.js +106 -0
  219. package/dist/server/compat/react/dom-react-server.js +37 -0
  220. package/dist/server/compat/react/dom-server.js +40 -0
  221. package/dist/server/compat/react/dom.js +52 -0
  222. package/dist/server/compat/react/hooks-extra.js +12 -0
  223. package/dist/server/compat/react/internals.js +14 -0
  224. package/dist/server/compat/react/parity.js +11 -0
  225. package/dist/server/compat/react/preact.js +113 -0
  226. package/dist/server/compat/react/react-server.js +133 -0
  227. package/dist/server/compat/react/router-shim.js +8 -0
  228. package/dist/server/compat/react/server-component-use.js +14 -0
  229. package/dist/server/compat/react/server-inserted-html.js +14 -0
  230. package/dist/server/compat/react/server.js +194 -0
  231. package/dist/server/compat/react/style-values.js +10 -0
  232. package/dist/server/compat/react/use.js +10 -0
  233. package/dist/server/compat/react/view-transition.js +12 -0
  234. package/dist/server/generation.json +1 -0
  235. package/package.json +4 -1
  236. package/reference/compat.md +1 -1
  237. package/reference/config.md +2 -2
  238. package/reference/dev.md +1 -1
  239. package/reference/getting-started.md +1 -1
  240. package/reference/rendering.md +4 -4
  241. package/src/api/dynamic.tsx +86 -4
  242. package/src/api/server.ts +24 -6
  243. package/src/cli/adapters/vercel-warm.ts +327 -62
  244. package/src/cli/adapters/vercel.ts +164 -1054
  245. package/src/cli/analyze.ts +3 -2
  246. package/src/cli/build/cache.ts +6 -6
  247. package/src/cli/build.ts +78 -45
  248. package/src/cli/index.ts +21 -3
  249. package/src/cli/serve/immutable.ts +31 -0
  250. package/src/cli/serve/pipeline.ts +97 -93
  251. package/src/cli/serve/standalone-worker.ts +8 -0
  252. package/src/cli/serve/standalone.ts +703 -0
  253. package/src/cli/start.ts +31 -8
  254. package/src/client/build.ts +1 -1
  255. package/src/client/chunk-fold.ts +19 -6
  256. package/src/client/react-tier.ts +3 -2
  257. package/src/client/reference-stub.ts +23 -36
  258. package/src/compat/actions/client-plugin.ts +4 -2
  259. package/src/compat/actions/config.ts +6 -5
  260. package/src/compat/actions/extensions.ts +5 -5
  261. package/src/compat/aliases.ts +8 -1
  262. package/src/compat/bundler/config.ts +88 -0
  263. package/src/compat/bundler/optimize-package-imports.ts +86 -26
  264. package/src/compat/bundler/relay-transform.ts +15 -3
  265. package/src/compat/bundler/require-context.ts +2 -1
  266. package/src/compat/bundler/webpack-loaders.ts +3 -2
  267. package/src/compat/bundler/worker.ts +2 -2
  268. package/src/compat/cache-control.ts +16 -8
  269. package/src/compat/css/inline-css.ts +1 -1
  270. package/src/compat/css/modules.ts +18 -5
  271. package/src/compat/export/build-complete.ts +1 -1
  272. package/src/compat/export/emit.ts +4 -4
  273. package/src/compat/export/standalone.ts +30 -65
  274. package/src/compat/image-optimizer/optimize.ts +1 -1
  275. package/src/compat/lifecycle/error-funnel.ts +1 -1
  276. package/src/compat/lifecycle/instrumentation-client.ts +4 -3
  277. package/src/compat/lifecycle/instrumentation.ts +117 -24
  278. package/src/compat/lifecycle/testmode.ts +3 -2
  279. package/src/compat/metadata-artifacts.ts +1 -1
  280. package/src/compat/next/config-loader.ts +140 -13
  281. package/src/compat/next/dynamic.tsx +10 -23
  282. package/src/compat/next/font/cache.ts +2 -1
  283. package/src/compat/next/font/runtime.ts +23 -3
  284. package/src/compat/next/og.ts +3 -3
  285. package/src/compat/next/usage.ts +3 -2
  286. package/src/compat/pages/api.ts +83 -14
  287. package/src/compat/pages/router.ts +30 -14
  288. package/src/compat/register/bundler.ts +3 -1
  289. package/src/compat/register/image.ts +1 -1
  290. package/src/compat/register/middleware.ts +1 -1
  291. package/src/compat/register/otel.ts +5 -9
  292. package/src/compat/register/render.ts +3 -1
  293. package/src/compat/register/use-cache.ts +3 -1
  294. package/src/compat/release.ts +46 -0
  295. package/src/compat/segment/serve.ts +1 -1
  296. package/src/compat/typecheck/check.ts +6 -4
  297. package/src/compat/validation/validate.ts +10 -7
  298. package/src/config.ts +76 -9
  299. package/src/css/assets.ts +213 -0
  300. package/src/css/build.ts +190 -247
  301. package/src/dev/client-chunk-store.ts +2 -1
  302. package/src/dev/restart/client-key.ts +2 -1
  303. package/src/dev/restart/global-css.ts +2 -1
  304. package/src/dev/restart/route-bundle-key.ts +2 -1
  305. package/src/dev/restart/route-facts.ts +2 -1
  306. package/src/dev/server.ts +34 -14
  307. package/src/extensions.ts +4 -0
  308. package/src/out-paths.ts +10 -0
  309. package/src/render/hooks.ts +13 -0
  310. package/src/render/renderer.ts +53 -33
  311. package/src/request/context.ts +9 -0
  312. package/src/resolve/dynamic.ts +7 -1
  313. package/src/resolve/engine.ts +2 -12
  314. package/src/resolve/imports.ts +12 -52
  315. package/src/resolve/package-json.ts +56 -0
  316. package/src/resolve/scan-facts.ts +250 -1
  317. package/src/routing/href.ts +10 -3
  318. package/src/routing/match.ts +287 -0
  319. package/src/routing/metadata-files.ts +20 -6
  320. package/src/routing/proxy-config.ts +137 -0
  321. package/src/routing/proxy.ts +35 -134
  322. package/src/routing/routes.ts +22 -281
  323. package/src/runtime/fingerprint.ts +23 -8
  324. package/src/runtime/load.ts +170 -0
  325. package/src/runtime/loader.ts +218 -44
  326. package/src/runtime/module-cache.ts +15 -1
  327. package/src/runtime/modules.ts +405 -82
  328. package/src/runtime/prebundle.ts +101 -0
  329. package/src/runtime/production.ts +456 -0
  330. package/src/runtime/release-graph.ts +342 -0
  331. package/src/runtime/vendor-build.ts +750 -17
  332. package/src/runtime/vendor.ts +16 -3
  333. package/src/types.ts +4 -3
  334. package/src/utils/directive.ts +40 -0
  335. package/src/utils/fs.ts +95 -4
  336. package/src/utils/resource-query.ts +12 -0
  337. package/src/cli/serve/entry.ts +0 -45
@@ -1,13 +1,7 @@
1
1
  import { existsSync, readFileSync, statSync } from 'node:fs'
2
2
  import path from 'node:path'
3
- // Lazy: the oxc-parser native binding costs ~12.6 MB RSS; load it only when a parse happens.
4
- const parseSync: typeof import('oxc-parser').parseSync = (...args) =>
5
- // eslint-disable-next-line @typescript-eslint/no-require-imports
6
- loadNative(() => require('oxc-parser') as typeof import('oxc-parser')).parseSync(...args)
7
- import { loadNative } from '../utils/native-require'
8
- import { registerServerRuntime } from '../runtime/loader'
9
3
  import { pathToFileHref, type ResolvedConfig } from '../config'
10
- import { devServerModuleHref } from '../runtime/modules'
4
+ import { devServerModuleHref, registerServerRuntime } from '../runtime/load'
11
5
  import { runWithRequest } from '../request/context'
12
6
  import {
13
7
  copyMiddlewareHeaders,
@@ -140,14 +134,20 @@ export interface ProxyRunnerOptions {
140
134
  * load the build-time bundle that has aliases baked in.
141
135
  */
142
136
  compiledModuleHref?: string
137
+ /** A production release's proxy: its source and literal matcher config, as the build found them. */
138
+ released?: { file?: string; config?: ProxyConfig }
143
139
  }
144
140
 
145
141
  export function createProxyRunner(
146
142
  config: ResolvedConfig,
147
143
  runnerOptions: ProxyRunnerOptions = {},
148
144
  ): ProxyRunner {
149
- const file = findProxyFile(config)
150
- registerServerRuntime(config, file ? [file] : [])
145
+ const { released } = runnerOptions
146
+ const file = released ? released.file : findProxyFile(config)
147
+ const releasedMatchers = released?.config ? proxyMatchers(released.config) : undefined
148
+ // Awaited before the proxy module loads; a failure surfaces there.
149
+ const runtimeReady = registerServerRuntime(config, file ? [file] : [])
150
+ runtimeReady.catch(() => undefined)
151
151
  let moduleKey: string | undefined
152
152
  let modulePromise: Promise<ProxyModule> | undefined
153
153
  let matchers: CompiledMatcher[] | undefined
@@ -155,12 +155,14 @@ export function createProxyRunner(
155
155
  /** The module for `options`, started at most once per import version. */
156
156
  function proxyModule(options: ProxyRunOptions) {
157
157
  const key = proxyModuleKey(options)
158
+ const load = () =>
159
+ runtimeReady.then(() => importProxyModule(config, file!, options, runnerOptions))
158
160
  if (key !== moduleKey) {
159
161
  moduleKey = key
160
- modulePromise = importProxyModule(config, file!, options, runnerOptions)
162
+ modulePromise = load()
161
163
  matchers = undefined
162
164
  }
163
- return (modulePromise ??= importProxyModule(config, file!, options, runnerOptions))
165
+ return (modulePromise ??= load())
164
166
  }
165
167
 
166
168
  const runner: ProxyRunner = async (request, options = {}) => {
@@ -176,7 +178,7 @@ export function createProxyRunner(
176
178
  // Matcher first, straight off the source: a request no matcher selects must not wait on the
177
179
  // proxy's compile+import, which is otherwise the first serial link of a cold page.
178
180
  if (!matchers && proxyMatcherGateEnabled()) {
179
- const staticMatchers = staticProxyMatchers(file)
181
+ const staticMatchers = released ? releasedMatchers : await staticProxyMatchers(file)
180
182
  if (staticMatchers && !matchesProxy(staticMatchers, url, request)) return undefined
181
183
  }
182
184
 
@@ -397,7 +399,7 @@ export function proxyRoutePatterns(config: ProxyConfig | undefined) {
397
399
  const matcher = config?.matcher
398
400
  if (!matcher) return ['^/(.*)$']
399
401
  const matchers = Array.isArray(matcher) ? matcher : [matcher]
400
- return matchers.map(item => `^${matcherPattern(matcherSource(item))}$`)
402
+ return matchers.map(item => anchoredMatcherPattern(matcherSource(item)))
401
403
  }
402
404
 
403
405
  async function importProxyModule(
@@ -440,7 +442,7 @@ const staticMatcherCache = new Map<string, { key: string; matchers?: CompiledMat
440
442
  * middleware-manifest.json at build time); anything computed, any parse error, and any file without a
441
443
  * literal `config` yields undefined, and the caller falls back to the imported module's config.
442
444
  */
443
- function staticProxyMatchers(file: string): CompiledMatcher[] | undefined {
445
+ async function staticProxyMatchers(file: string): Promise<CompiledMatcher[] | undefined> {
444
446
  let key: string
445
447
  try {
446
448
  const stats = statSync(file)
@@ -452,6 +454,7 @@ function staticProxyMatchers(file: string): CompiledMatcher[] | undefined {
452
454
  if (cached?.key === key) return cached.matchers
453
455
  let matchers: CompiledMatcher[] | undefined
454
456
  try {
457
+ const { staticProxyConfig } = await import('./proxy-config')
455
458
  const config = staticProxyConfig(file, readFileSync(file, 'utf8'))
456
459
  if (config) matchers = proxyMatchers(config)
457
460
  } catch {
@@ -461,121 +464,6 @@ function staticProxyMatchers(file: string): CompiledMatcher[] | undefined {
461
464
  return matchers
462
465
  }
463
466
 
464
- function staticProxyConfig(file: string, source: string): ProxyConfig | undefined {
465
- const result = parseSync(file, source, { lang: parserLang(file) })
466
- // A recovered parse may have dropped the very declaration we are reading.
467
- if (result.errors.length > 0) return undefined
468
- for (const statement of result.program.body as StaticNode[]) {
469
- if (statement.type !== 'ExportNamedDeclaration') continue
470
- const declaration = statement.declaration
471
- if (declaration?.type !== 'VariableDeclaration') continue
472
- for (const declarator of declaration.declarations ?? []) {
473
- if (declarator.id?.type !== 'Identifier' || declarator.id.name !== 'config') continue
474
- const value = staticValue(declarator.init)
475
- return isProxyConfigShape(value) ? (value as ProxyConfig) : undefined
476
- }
477
- }
478
- return undefined
479
- }
480
-
481
- function parserLang(file: string) {
482
- const ext = path.extname(file)
483
- if (ext === '.tsx') return 'tsx'
484
- if (ext === '.jsx') return 'jsx'
485
- return ext === '.js' || ext === '.mjs' ? 'js' : 'ts'
486
- }
487
-
488
- /** Structural view of the oxc AST nodes this file reads — no full type import. */
489
- interface StaticNode {
490
- type: string
491
- name?: string
492
- value?: unknown
493
- computed?: boolean
494
- shorthand?: boolean
495
- key?: StaticNode
496
- init?: StaticNode
497
- id?: StaticNode
498
- declaration?: StaticNode
499
- declarations?: StaticNode[]
500
- expression?: StaticNode
501
- properties?: StaticNode[]
502
- elements?: (StaticNode | null)[]
503
- expressions?: StaticNode[]
504
- quasis?: { value?: { cooked?: string } }[]
505
- }
506
-
507
- /** Literal-only evaluation; `undefined` marks "not statically known". */
508
- function staticValue(node: StaticNode | undefined | null): unknown {
509
- if (!node) return undefined
510
- switch (node.type) {
511
- // `… as const` / `… satisfies ProxyConfig` wrap the literal, they never change it.
512
- case 'TSAsExpression':
513
- case 'TSSatisfiesExpression':
514
- case 'TSNonNullExpression':
515
- return staticValue(node.expression)
516
- case 'Literal':
517
- case 'StringLiteral':
518
- case 'NumericLiteral':
519
- case 'BooleanLiteral':
520
- // A regex literal reports `value: null` here; it is not a matcher shape.
521
- return node.value === null ? undefined : node.value
522
- case 'TemplateLiteral':
523
- return node.expressions?.length === 0
524
- ? (node.quasis?.[0]?.value?.cooked ?? undefined)
525
- : undefined
526
- case 'ArrayExpression': {
527
- const items: unknown[] = []
528
- for (const element of node.elements ?? []) {
529
- const value = staticValue(element)
530
- if (value === undefined) return undefined
531
- items.push(value)
532
- }
533
- return items
534
- }
535
- case 'ObjectExpression': {
536
- const object: Record<string, unknown> = {}
537
- for (const property of node.properties ?? []) {
538
- if (property.type !== 'Property' && property.type !== 'ObjectProperty') return undefined
539
- if (property.computed) return undefined
540
- const key =
541
- property.key?.type === 'Identifier' ? property.key.name : (property.key?.value as string)
542
- if (typeof key !== 'string') return undefined
543
- const value = staticValue(property.value as StaticNode | undefined)
544
- if (value === undefined) return undefined
545
- object[key] = value
546
- }
547
- return object
548
- }
549
- default:
550
- return undefined
551
- }
552
- }
553
-
554
- /** Only a config whose `matcher` is exactly the documented shape may gate. */
555
- function isProxyConfigShape(value: unknown): boolean {
556
- if (typeof value !== 'object' || value === null) return false
557
- const matcher = (value as { matcher?: unknown }).matcher
558
- if (matcher === undefined) return true
559
- const items = Array.isArray(matcher) ? matcher : [matcher]
560
- return items.every(item => {
561
- if (typeof item === 'string') return true
562
- if (typeof item !== 'object' || item === null) return false
563
- const { source, has, missing } = item as Record<string, unknown>
564
- if (typeof source !== 'string') return false
565
- return [has, missing].every(
566
- list => list === undefined || (Array.isArray(list) && list.every(isConditionShape)),
567
- )
568
- })
569
- }
570
-
571
- function isConditionShape(value: unknown): boolean {
572
- if (typeof value !== 'object' || value === null) return false
573
- const { type, key, value: expected } = value as Record<string, unknown>
574
- if (type !== 'header' && type !== 'query' && type !== 'cookie' && type !== 'host') return false
575
- if (key !== undefined && typeof key !== 'string') return false
576
- return expected === undefined || typeof expected === 'string'
577
- }
578
-
579
467
  function proxyMatchers(config: ProxyConfig | undefined): CompiledMatcher[] {
580
468
  const matcher = config?.matcher
581
469
  if (!matcher) return [{ regex: /.*/ }]
@@ -588,10 +476,11 @@ function proxyMatchers(config: ProxyConfig | undefined): CompiledMatcher[] {
588
476
  }
589
477
 
590
478
  function matchesProxy(matchers: CompiledMatcher[], url: URL, request: Request) {
591
- const decoded = decodePathSeparators(url.pathname)
479
+ const { pathname } = url
480
+ const decoded = pathname.includes('%') ? decodedPathnames(pathname) : []
592
481
  return matchers.some(
593
482
  matcher =>
594
- (matcher.regex.test(url.pathname) || matcher.regex.test(decoded)) &&
483
+ (matcher.regex.test(pathname) || decoded.some(item => matcher.regex.test(item))) &&
595
484
  matchesConditions(matcher, url, request),
596
485
  )
597
486
  }
@@ -641,8 +530,15 @@ function conditionValue(condition: ProxyRouteCondition, url: URL, request: Reque
641
530
  }
642
531
  }
643
532
 
644
- function decodePathSeparators(pathname: string) {
645
- return pathname.replace(/%2f/gi, '/')
533
+ // Next also matches `decodeURIComponent(pathname)`, so encoded paths can't skip the proxy.
534
+ function decodedPathnames(pathname: string) {
535
+ const decoded = [pathname.replace(/%2f/gi, '/')]
536
+ try {
537
+ decoded.push(decodeURIComponent(pathname))
538
+ } catch {
539
+ // Malformed escapes match on the raw forms only.
540
+ }
541
+ return decoded
646
542
  }
647
543
 
648
544
  function matcherSource(matcher: ProxyMatcher) {
@@ -650,7 +546,12 @@ function matcherSource(matcher: ProxyMatcher) {
650
546
  }
651
547
 
652
548
  function matcherRegex(matcher: string) {
653
- return new RegExp(`^${matcherPattern(matcher)}$`)
549
+ return new RegExp(anchoredMatcherPattern(matcher))
550
+ }
551
+
552
+ // Next's path-to-regexp (strict: false) also matches a trailing slash; routing strips any run of them.
553
+ function anchoredMatcherPattern(matcher: string) {
554
+ return `^(?:${matcherPattern(matcher)})/*$`
654
555
  }
655
556
 
656
557
  function matcherPattern(matcher: string) {
@@ -1,7 +1,6 @@
1
1
  import path from 'node:path'
2
2
  import { existsSync } from 'node:fs'
3
- import { listFiles, listFilesSync, readDirListing, toPosixPath, withDirCache } from '../utils/fs'
4
- import { safeDecode } from '../utils/decode'
3
+ import { listFiles, listFilesSync, toPosixPath, withDirCache } from '../utils/fs'
5
4
  import {
6
5
  dynamicCallsFromSource,
7
6
  pnextDynamicImportNames,
@@ -11,6 +10,13 @@ import { resolveImport, resolveModuleAlias, resolvePackageSpecifier } from '../r
11
10
  import { dynamicCallFacts, scanFacts, type DynamicCallFact } from '../resolve/scan-facts'
12
11
  import { readSourceSync } from '../resolve/source-text'
13
12
  import { globalCssSourcesForPaths } from '../css/build'
13
+ import {
14
+ conventionFileName,
15
+ findConventionFiles,
16
+ findLayouts,
17
+ pageExtensions,
18
+ serverActionsUnsupportedMessage,
19
+ } from './match'
14
20
  import {
15
21
  childrenDefaultFile,
16
22
  interceptionMarkerLevels,
@@ -24,11 +30,9 @@ import type {
24
30
  ClientEntryReason,
25
31
  MetadataRouteEntry,
26
32
  MetadataRouteKind,
27
- NavState,
28
33
  RouteInterception,
29
34
  RouteManifestEntry,
30
35
  RouteMode,
31
- RouteParamValue,
32
36
  RouteSegmentConfig,
33
37
  } from '../types'
34
38
  import {
@@ -48,20 +52,28 @@ import {
48
52
  sourceUsesRegisteredRequestApi,
49
53
  } from '../extensions'
50
54
 
55
+ export {
56
+ findConventionFiles,
57
+ findGlobalError,
58
+ findLayouts,
59
+ matchInterception,
60
+ matchRoute,
61
+ normalizePathname,
62
+ parseNavState,
63
+ selectRouteForRequest,
64
+ serverActionsUnsupportedMessage,
65
+ type InterceptionRouteMatch,
66
+ type RouteRenderSelection,
67
+ } from './match'
68
+
51
69
  // Single source of truth for the page/convention extension list. `.tsx`/`.ts` stay first so an all-.tsx app
52
70
  // resolves on the first candidate and produces the identical file as before; .js/.jsx/.mjs are additive
53
71
  // alternatives, and compat appends `mdx`/`md` via registerPageExtensions. EVERYTHING below derives from
54
72
  // pageExtensions()/routeHandlerExtensions() LAZILY, computed on first use and memoized, so the compat
55
73
  // registration that runs at bootstrap - before scanRoutes - is honored; a module-level const regex is baked
56
74
  // at import time, which is too early.
57
- const BASE_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mjs'] as const
58
75
  const BASE_ROUTE_HANDLER_EXTENSIONS = ['ts', 'tsx', 'js', 'mjs'] as const
59
76
 
60
- /** Base + compat-registered page extensions, de-duplicated in order. */
61
- function pageExtensions(): string[] {
62
- return [...new Set([...BASE_PAGE_EXTENSIONS, ...extraPageExtensions()])]
63
- }
64
-
65
77
  /** Route-handler extensions (route.*); compat page extensions do not apply. */
66
78
  function routeHandlerExtensions(): readonly string[] {
67
79
  return BASE_ROUTE_HANDLER_EXTENSIONS
@@ -79,8 +91,6 @@ function boundaryConventions(): string[] {
79
91
  return [...componentConventions, ...extraBoundaryConventionNames()]
80
92
  }
81
93
 
82
- const globalErrorBase = 'global-error'
83
-
84
94
  // The extension list is only known after compat bootstrap runs (which may
85
95
  // registerPageExtensions), and scanRoutes always runs after bootstrap. Each
86
96
  // pattern is computed on first use and memoized so the compat additions land
@@ -153,15 +163,6 @@ function extensionRegex(base: string, extensions: readonly string[]) {
153
163
  return new RegExp(`(^|/)${escapeRegex(base)}\\.(${extensions.join('|')})$`)
154
164
  }
155
165
 
156
- /** Nearest existing file named `<base>.<ext>` for the convention extensions. */
157
- function conventionFileName(dir: string, base: string) {
158
- const { files } = readDirListing(dir)
159
- for (const extension of pageExtensions()) {
160
- if (files.has(`${base}.${extension}`)) return path.join(dir, `${base}.${extension}`)
161
- }
162
- return undefined
163
- }
164
-
165
166
  interface RouteParts {
166
167
  route: string
167
168
  pattern: string
@@ -1107,10 +1108,6 @@ function appendDefaultDerivedRoutes(
1107
1108
  }
1108
1109
  }
1109
1110
 
1110
- export function findLayouts(appPath: string, routeFilePath: string) {
1111
- return findConventionFiles(appPath, routeFilePath, 'layout')
1112
- }
1113
-
1114
1111
  /**
1115
1112
  * CSS imported by the app's ROOT `not-found.*` (and its module graph), in
1116
1113
  * source order, excluding the root-layout global CSS (which is served
@@ -1147,236 +1144,6 @@ export async function collectFileCss(
1147
1144
  return collectCssImports(context, existing)
1148
1145
  }
1149
1146
 
1150
- /**
1151
- * Walk from the route's directory up to `appPath`, returning one convention file per segment ordered
1152
- * root-to-leaf. `name` may be a bare base (`'layout'`) or carry an extension - a known extension is stripped
1153
- * so the full extension list is tried. Each segment resolves to the first existing `<base>.<ext>`; when none
1154
- * exists the `.tsx` candidate is returned so existing callers that filter by `existsSync` keep
1155
- * byte-identical behavior.
1156
- */
1157
- export function findConventionFiles(appPath: string, routeFilePath: string, name: string) {
1158
- const base = conventionBase(name)
1159
- const files: string[] = []
1160
- let dir = path.dirname(routeFilePath)
1161
-
1162
- while (dir.startsWith(appPath)) {
1163
- files.push(conventionFileName(dir, base) ?? path.join(dir, `${base}.tsx`))
1164
- if (dir === appPath) break
1165
- dir = path.dirname(dir)
1166
- }
1167
-
1168
- return files.reverse()
1169
- }
1170
-
1171
- function conventionBase(name: string) {
1172
- const match = new RegExp(`\\.(${pageExtensions().join('|')})$`).exec(name)
1173
- return match ? name.slice(0, -match[0].length) : name
1174
- }
1175
-
1176
- /** App-root `global-error.*` file, if present (BuildManifest.globalErrorFile). */
1177
- export function findGlobalError(appPath: string) {
1178
- return conventionFileName(appPath, globalErrorBase)
1179
- }
1180
-
1181
- export function matchRoute(routes: RouteManifestEntry[], pathname: string) {
1182
- // Trailing-slash tolerance: '/route/' matches the same entry as '/route'
1183
- // (Next redirects these away; static file serving already accepts both).
1184
- const normalized = normalizePathname(pathname)
1185
- for (const route of routes) {
1186
- // Interception entries never match a plain (hard) request; they apply
1187
- // only through matchInterception on soft navigations.
1188
- if (route.interception) continue
1189
- const match = routeRegex(route).exec(normalized)
1190
- if (!match) continue
1191
- return { route, params: routeMatchParams(route, match) }
1192
- }
1193
- return null
1194
- }
1195
-
1196
- export function normalizePathname(pathname: string) {
1197
- return pathname.length > 1 ? pathname.replace(/\/+$/, '') || '/' : pathname
1198
- }
1199
-
1200
- function routeMatchParams(route: RouteManifestEntry, match: RegExpExecArray) {
1201
- const params: Record<string, RouteParamValue> = {}
1202
- route.params.forEach((name, index) => {
1203
- const decoded = safeDecode(match[index + 1] ?? '')
1204
- // A runtime request whose segment IS the dynamic placeholder for its own
1205
- // param (`[slug]`, whether sent raw or as `%5Bslug%5D`) is Next's "params
1206
- // placeholder": it must surface the ENCODED placeholder (`%5Bslug%5D`), not
1207
- // a decoded `[slug]`. A decoded `[slug]` reads as a fallback param and would
1208
- // trigger a fallback-shell render (failing without a parent Suspense
1209
- // boundary); Next keeps it URL-encoded to render at runtime instead.
1210
- // Fallback-shell generation injects its placeholder params through a
1211
- // separate build path, so this only affects live requests.
1212
- params[name] = decoded === `[${name}]` ? encodeURIComponent(decoded) : decoded
1213
- })
1214
- if (route.catchAll) {
1215
- params[route.catchAll] = (match[route.params.length + 1] ?? '')
1216
- .split('/')
1217
- .filter(Boolean)
1218
- .map(safeDecode)
1219
- }
1220
- return params
1221
- }
1222
-
1223
- export interface InterceptionRouteMatch {
1224
- route: RouteManifestEntry
1225
- params: Record<string, RouteParamValue>
1226
- }
1227
-
1228
- /**
1229
- * Match a soft navigation against interception entries: the entry's target
1230
- * pattern must match the destination and the navigation must originate at or
1231
- * below the interceptor's base (the children path the current document
1232
- * rendered from). The deepest base wins. A no-op when destination equals the
1233
- * origin (a refresh re-renders whatever produced the current document).
1234
- */
1235
- export function matchInterception(
1236
- routes: RouteManifestEntry[],
1237
- pathname: string,
1238
- fromPath: string | undefined,
1239
- ): InterceptionRouteMatch | null {
1240
- if (!fromPath) return null
1241
- const target = normalizePathname(pathname)
1242
- const from = normalizePathname(fromPath)
1243
- if (target === from) return null
1244
- let best: InterceptionRouteMatch | null = null
1245
- let bestDepth = -1
1246
- for (const route of routes) {
1247
- const interception = route.interception
1248
- if (!interception) continue
1249
- const match = routeRegex(route).exec(target)
1250
- if (!match) continue
1251
- const basePattern = interception.basePattern ? `/${interception.basePattern}` : ''
1252
- if (!new RegExp(`^${basePattern}(?:/.*)?$`).test(from)) continue
1253
- const depth = interception.base.split('/').filter(Boolean).length
1254
- if (depth <= bestDepth) continue
1255
- bestDepth = depth
1256
- best = { route, params: routeMatchParams(route, match) }
1257
- }
1258
- return best
1259
- }
1260
-
1261
- export interface RouteRenderSelection {
1262
- route: RouteManifestEntry
1263
- params: Record<string, RouteParamValue>
1264
- /** Pathname the children tree renders from (normalized). */
1265
- childrenPath: string
1266
- /** Internal pathname after rewrites, used to resolve interception slots. */
1267
- targetPath: string
1268
- }
1269
-
1270
- /**
1271
- * Pick the entry a page request renders. Hard requests match plainly. Soft navigations first consult
1272
- * interception entries; a slot interception (and a synthetic slot URL reached with known origin) renders the
1273
- * ORIGIN's entry as host - the current page stays while the slot content changes - with the children tree
1274
- * anchored at `childrenPath`.
1275
- */
1276
- export function selectRouteForRequest(
1277
- routes: RouteManifestEntry[],
1278
- pathname: string,
1279
- nav?: import('../types').NavState,
1280
- ): RouteRenderSelection | null {
1281
- const target = normalizePathname(pathname)
1282
- if (nav) {
1283
- const intercepted = matchInterception(routes, target, nav.children)
1284
- if (intercepted) {
1285
- if (!intercepted.route.interception?.slotDir) {
1286
- return {
1287
- route: intercepted.route,
1288
- params: intercepted.params,
1289
- childrenPath: target,
1290
- targetPath: target,
1291
- }
1292
- }
1293
- const host = hostMatch(routes, nav.children)
1294
- if (host) return { ...host, targetPath: target }
1295
- }
1296
- }
1297
- const matched = matchRoute(routes, target)
1298
- if (!matched) return null
1299
- if (nav && matched.route.synthetic && nav.children) {
1300
- const primary = matchRoute(
1301
- routes.filter(route => !route.synthetic),
1302
- target,
1303
- )
1304
- const syntheticOwner = matched.route.syntheticSlotDir
1305
- ? path.dirname(matched.route.syntheticSlotDir)
1306
- : undefined
1307
- const primaryFromOwner =
1308
- primary && syntheticOwner ? path.relative(syntheticOwner, primary.route.file) : undefined
1309
- if (
1310
- primary &&
1311
- primaryFromOwner !== undefined &&
1312
- primaryFromOwner !== '..' &&
1313
- !primaryFromOwner.startsWith(`..${path.sep}`)
1314
- ) {
1315
- return {
1316
- route: primary.route,
1317
- params: primary.params,
1318
- childrenPath: target,
1319
- targetPath: target,
1320
- }
1321
- }
1322
- const host = hostMatch(routes, nav.children)
1323
- if (
1324
- host &&
1325
- host.route !== matched.route &&
1326
- matched.route.syntheticSlotDir &&
1327
- host.route.slotDirs?.includes(matched.route.syntheticSlotDir)
1328
- ) {
1329
- return { ...host, targetPath: target }
1330
- }
1331
- }
1332
- return {
1333
- route: matched.route,
1334
- params: matched.params,
1335
- childrenPath: target,
1336
- targetPath: target,
1337
- }
1338
- }
1339
-
1340
- /**
1341
- * Navigation headers from the client soft-nav runtime: `x-pnext-soft-nav`
1342
- * marks the fetch and `x-pnext-nav-state` carries the current document's
1343
- * parallel-route state (URI-encoded JSON).
1344
- */
1345
- export function parseNavState(request: Request): NavState | undefined {
1346
- if (!request.headers.get('x-pnext-soft-nav')) return undefined
1347
- const raw = request.headers.get('x-pnext-nav-state')
1348
- if (!raw) return {}
1349
- try {
1350
- const parsed = JSON.parse(decodeURIComponent(raw)) as unknown
1351
- if (parsed && typeof parsed === 'object') return parsed
1352
- } catch {
1353
- // Malformed state degrades to a plain soft navigation.
1354
- }
1355
- return {}
1356
- }
1357
-
1358
- function hostMatch(
1359
- routes: RouteManifestEntry[],
1360
- fromPath: string | undefined,
1361
- ): RouteRenderSelection | null {
1362
- if (!fromPath) return null
1363
- const from = normalizePathname(fromPath)
1364
- const host = matchRoute(routes, from)
1365
- if (host?.route.kind !== 'page') return null
1366
- return { route: host.route, params: host.params, childrenPath: from, targetPath: from }
1367
- }
1368
-
1369
- const routeRegexCache = new WeakMap<RouteManifestEntry, RegExp>()
1370
-
1371
- function routeRegex(route: RouteManifestEntry) {
1372
- let regex = routeRegexCache.get(route)
1373
- if (!regex) {
1374
- regex = new RegExp(`^${route.pattern}$`)
1375
- routeRegexCache.set(route, regex)
1376
- }
1377
- return regex
1378
- }
1379
-
1380
1147
  interface RouteSegmentToken {
1381
1148
  route: string
1382
1149
  /** Regex piece; absent for the optional catch-all (assembled specially). */
@@ -1784,32 +1551,6 @@ function serverActionFileFact(context: ScanContext, sourceFiles: string[]) {
1784
1551
  * Read once, act once: the file, what pnext will not do, and both remedies. No
1785
1552
  * stack - the CLI prints `error.message` alone (cli/index.ts).
1786
1553
  */
1787
- export function serverActionsUnsupportedMessage(offender: {
1788
- file: string
1789
- route?: string
1790
- root?: string
1791
- }) {
1792
- const file = offender.root
1793
- ? toPosixPath(path.relative(offender.root, offender.file))
1794
- : offender.file
1795
- const reach = offender.route ? `, reachable from the route ${offender.route}` : ''
1796
- return [
1797
- `Server actions need next compat, and this app is pure core.`,
1798
- ``,
1799
- ` ${file} has a 'use server' directive${reach}.`,
1800
- ``,
1801
- `pnext will not build this app: core has no server-action registry and no`,
1802
- `dispatch endpoint, so the action could only render as a form that submits`,
1803
- `nowhere.`,
1804
- ``,
1805
- `Fix it one of two ways:`,
1806
- ` 1. Enable compat in pnext.config.ts:`,
1807
- ` export default { compat: { next: true } }`,
1808
- ` 2. Or delete the 'use server' directive from ${file}, if those exports are`,
1809
- ` only ever called on the server (in core the directive does nothing).`,
1810
- ].join('\n')
1811
- }
1812
-
1813
1554
  /**
1814
1555
  * Fail the build / the dev request rather than render a form whose action goes
1815
1556
  * nowhere. Only page routes carry the fact (see serverActionFileFact), so a
@@ -7,6 +7,12 @@ import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, type Dir
7
7
  import path from 'node:path'
8
8
  import { fileURLToPath } from 'node:url'
9
9
 
10
+ declare const PNEXT_PREBUNDLE: { fingerprint: string } | undefined
11
+
12
+ // Read off the prebundle's define, not imported: compat compiles this module into app graphs.
13
+ const prebundleFingerprint =
14
+ typeof PNEXT_PREBUNDLE === 'object' ? PNEXT_PREBUNDLE.fingerprint : undefined
15
+
10
16
  /**
11
17
  * Exactly what the package publishes and the vercel adapter ships: source extensions only, never
12
18
  * declarations (both drop them) and never machine-local files like `.DS_Store`. The same tree must
@@ -101,6 +107,9 @@ function writeRecord(dir: string, record: Record) {
101
107
  }
102
108
 
103
109
  let fingerprint: string | undefined
110
+ let fingerprintStats: string | undefined
111
+ // Set once this process has hashed the tree's content: only that answer may serve another app.
112
+ let scanned = false
104
113
  const recorded = new Set<string>()
105
114
 
106
115
  /**
@@ -109,24 +118,30 @@ const recorded = new Set<string>()
109
118
  *
110
119
  * `recordDir` (a cache root) buys the O(1) stat path across restarts; without it the tree is read.
111
120
  * `PNEXT_FRAMEWORK_FINGERPRINT` pins the value outright — the bisect seam, and how a deployment can
112
- * replay its build's generation without walking the tree at all.
121
+ * replay its build's generation without walking the tree at all. The prebundle pins its publish-time value.
113
122
  */
114
123
  export function frameworkFingerprint(recordDir?: string) {
115
124
  // eslint-disable-next-line turbo/no-undeclared-env-vars
116
- const pinned = process.env.PNEXT_FRAMEWORK_FINGERPRINT
125
+ const pinned = process.env.PNEXT_FRAMEWORK_FINGERPRINT ?? prebundleFingerprint
117
126
  if (pinned) return pinned
118
127
  // Memoized, but a second app in the same process still gets its own record - without one its next
119
128
  // boot pays the full read for a fingerprint this process already knows.
120
129
  if (fingerprint !== undefined && (!recordDir || recorded.has(recordDir))) return fingerprint
121
- const files = sourceFiles(frameworkSrc)
122
- const stats = statSignature(files, frameworkSrc)
123
130
  const previous = recordDir ? readRecord(recordDir) : undefined
124
- fingerprint =
125
- previous?.stats === stats ? previous.fingerprint : contentFingerprint(files, frameworkSrc)
131
+ // One content scan per process; after it a later record dir only needs its record written. A
132
+ // stat-only answer from an app's record serves that app, never another app's fresh cache.
133
+ if (!scanned || fingerprint === undefined || fingerprintStats === undefined) {
134
+ const files = sourceFiles(frameworkSrc)
135
+ fingerprintStats = statSignature(files, frameworkSrc)
136
+ const recordedFingerprint =
137
+ previous?.stats === fingerprintStats ? previous.fingerprint : undefined
138
+ scanned = recordedFingerprint === undefined
139
+ fingerprint = recordedFingerprint ?? contentFingerprint(files, frameworkSrc)
140
+ }
126
141
  if (recordDir) {
127
142
  recorded.add(recordDir)
128
- if (previous?.stats !== stats)
129
- writeRecord(recordDir, { version: RECORD_VERSION, stats, fingerprint })
143
+ if (previous?.stats !== fingerprintStats)
144
+ writeRecord(recordDir, { version: RECORD_VERSION, stats: fingerprintStats, fingerprint })
130
145
  }
131
146
  return fingerprint
132
147
  }