@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
@@ -18,9 +18,8 @@ import { currentRequest, getWorkUnit } from '../../request/context'
18
18
  // readonly property." `require()` returns the real, mutable CJS exports
19
19
  // object that node/http/https resolve `.get` calls through at runtime, so
20
20
  // patch that instead of the namespace binding.
21
+ // Required on install, not at import: loading node:http/https costs every build ~10 ms.
21
22
  const requireNode = createRequire(import.meta.url)
22
- const httpModule = requireNode('node:http') as typeof http
23
- const httpsModule = requireNode('node:https') as typeof https
24
23
 
25
24
  interface TestProxyInfo {
26
25
  proxyPort: number
@@ -284,6 +283,8 @@ export function installTestProxyFetch(): void {
284
283
  ;(testFetch as typeof fetch).preconnect = originalFetch.preconnect?.bind(originalFetch)
285
284
  globalThis.fetch = testFetch as typeof fetch
286
285
 
286
+ const httpModule = requireNode('node:http') as typeof http
287
+ const httpsModule = requireNode('node:https') as typeof https
287
288
  const originalHttpGet = httpModule.get
288
289
  const originalHttpsGet = httpsModule.get
289
290
  httpModule.get = patchableGet(originalHttpGet, originalFetch)
@@ -329,7 +329,7 @@ async function writeStaticMetadataArtifacts(
329
329
  metadata: StaticFileMetadata | undefined,
330
330
  ): Promise<void> {
331
331
  if (!metadata) return
332
- const source = path.join(outPath, 'public', relative)
332
+ const source = path.join(outPath, 'static', relative)
333
333
  const outputBase = path.join(root, 'server', 'app', toPosixPath(relative))
334
334
  await mkdir(path.dirname(outputBase), { recursive: true })
335
335
  const routeMeta = JSON.stringify(toRouteMeta(metadata))
@@ -13,7 +13,15 @@
13
13
  // export / top-level await / dynamic import() and CJS+ESM authoring styles.
14
14
  // ---------------------------------------------------------------------------
15
15
 
16
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ readdirSync,
21
+ renameSync,
22
+ rmSync,
23
+ writeFileSync,
24
+ } from 'node:fs'
17
25
  import { createRequire } from 'node:module'
18
26
  import path from 'node:path'
19
27
  import { pathToFileURL } from 'node:url'
@@ -43,6 +51,12 @@ const CONFIG_BASENAMES = [
43
51
  ]
44
52
 
45
53
  let store: NextConfigObject = {}
54
+ let storeReleased = false
55
+
56
+ /** Whether the stored config came from a production release, which no consumer may re-read from source. */
57
+ export function nextConfigReleased(): boolean {
58
+ return storeReleased
59
+ }
46
60
 
47
61
  /** The full loaded next.config object; empty until loadNextConfig runs. */
48
62
  export function getNextConfig(): NextConfigObject {
@@ -188,19 +202,104 @@ async function importConfigModule(
188
202
  return importConfigBundle(outfile, cjs)
189
203
  }
190
204
 
205
+ /**
206
+ * Compile the app's next.config into `outDir`, plus the cache handlers it names, so a production server
207
+ * imports neither from source. Names carry a content hash: a rebuilt release never reuses the previous
208
+ * generation's module instance. Paths are relative to the bundle, so the release can move.
209
+ */
210
+ export async function emitNextConfigBundle(root: string, outDir: string) {
211
+ const configPath = findConfigPath(root)
212
+ if (!configPath) return undefined
213
+ const cjs = cjsConfigFormat(configPath, root)
214
+ const [file, ...handlerFiles] = await Promise.all([
215
+ hashedOutput(outDir, 'next.config', cjs ? '.cjs' : '.mjs', outfile =>
216
+ bundleConfig({ configPath, root, outDir, outfile, cjs, relocatable: true }),
217
+ ),
218
+ ...cacheHandlerFiles(store).map(([name, source]) =>
219
+ hashedOutput(outDir, name, '.cjs', outfile =>
220
+ build({
221
+ entryPoints: [source],
222
+ outfile,
223
+ bundle: true,
224
+ platform: 'node',
225
+ format: 'cjs',
226
+ target: 'esnext',
227
+ packages: 'external',
228
+ logLevel: 'silent',
229
+ }),
230
+ ).then(output => [name, output] as const),
231
+ ),
232
+ ])
233
+ return { file, handlers: Object.fromEntries(handlerFiles as [string, string][]) }
234
+ }
235
+
236
+ /** Write through `emit` to a temporary name, then publish as `<name>.<content hash><ext>`. */
237
+ async function hashedOutput(
238
+ outDir: string,
239
+ name: string,
240
+ ext: string,
241
+ emit: (outfile: string) => Promise<unknown>,
242
+ ) {
243
+ const temporary = path.join(outDir, `${name}.${process.pid}.tmp${ext}`)
244
+ await emit(temporary)
245
+ const hash = bun.hash(readFileSync(temporary)).toString(36)
246
+ const outfile = path.join(outDir, `${name}.${hash}${ext}`)
247
+ renameSync(temporary, outfile)
248
+ return outfile
249
+ }
250
+
251
+ /** The configured cache handler sources, by the name their release bundle takes. */
252
+ function cacheHandlerFiles(config: NextConfigObject): [string, string][] {
253
+ const handlers = config.cacheHandlers as Record<string, unknown> | undefined
254
+ return [
255
+ ['cache-handler', config.cacheHandler],
256
+ [
257
+ 'cache-handler-default',
258
+ handlers && typeof handlers === 'object' ? handlers.default : undefined,
259
+ ],
260
+ ].filter(
261
+ (entry): entry is [string, string] =>
262
+ typeof entry[1] === 'string' && path.isAbsolute(entry[1]) && existsSync(entry[1]),
263
+ )
264
+ }
265
+
266
+ /** Point a released config's cache handlers at the bundles its build emitted. */
267
+ function releasedCacheHandlers(
268
+ config: NextConfigObject,
269
+ handlers: Record<string, string>,
270
+ ): NextConfigObject {
271
+ const handler = handlers['cache-handler']
272
+ const fallback = handlers['cache-handler-default']
273
+ return {
274
+ ...config,
275
+ ...(handler ? { cacheHandler: handler } : {}),
276
+ ...(fallback
277
+ ? { cacheHandlers: { ...(config.cacheHandlers as object), default: fallback } }
278
+ : {}),
279
+ }
280
+ }
281
+
282
+ const toPosix = (file: string) => file.split(path.sep).join('/')
283
+
191
284
  function bundleConfig({
192
285
  configPath,
193
286
  root,
194
287
  outDir,
195
288
  outfile,
196
289
  cjs,
290
+ relocatable = false,
197
291
  }: {
198
292
  configPath: string
199
293
  root: string
200
294
  outDir: string
201
295
  outfile: string
202
296
  cjs: boolean
297
+ /** A release bundle: locate the config from the bundle itself, never from the build machine's paths. */
298
+ relocatable?: boolean
203
299
  }) {
300
+ const configUrl = relocatable
301
+ ? `new URL(${JSON.stringify(toPosix(path.relative(outDir, configPath)))}, ${cjs ? '__pnextNodeRequire("node:url").pathToFileURL(__filename)' : 'import.meta.url'}).href`
302
+ : JSON.stringify(pathToFileURL(configPath).href)
204
303
  return build({
205
304
  entryPoints: [configPath],
206
305
  outfile,
@@ -220,12 +319,20 @@ function bundleConfig({
220
319
  ? {
221
320
  js:
222
321
  'var __pnextNodeRequire = module.require.bind(module);\n' +
223
- `var require = __pnextNodeRequire("node:module").createRequire(${JSON.stringify(pathToFileURL(configPath).href)});`,
322
+ `var __pnextConfigUrl = ${configUrl};\n` +
323
+ 'var __pnextConfigFile = __pnextNodeRequire("node:url").fileURLToPath(__pnextConfigUrl);\n' +
324
+ 'var __pnextConfigDir = __pnextNodeRequire("node:path").dirname(__pnextConfigFile);\n' +
325
+ 'var require = __pnextNodeRequire("node:module").createRequire(__pnextConfigUrl);',
224
326
  }
225
327
  : {
226
328
  js:
227
329
  'import { createRequire as __pnextCreateRequire } from "node:module";\n' +
228
- `const require = __pnextCreateRequire(${JSON.stringify(pathToFileURL(configPath).href)});`,
330
+ 'import { fileURLToPath as __pnextFileURLToPath } from "node:url";\n' +
331
+ 'import { dirname as __pnextDirname } from "node:path";\n' +
332
+ `const __pnextConfigUrl = ${configUrl};\n` +
333
+ 'const __pnextConfigFile = __pnextFileURLToPath(__pnextConfigUrl);\n' +
334
+ 'const __pnextConfigDir = __pnextDirname(__pnextConfigFile);\n' +
335
+ 'const require = __pnextCreateRequire(__pnextConfigUrl);',
229
336
  },
230
337
  // Relative/absolute imports (incl. .ts/.cts/.mts/.cjs/.mjs and tsconfig
231
338
  // `paths` aliases) are bundled in. esbuild reads `paths`/`extends` and
@@ -235,9 +342,9 @@ function bundleConfig({
235
342
  // import.meta.url in ESM configs (node-api-esm) must point at the config's
236
343
  // original location, not the emitted file under .pnext/config.
237
344
  define: {
238
- __dirname: JSON.stringify(path.dirname(configPath)),
239
- __filename: JSON.stringify(configPath),
240
- 'import.meta.url': JSON.stringify(pathToFileURL(configPath).href),
345
+ __dirname: '__pnextConfigDir',
346
+ __filename: '__pnextConfigFile',
347
+ 'import.meta.url': '__pnextConfigUrl',
241
348
  },
242
349
  logLevel: 'silent',
243
350
  sourcemap: false,
@@ -381,6 +488,9 @@ function mapOverrides(config: NextConfigObject): NextConfigCoreOverrides {
381
488
  config.output !== 'export'
382
489
  ) {
383
490
  overrides.outDir = config.distDir
491
+ } else if (config.output === 'standalone') {
492
+ // Next's `.next/standalone`: the folder its standalone contract names.
493
+ overrides.outDir = '.next'
384
494
  }
385
495
  return overrides
386
496
  }
@@ -647,20 +757,36 @@ function validateDevIndicatorPosition(config: NextConfigObject): void {
647
757
  */
648
758
  export async function loadNextConfig(
649
759
  root: string,
650
- options: { dev?: boolean; serve?: boolean; warnings?: boolean } = {},
760
+ options: {
761
+ dev?: boolean
762
+ serve?: boolean
763
+ warnings?: boolean
764
+ bundle?: string | null
765
+ handlers?: Record<string, string>
766
+ } = {},
651
767
  ): Promise<LoadNextConfigResult> {
652
- const configPath = findConfigPath(root)
768
+ // A production server names its compiled config, or null when the app had none.
769
+ storeReleased = options.bundle !== undefined
770
+ const configPath = options.bundle === undefined ? findConfigPath(root) : options.bundle
653
771
  if (!configPath) {
654
772
  setNextConfig({})
655
773
  emitSessionStartedTelemetry({})
656
774
  return { config: {}, overrides: {} }
657
775
  }
776
+ if (options.bundle && !existsSync(options.bundle)) {
777
+ // The release names this file; serving defaults in its place would drop headers, redirects and more.
778
+ throw new Error(
779
+ `pnext: the build's compiled next.config (${options.bundle}) is missing. Run 'pnext build' again.`,
780
+ )
781
+ }
658
782
  let exported: NextConfigExport
659
783
  try {
660
- exported = await importConfigModule(configPath, root, {
661
- dev: Boolean(options.dev),
662
- serve: Boolean(options.serve),
663
- })
784
+ exported = options.bundle
785
+ ? await importConfigBundle(options.bundle, options.bundle.endsWith('.cjs'))
786
+ : await importConfigModule(configPath, root, {
787
+ dev: Boolean(options.dev),
788
+ serve: Boolean(options.serve),
789
+ })
664
790
  } catch (error) {
665
791
  // A next.config that pulls in an optional wrapper dependency not installed
666
792
  // in this app (e.g. @next/mdx when deps are declared but not installed)
@@ -677,10 +803,11 @@ export async function loadNextConfig(
677
803
  }
678
804
  throw error
679
805
  }
680
- const config = normalizeCacheHandlerPath(
806
+ const resolved = normalizeCacheHandlerPath(
681
807
  await resolveConfigExport(exported, Boolean(options.dev)),
682
808
  root,
683
809
  )
810
+ const config = options.bundle ? releasedCacheHandlers(resolved, options.handlers ?? {}) : resolved
684
811
  // Internal build workers need the resolved config but must not surface a
685
812
  // second copy of CLI warnings already emitted by the owning build process.
686
813
  validateConfig(config, configPath, options.warnings !== false)
@@ -1,6 +1,5 @@
1
1
  /** @jsxImportSource preact */
2
2
  import { h, type ComponentType } from 'preact'
3
- import { lazy, Suspense } from 'preact/compat'
4
3
  import {
5
4
  dynamic as pnextDynamic,
6
5
  dynamicReferenceSymbol,
@@ -19,25 +18,17 @@ export default function dynamic<Props extends object = object>(
19
18
  // Injected by the server compile; see rewriteDynamicCallTargets.
20
19
  target?: DynamicTarget,
21
20
  ) {
22
- if (typeof loader === 'string') return pnextDynamic(loader, options, target)
23
-
24
21
  const coreDynamic = pnextDynamic(loader, options, target) as DynamicWithReference<Props>
25
- const Lazy = lazy(() => loader().then(module => ({ default: componentFromModule(module) })))
26
- const DynamicComponent = ((props: Props) => {
27
- const Loading = options.loading
28
- // ssr:false renders nothing on the server, so the client mount is a *hydration* whose only child
29
- // suspends. preact/compat nulls a hydrating vnode's type when it unmounts, and Suspense reuses the
30
- // same props.children vnode on its resolve re-render - the poisoned element then renders as text.
31
- // The hooks-based core component loads without suspending, so that boundary is never entered.
32
- if (options.ssr === false) {
33
- return h(
34
- 'pnext-dynamic',
35
- { style: { display: 'contents' } },
36
- !process.browser && typeof window === 'undefined' ? null : h(coreDynamic, props),
37
- )
38
- }
39
- return h(Suspense, { fallback: Loading ? h(Loading, props) : null }, h(Lazy, props))
40
- }) as DynamicWithReference<Props>
22
+ // Core's component server-renders and hydrates like Next, waiting for a chunk still loading.
23
+ if (typeof loader === 'string' || options.ssr !== false) return coreDynamic
24
+
25
+ // ssr:false renders nothing on the server; the client renders `loading`, then the component.
26
+ const DynamicComponent = ((props: Props) =>
27
+ h(
28
+ 'pnext-dynamic',
29
+ { style: { display: 'contents' } },
30
+ !process.browser && typeof window === 'undefined' ? null : h(coreDynamic, props),
31
+ )) as DynamicWithReference<Props>
41
32
 
42
33
  DynamicComponent[dynamicReferenceSymbol] = coreDynamic[dynamicReferenceSymbol]
43
34
  return DynamicComponent
@@ -48,7 +39,3 @@ export { dynamic }
48
39
  type DynamicWithReference<Props extends object> = ComponentType<Props> & {
49
40
  [dynamicReferenceSymbol]?: DynamicReference<Props>
50
41
  }
51
-
52
- function componentFromModule<Props>(module: DynamicModule<Props>) {
53
- return typeof module === 'function' ? module : module.default
54
- }
@@ -15,6 +15,7 @@ import { restartCacheEnabled } from '../../../dev/restart/enabled'
15
15
  import { frameworkFingerprint } from '../../../runtime/fingerprint'
16
16
  import { writeFileAtomic } from '../../../utils/fs'
17
17
  import type { MetadataLink } from '../../../types'
18
+ import { outCachePath } from '../../../out-paths'
18
19
 
19
20
  export interface PersistableFont {
20
21
  css: string
@@ -54,7 +55,7 @@ interface FontIndex {
54
55
  const indexes = new Map<string, FontIndex>()
55
56
 
56
57
  function fontIndex(config: ResolvedConfig): FontIndex {
57
- const file = path.join(config.outPath, 'cache', 'fonts', 'index.json')
58
+ const file = path.join(outCachePath(config.outPath), 'fonts', 'index.json')
58
59
  const existing = indexes.get(file)
59
60
  if (existing) return existing
60
61
  const created: FontIndex = { file, fonts: readIndex(file), dirty: false }
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'
8
8
  import type { ResolvedConfig } from '../../../config'
9
9
  import { onExtensionHostReset, registerResponseFinalizers } from '../../../extensions'
10
10
  import { getWorkUnit } from '../../../request/context'
11
+ import { servedRelease } from '../../../runtime/production'
11
12
  import type { MetadataLink } from '../../../types'
12
13
  import { persistFont, persistedFont } from './cache'
13
14
  import { escapeRegex } from '../../../utils/code'
@@ -383,7 +384,23 @@ function registerFont(definition: FontDefinition) {
383
384
  fontStorage.getStore()?.fonts.set(definition.key, definition)
384
385
  }
385
386
 
386
- async function resolveFont(definition: FontDefinition, context: FontResolveContext) {
387
+ /** Every declared font, resolved for a release (its files emitted under the out dir). */
388
+ export async function releaseFonts(config: ResolvedConfig): Promise<Record<string, ResolvedFont>> {
389
+ const fonts: Record<string, ResolvedFont> = {}
390
+ for (const font of declaredFonts.values()) {
391
+ const { sources: _sources, ...resolved } = await resolveFont(font, { config })
392
+ fonts[font.key] = resolved
393
+ }
394
+ return fonts
395
+ }
396
+
397
+ async function resolveFont(
398
+ definition: FontDefinition,
399
+ context: FontResolveContext,
400
+ ): Promise<ResolvedFont> {
401
+ // A production release serves the build's resolution: font sources are not read at request time.
402
+ const released = servedRelease()?.fonts?.[definition.key]
403
+ if (released) return released
387
404
  const key = `${context.config.outPath}:${context.dev ? 'dev' : 'build'}:${definition.key}`
388
405
  let resolved = resolvedFontCache.get(key)
389
406
  if (!resolved) {
@@ -506,7 +523,10 @@ async function resolveLocalFont(
506
523
  ? false
507
524
  : true
508
525
  const preload = request.options.preload !== false
509
- const sources = normalizeLocalSources(request.options, request.callerFile)
526
+ const sources = normalizeLocalSources(
527
+ request.options,
528
+ request.callerFile && path.resolve(context.config.root, request.callerFile),
529
+ )
510
530
  const emitted = await Promise.all(
511
531
  sources.map(async source => ({
512
532
  ...source,
@@ -785,7 +805,7 @@ async function emitFontBytes(
785
805
  // Served from `/_next/static/media/` — files under the out dir's public/ map 1:1 to the URL path,
786
806
  // which is the only place dev and build both look them up.
787
807
  const mediaSegments = ['_next', 'static', 'media']
788
- const outDir = path.join(context.config.outPath, 'public', ...mediaSegments)
808
+ const outDir = path.join(context.config.outPath, 'static', ...mediaSegments)
789
809
  const file = path.join(outDir, filename)
790
810
  const asset = {
791
811
  ext,
@@ -134,13 +134,13 @@ async function renderWithSatori(
134
134
  element: unknown,
135
135
  options: ImageResponseOptions,
136
136
  ): Promise<Uint8Array | undefined> {
137
+ // Fonts first: without one satori cannot lay out text, so loading it would be wasted work.
138
+ const fonts = normalizeFonts(options.fonts)
139
+ if (fonts.length === 0) return undefined
137
140
  const satori = await loadOptional<SatoriModule>('satori')
138
141
  const resvg = await loadOptional<ResvgModule>('@resvg/resvg-js')
139
142
  if (!satori || !resvg) return undefined
140
143
 
141
- const fonts = normalizeFonts(options.fonts)
142
- if (fonts.length === 0) return undefined
143
-
144
144
  const width = dimension(options.width, DEFAULT_WIDTH, 'width')
145
145
  const height = dimension(options.height, DEFAULT_HEIGHT, 'height')
146
146
 
@@ -1,5 +1,6 @@
1
- import { existsSync, readdirSync, readFileSync, type Dirent } from 'node:fs'
1
+ import { existsSync, readdirSync, type Dirent } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { readSourceSync } from '../../resolve/source-text'
3
4
 
4
5
  /**
5
6
  * Build-time app facts that let compat drop code an app cannot reach. The fact here: whether any
@@ -95,7 +96,7 @@ function scan(dir: string, match: (source: string) => boolean): boolean {
95
96
  if (!sourceFile.test(entry.name)) continue
96
97
  let source: string
97
98
  try {
98
- source = readFileSync(file, 'utf8')
99
+ source = readSourceSync(file)
99
100
  } catch {
100
101
  continue
101
102
  }
@@ -2,15 +2,19 @@
2
2
  // shims plus the API-route and data-fetch (`/_next/data`) dispatch. Registered by
3
3
  // ../register/pages-api.ts, which wires the exports below into the extension registries.
4
4
 
5
- import { existsSync, readFileSync } from 'node:fs'
5
+ import { type Dirent, existsSync, readFileSync, readdirSync } from 'node:fs'
6
6
  import { Writable } from 'node:stream'
7
7
  import path from 'node:path'
8
- import { devServerModuleHref } from '../../runtime/modules'
9
- import { pagesApiBundleTargetForRuntime } from '../../runtime/loader'
8
+ import {
9
+ devServerModuleHref,
10
+ pagesApiBundleTargetForRuntime,
11
+ serverBundleTargetForRuntime,
12
+ } from '../../runtime/load'
13
+ import { productionRelease } from '../../runtime/production'
14
+ import type { ResolvedConfig } from '../../config'
10
15
  import { getRequestExtensions, type RequestInterceptor } from '../../extensions'
11
16
  import { getRequestRuntime } from '../../routing/request-environment'
12
- import { selectRouteForRequest } from '../../routing/routes'
13
- import { serverBundleTargetForRuntime } from '../../runtime/loader'
17
+ import { selectRouteForRequest } from '../../routing/match'
14
18
  import { setWorkUnitRoute } from '../../request/context'
15
19
  import { canonicalUrlHref } from '../next/canonical-url'
16
20
  import { markPathRevalidated, normalizeRevalidatePath } from '../cache/revalidate'
@@ -65,7 +69,7 @@ export const pagesDataInterceptor: RequestInterceptor = async request => {
65
69
  return dataResponse({ notFound: true }, 404, url)
66
70
  }
67
71
  const route = selection.route
68
- if (!isMaterializedPagesRoute(route.file)) return undefined
72
+ if (!isMaterializedPagesRoute(runtime.config, route.file)) return undefined
69
73
 
70
74
  const module = (await import(
71
75
  await devServerModuleHref(
@@ -150,8 +154,19 @@ function dataResponse(body: Record<string, unknown>, status: number, url: URL):
150
154
 
151
155
  // The materialized hybrid app hosts BOTH routers; only pages wrappers
152
156
  // re-export from source-pages/, so sniff the (tiny, generated) wrapper body —
153
- // app routes must never serve _next/data.
154
- function isMaterializedPagesRoute(file: string): boolean {
157
+ // app routes must never serve _next/data. A release carries the answer.
158
+ const releasedPagesRoutes = new WeakMap<object, Set<string>>()
159
+
160
+ export function isMaterializedPagesRoute(config: ResolvedConfig, file: string): boolean {
161
+ const released = productionRelease(config)?.compat?.pagesRoutes
162
+ if (released) {
163
+ let routes = releasedPagesRoutes.get(released)
164
+ if (!routes) {
165
+ routes = new Set(released.map(route => path.join(config.root, route)))
166
+ releasedPagesRoutes.set(released, routes)
167
+ }
168
+ return routes.has(file)
169
+ }
155
170
  if (!file.split(path.sep).join('/').includes('pnext-pages-compat/')) return false
156
171
  try {
157
172
  return readFileSync(file, 'utf8').includes('source-pages/')
@@ -160,6 +175,29 @@ function isMaterializedPagesRoute(file: string): boolean {
160
175
  }
161
176
  }
162
177
 
178
+ /** The materialized pages-router wrappers of a pages-compat app, root-relative, for its release. */
179
+ export function pagesRouteFacts(config: ResolvedConfig): string[] {
180
+ if (!config.appPath.includes('pnext-pages-compat')) return []
181
+ const routes: string[] = []
182
+ const visit = (dir: string) => {
183
+ let entries: Dirent[]
184
+ try {
185
+ entries = readdirSync(dir, { withFileTypes: true })
186
+ } catch {
187
+ return
188
+ }
189
+ for (const entry of entries) {
190
+ const file = path.join(dir, entry.name)
191
+ if (entry.isDirectory()) visit(file)
192
+ else if (isMaterializedPagesRoute(config, file)) {
193
+ routes.push(path.relative(config.root, file).split(path.sep).join('/'))
194
+ }
195
+ }
196
+ }
197
+ visit(config.appPath)
198
+ return routes
199
+ }
200
+
163
201
  function pagesDataRequestShim(request: Request, url: URL) {
164
202
  return {
165
203
  method: request.method,
@@ -194,11 +232,16 @@ function pagesDataResponseShim() {
194
232
 
195
233
  export const pagesApiInterceptor: RequestInterceptor = async (request, { config }) => {
196
234
  const url = new URL(request.url)
197
- const file = pagesApiFile(config.root, url.pathname)
235
+ const released = productionRelease(config)?.compat?.pagesApi
236
+ const exists = released
237
+ ? (candidate: string) => path.relative(config.root, candidate) in released
238
+ : existsSync
239
+ const file = pagesApiFile(config.root, url.pathname, exists)
198
240
  if (!file) return undefined
241
+ const runtime = released ? released[path.relative(config.root, file)] : apiRuntime(file)
199
242
  const imported = (await import(
200
243
  await devServerModuleHref(config, file, 'build', {
201
- conditionTarget: pagesApiBundleTargetForRuntime(apiRuntime(file)),
244
+ conditionTarget: pagesApiBundleTargetForRuntime(runtime),
202
245
  })
203
246
  )) as PagesApiModule
204
247
  if (typeof imported.default !== 'function') return undefined
@@ -208,7 +251,7 @@ export const pagesApiInterceptor: RequestInterceptor = async (request, { config
208
251
  // nextUrl, text() — Request prototype getters a spread would lose); node
209
252
  // handlers get the IncomingMessage-shaped shim.
210
253
  const handlerRequest =
211
- apiRuntime(file) === 'edge' ? toNextRequest(request) : createPagesApiRequest(request, url)
254
+ runtime === 'edge' ? toNextRequest(request) : createPagesApiRequest(request, url)
212
255
  const finished = withPagesApiHandlerSpan(url.pathname, () =>
213
256
  Promise.resolve(imported.default!(handlerRequest, responder.res)),
214
257
  ).catch(error => responder.fail(error))
@@ -226,7 +269,33 @@ export const pagesApiInterceptor: RequestInterceptor = async (request, { config
226
269
 
227
270
  const STREAM_STARTED = Symbol('pnext.pagesApiStreamStarted')
228
271
 
229
- function pagesApiFile(root: string, pathname: string): string | undefined {
272
+ /** Every pages API source with its runtime (`nodejs` unless declared), root-relative: what a release serves. */
273
+ export function pagesApiFacts(root: string): Record<string, string> {
274
+ const facts: Record<string, string> = {}
275
+ const visit = (dir: string) => {
276
+ let entries: Dirent[]
277
+ try {
278
+ entries = readdirSync(dir, { withFileTypes: true })
279
+ } catch {
280
+ return
281
+ }
282
+ for (const entry of entries) {
283
+ const file = path.join(dir, entry.name)
284
+ if (entry.isDirectory()) visit(file)
285
+ else if (pageExtensions.some(ext => entry.name.endsWith(`.${ext}`))) {
286
+ facts[path.relative(root, file)] = apiRuntime(file) ?? 'nodejs'
287
+ }
288
+ }
289
+ }
290
+ visit(path.join(root, 'pages', 'api'))
291
+ return facts
292
+ }
293
+
294
+ function pagesApiFile(
295
+ root: string,
296
+ pathname: string,
297
+ exists: (file: string) => boolean,
298
+ ): string | undefined {
230
299
  if (pathname !== '/api' && !pathname.startsWith('/api/')) return undefined
231
300
  let relative: string
232
301
  try {
@@ -239,9 +308,9 @@ function pagesApiFile(root: string, pathname: string): string | undefined {
239
308
  if (base !== baseDir && !base.startsWith(`${baseDir}${path.sep}`)) return undefined
240
309
  for (const ext of pageExtensions) {
241
310
  const file = `${base}.${ext}`
242
- if (existsSync(file)) return file
311
+ if (exists(file)) return file
243
312
  const index = path.join(base, `index.${ext}`)
244
- if (existsSync(index)) return index
313
+ if (exists(index)) return index
245
314
  }
246
315
  return undefined
247
316
  }