@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
@@ -14,7 +14,7 @@
14
14
  // Everything is resolved relative to `process.cwd()`, matching the SWC transform's root. That is what
15
15
  // makes the multi-project fixture work: each sub-project is built with its own cwd.
16
16
 
17
- import { existsSync } from 'node:fs'
17
+ import { existsSync, realpathSync } from 'node:fs'
18
18
  import path from 'node:path'
19
19
  import { withSniff, type ServerSourceTransform } from '../../extensions'
20
20
  import type { RelayCompilerConfig } from './config'
@@ -35,6 +35,14 @@ function relayTagPattern(): RegExp {
35
35
 
36
36
  const OPERATION_NAME = /\b(?:query|mutation|subscription|fragment)\s+([A-Za-z_][\w]*)/
37
37
 
38
+ function realFile(file: string): string {
39
+ try {
40
+ return realpathSync(file)
41
+ } catch {
42
+ return file
43
+ }
44
+ }
45
+
38
46
  function isUnder(dir: string, file: string): boolean {
39
47
  const relative = path.relative(dir, file)
40
48
  return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
@@ -61,6 +69,7 @@ export function createRelayTransform(
61
69
 
62
70
  const cwd = path.resolve(process.cwd())
63
71
  const srcDir = path.resolve(cwd, relay.src)
72
+ const realSrcDir = realFile(srcDir)
64
73
  const artifactDir = relay.artifactDirectory
65
74
  ? path.resolve(cwd, relay.artifactDirectory)
66
75
  : undefined
@@ -88,11 +97,14 @@ export function createRelayTransform(
88
97
 
89
98
  return withSniff(['graphql'], (source, file) => {
90
99
  if (!source.includes('graphql`') && !/graphql\s*`/.test(source)) return source
91
- const absolute = path.resolve(file)
100
+ const resolved = path.resolve(file)
101
+ // A build materializes pages under its out dir; its links lead back to the source file.
102
+ const absolute = isUnder(cwd, resolved) ? realFile(resolved) : resolved
92
103
  // Next scopes the transform to `src`. Files outside the build cwd entirely
93
104
  // (materialized copies) are not covered by that check, so only skip a file
94
105
  // that lives under the cwd but outside the configured source root.
95
- if (isUnder(cwd, absolute) && !isUnder(srcDir, absolute)) return source
106
+ const inSrc = isUnder(srcDir, resolved) || isUnder(realSrcDir, absolute)
107
+ if (isUnder(cwd, absolute) && !inSrc) return source
96
108
 
97
109
  const bindings = new Map<string, string>()
98
110
  const replaced = source.replace(relayTagPattern(), (match, body: string) => {
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { existsSync, readdirSync, statSync } from 'node:fs'
14
14
  import path from 'node:path'
15
+ import { outCachePath } from '../../out-paths'
15
16
 
16
17
  /** Extensions whose files also get an extension-less request key (webpack resolves both). */
17
18
  const RESOLVABLE_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.json']
@@ -210,7 +211,7 @@ function resolveContextDir(
210
211
  const direct = path.resolve(path.dirname(file), specifier)
211
212
  if (existsSync(direct)) return direct
212
213
  if (paths?.outPath && paths.workspaceRoot) {
213
- const cacheRoot = path.join(paths.outPath, 'cache', 'server')
214
+ const cacheRoot = path.join(outCachePath(paths.outPath), 'server')
214
215
  const relative = path.relative(cacheRoot, file)
215
216
  if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
216
217
  const segments = relative.split(path.sep)
@@ -25,6 +25,7 @@ import type { Plugin } from 'esbuild'
25
25
  import type { ResolvedConfig } from '../../config'
26
26
  import { rewriteSpecifierLiterals } from '../../resolve/scan-facts'
27
27
  import { turbopackLoaderRules, turbopackResolveAlias, type WebpackLoaderRule } from './config'
28
+ import { outCachePath } from '../../out-paths'
28
29
 
29
30
  interface LoaderFn {
30
31
  (this: LoaderContext, content: string | Buffer): unknown
@@ -293,7 +294,7 @@ function resolveLoaderRequest(root: string, context: string, request: string): P
293
294
  */
294
295
  function originalResourcePath(resourcePath: string, config: ResolvedConfig | undefined): string {
295
296
  if (config) {
296
- const cacheRoot = path.join(config.outPath, 'cache', 'server')
297
+ const cacheRoot = path.join(outCachePath(config.outPath), 'server')
297
298
  const rel = path.relative(cacheRoot, resourcePath)
298
299
  if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) {
299
300
  const segments = rel.split(path.sep)
@@ -451,7 +452,7 @@ async function emitAssetResourceModule(
451
452
  .basename(resourcePath, path.extname(resourcePath))
452
453
  .replace(/[^A-Za-z0-9_-]+/g, '-')
453
454
  const relative = path.posix.join('_next', 'static', 'media', `${base}.${hash}${ext}`)
454
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
455
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
455
456
  await mkdir(path.dirname(target), { recursive: true })
456
457
  if (!existsSync(target)) await writeFile(target, bytes)
457
458
  const src = `/${relative}`
@@ -128,7 +128,7 @@ async function workerAssetModuleSource(config: ResolvedConfig, file: string): Pr
128
128
  source = `const src = ${JSON.stringify(src)};\nexport default { src };\nexport { src };\n`
129
129
  }
130
130
  for (const relative of emitted) {
131
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
131
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
132
132
  await mkdir(path.dirname(target), { recursive: true })
133
133
  if (!existsSync(target)) await copyFile(sourcePath, target)
134
134
  }
@@ -208,7 +208,7 @@ async function bundleWorkerEntry(config: ResolvedConfig, entry: string): Promise
208
208
  const hash = createHash('sha256').update(output.contents).digest('hex').slice(0, 8)
209
209
  const base = path.basename(entry, path.extname(entry)).replace(/[^A-Za-z0-9_-]+/g, '-')
210
210
  const relative = path.posix.join('assets', 'workers', `${base}.${hash}.js`)
211
- const target = path.join(config.outPath, 'public', ...relative.split('/'))
211
+ const target = path.join(config.outPath, 'static', ...relative.split('/'))
212
212
  await mkdir(path.dirname(target), { recursive: true })
213
213
  await writeFile(target, output.contents)
214
214
  return `/${relative}`
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { nextCompatEnabled } from '../compat/aliases'
4
+ import { productionRelease } from '../runtime/production'
4
5
  import type { ResolvedConfig } from '../config'
5
6
  import type { ResponseFinalizer, ResponseFinalizerContext } from '../extensions'
6
7
  import { getNextConfig } from './next/config-loader'
@@ -31,7 +32,7 @@ export function cacheControlFinalizers(config: ResolvedConfig): ResponseFinalize
31
32
  rules ??= loadCompatHeaders(config.root)
32
33
  applyConfigHeaders(ctx, await rules, config.basePath)
33
34
  applyPoweredByHeader(ctx)
34
- pagesRevalidateRules ??= loadPagesRevalidateRules(config.root)
35
+ pagesRevalidateRules ??= loadPagesRevalidateRules(config)
35
36
  applyDefaultIsrHeader(ctx, pagesRevalidateRules)
36
37
  }
37
38
  return [finalizer]
@@ -139,20 +140,27 @@ function isBrowserCacheable(value: string): boolean {
139
140
  return !/\bno-store\b|\bno-cache\b/i.test(value)
140
141
  }
141
142
 
142
- function loadPagesRevalidateRules(root: string): RevalidateRule[] {
143
+ function loadPagesRevalidateRules(config: ResolvedConfig): RevalidateRule[] {
144
+ const facts =
145
+ productionRelease(config)?.compat?.pagesRevalidate ?? pagesRevalidateFacts(config.root)
146
+ return facts.flatMap(([relative, revalidateSeconds]) => {
147
+ const regex = pagesRouteRegex(relative)
148
+ return regex ? [{ regex, revalidateSeconds }] : []
149
+ })
150
+ }
151
+
152
+ /** Pages with a `getStaticProps` revalidate window: `[pages-relative file, seconds]`. */
153
+ export function pagesRevalidateFacts(root: string): [string, number][] {
143
154
  const pagesDir = path.join(root, 'pages')
144
155
  if (!existsSync(pagesDir)) return []
145
- const rules: RevalidateRule[] = []
156
+ const facts: [string, number][] = []
146
157
  for (const file of walkFiles(pagesDir)) {
147
158
  const source = readFileSync(file, 'utf8')
148
159
  if (!/\bgetStaticProps\b/.test(source)) continue
149
160
  const match = /\brevalidate\s*:\s*(\d+)/.exec(source)
150
- if (!match?.[1]) continue
151
- const regex = pagesRouteRegex(path.relative(pagesDir, file))
152
- if (!regex) continue
153
- rules.push({ regex, revalidateSeconds: Number(match[1]) })
161
+ if (match?.[1]) facts.push([path.relative(pagesDir, file), Number(match[1])])
154
162
  }
155
- return rules
163
+ return facts
156
164
  }
157
165
 
158
166
  function walkFiles(dir: string): string[] {
@@ -65,7 +65,7 @@ export function inlineCssStylesheets(
65
65
  ): string[] | undefined {
66
66
  if (options.dev || suppressed.getStore() === true || !inlineCssEnabled(config)) return undefined
67
67
  const assets = options.assetNames.map(asset =>
68
- readCss(`${config.outPath}/public/assets/${asset}`),
68
+ readCss(`${config.outPath}/static/assets/${asset}`),
69
69
  )
70
70
  if (assets.some(css => css === undefined)) return undefined
71
71
  const css = [options.prependCss, ...assets].filter(Boolean).join('\n')
@@ -4,10 +4,10 @@ import path from 'node:path'
4
4
  import type { Plugin } from 'esbuild'
5
5
  import type { ResolvedConfig } from '../../config'
6
6
  import { resolveImport } from '../../resolve/imports'
7
- import { hoistCssImports } from '../../css/build'
8
7
  import { maybeLightningcssTransform } from './lightningcss'
9
8
  import { escapeRegex } from '../../utils/code'
10
9
  import { cssModuleScopePath } from '../../utils/fs'
10
+ import { productionRelease } from '../../runtime/production'
11
11
 
12
12
  interface ComposeRef {
13
13
  names: string[]
@@ -23,31 +23,44 @@ interface CssModuleInfo {
23
23
  const pagesCompatIgnoredSourceEntries = new Set(['.next', '.pnext', 'app', 'node_modules', 'pages'])
24
24
 
25
25
  export function materializeNextCssSources(config: ResolvedConfig) {
26
+ // A production release already compiled everything these links would let resolve.
27
+ if (productionRelease(config)) return
26
28
  const generatedRoot = path.dirname(config.appPath)
27
29
  if (path.basename(path.dirname(generatedRoot)) !== 'pnext-pages-compat') return
28
30
 
31
+ const outTop = path.relative(config.root, config.outRootPath).split(path.sep)[0]
32
+ // Inside the app (a build's output) links stay relative, naming no build-machine path.
33
+ const inApp = !path.relative(config.root, generatedRoot).startsWith('..')
29
34
  for (const entry of readdirSync(config.root)) {
30
- if (pagesCompatIgnoredSourceEntries.has(entry)) continue
35
+ if (pagesCompatIgnoredSourceEntries.has(entry) || entry === outTop) continue
31
36
  const source = path.join(config.root, entry)
32
37
  const target = path.join(generatedRoot, entry)
33
38
  if (existsSync(target)) continue
34
39
  try {
35
- symlinkSync(source, target, lstatSync(source).isDirectory() ? 'dir' : 'file')
40
+ symlinkSync(
41
+ inApp ? path.relative(generatedRoot, source) : source,
42
+ target,
43
+ lstatSync(source).isDirectory() ? 'dir' : 'file',
44
+ )
36
45
  } catch {
37
46
  // A concurrent materialization may have linked the same source already.
38
47
  }
39
48
  }
40
49
  }
41
50
 
51
+ let cssBuild: Promise<typeof import('../../css/build')> | undefined
52
+
42
53
  export function nextCssModuleChunkPlugin(config: ResolvedConfig): Plugin {
43
54
  return {
44
55
  name: 'pnext-compat-css-modules',
45
56
  setup(build) {
46
- build.onLoad({ filter: /\.css$/ }, ({ path: file }) => {
57
+ build.onLoad({ filter: /\.css$/ }, async ({ path: file }) => {
47
58
  const source = nextCssSource(file, config.root)
48
59
  // This onLoad claims every `.css`, so core's directive-order plugin
49
60
  // (registered after it) never sees a compat app's stylesheet — hoist here.
50
- const raw = hoistCssImports(readFileSync(source, 'utf8'))
61
+ const raw = (await (cssBuild ??= import('../../css/build'))).hoistCssImports(
62
+ readFileSync(source, 'utf8'),
63
+ )
51
64
  const contents = file.endsWith('.module.css')
52
65
  ? transformCssModule(raw, source, config.root)
53
66
  : rewriteTransparentBodyChildren(raw)
@@ -130,7 +130,7 @@ function collectOutputs(config: ResolvedConfig, manifest: BuildManifest): Adapte
130
130
  const prerenderedRoutes = new Set<string>()
131
131
 
132
132
  for (const [relative, metadata] of Object.entries(manifest.staticFiles ?? {})) {
133
- const filePath = path.join(config.outPath, 'public', relative)
133
+ const filePath = path.join(config.outPath, 'static', relative)
134
134
  // The manifest also records prerenders that produced no body on disk
135
135
  // (an empty response, `_not-found` / `_global-error`). Those are not
136
136
  // deployable artifacts: leave them out, and leave the owning route as a
@@ -42,7 +42,7 @@ export async function emitStaticExport({ config, manifest, log }: EmitStaticExpo
42
42
  // the running server - client runtime code fetches it unconditionally, and Next's own asset-404 tests
43
43
  // probe it as "a known valid asset path". Export mode additionally snapshots a standalone `out/` tree
44
44
  // below; the manifest files it needs are the same bytes, so write the server-served copy first.
45
- await log.step('build manifest', () => writeBuildManifests(path.join(config.outPath, 'public')))
45
+ await log.step('build manifest', () => writeBuildManifests(path.join(config.outPath, 'static')))
46
46
  const output = getNextConfig().output
47
47
  if (output === 'standalone') {
48
48
  await emitStandalone({ config, manifest, log })
@@ -74,7 +74,7 @@ async function writeExportTree(config: ResolvedConfig, manifest: BuildManifest):
74
74
  * can be pointed at their new location.
75
75
  */
76
76
  async function copyRuntimeAssets(config: ResolvedConfig, outDir: string): Promise<Set<string>> {
77
- const assetsDir = path.join(config.outPath, 'public', 'assets')
77
+ const assetsDir = path.join(config.outPath, 'static', 'assets')
78
78
  const assets = new Set<string>()
79
79
  for (const file of await listFiles(assetsDir)) {
80
80
  const relative = toPosixPath(path.relative(assetsDir, file))
@@ -91,7 +91,7 @@ async function copyPublicTree(
91
91
  outDir: string,
92
92
  assets: ReadonlySet<string>,
93
93
  ): Promise<void> {
94
- const publicDir = path.join(config.outPath, 'public')
94
+ const publicDir = path.join(config.outPath, 'static')
95
95
  for (const file of await listFiles(publicDir)) {
96
96
  const relative = toPosixPath(path.relative(publicDir, file))
97
97
  if (relative.startsWith('assets/')) continue
@@ -159,7 +159,7 @@ function exportPages(config: ResolvedConfig, manifest: BuildManifest): ExportPag
159
159
  if (metadata.kind !== 'page' || !relative.endsWith('.html')) continue
160
160
  const route = metadata.routeId ? routeById.get(metadata.routeId) : undefined
161
161
  if (route?.kind !== 'page') continue
162
- const htmlFile = path.join(config.outPath, 'public', ...relative.split('/'))
162
+ const htmlFile = path.join(config.outPath, 'static', ...relative.split('/'))
163
163
  pages.push({
164
164
  route,
165
165
  htmlFile,
@@ -1,18 +1,13 @@
1
1
  // `output: 'standalone'` emitter (COMPAT).
2
2
  //
3
- // Next's standalone build drops a self-contained `.next/standalone/` tree whose `server.js` boots the
4
- // production server on PORT/HOSTNAME. The e2e suites MOVE that folder to a fresh tmp dir, run
5
- // `node server.js`, wait for the `- Local:` ready banner, and expect route requests to 200. The trace
6
- // suite additionally reads a `.nft.json` beside each page.
7
- //
8
- // pnext has no ahead-of-time Node bundle to drop in, and its production server runs under Bun, so the
9
- // emitted `server.js` is a thin Node launcher that re-execs the real pnext server via Bun, pointed back
10
- // at the original build root. The child prints the `- Local:` banner itself, which the launcher inherits
11
- // straight onto its own stdout, so the harness's readiness regex fires without a proxy hop. The original
12
- // build tree outlives the moved standalone folder for the duration of the test, so absolute references
13
- // back into it are safe.
3
+ // Every pnext build writes `<outDir>/standalone/`, the self-contained server; with `output:
4
+ // 'standalone'` the out dir defaults to `.next` (compat/next/config-loader), so it lands where Next's
5
+ // does. Next's contract on top: a `server.js` that boots on PORT/HOSTNAME under `node server.js`, and
6
+ // the `.nft.json` traces its suites read. The e2e suites MOVE the folder to a fresh tmp dir, run
7
+ // `node server.js` and wait for the `- Local:` banner; `server.js` hands over to Bun, which serves the
8
+ // folder's own `server/entry.js`.
14
9
  import { existsSync, readFileSync } from 'node:fs'
15
- import { mkdir, rm, writeFile } from 'node:fs/promises'
10
+ import { mkdir, writeFile } from 'node:fs/promises'
16
11
  import path from 'node:path'
17
12
  import type { ResolvedConfig } from '../../config'
18
13
  import type { BuildManifest, RouteManifestEntry } from '../../types'
@@ -37,68 +32,38 @@ export async function emitStandalone({
37
32
  }
38
33
 
39
34
  async function writeStandalone(config: ResolvedConfig, manifest: BuildManifest): Promise<void> {
40
- const standaloneDir = path.join(config.root, '.next', 'standalone')
41
- await rm(standaloneDir, { recursive: true, force: true })
42
- await mkdir(standaloneDir, { recursive: true })
43
- await writeServerEntry(config, standaloneDir)
35
+ const standaloneDir = config.outPath
36
+ await writeFile(path.join(standaloneDir, 'server.js'), standaloneServerSource())
44
37
  await writeTraceFiles(config, standaloneDir)
45
38
  await writeMiddlewareManifest(manifest, standaloneDir)
46
39
  }
47
40
 
48
41
  /**
49
- * The self-contained launcher plus its package.json. The launcher is always CommonJS (a
50
- * `{ "type": "commonjs" }` sidecar pins the module system even when the app - and the tmp dir the test
51
- * moves the folder into - is ESM), so it runs unchanged under `node server.js` regardless of the
52
- * surrounding package type.
42
+ * Starts `server/entry.js` under Bun on the PORT/HOSTNAME the standalone contract passes through the
43
+ * environment. The child's `- Local:` banner is inherited onto this process's stdout, so the harness's
44
+ * readiness regex fires directly. Signals are forwarded so the harness's `killApp` (SIGTERM) tears the
45
+ * whole tree down and lets pending after() callbacks drain.
53
46
  */
54
- async function writeServerEntry(config: ResolvedConfig, standaloneDir: string): Promise<void> {
55
- await writeFile(
56
- path.join(standaloneDir, 'package.json'),
57
- `${JSON.stringify({ name: 'pnext-standalone', version: '0.0.0', type: 'commonjs', private: true }, null, 2)}\n`,
58
- )
59
- await writeFile(path.join(standaloneDir, 'server.js'), standaloneServerSource(config))
60
- }
47
+ function standaloneServerSource(): string {
48
+ return `// pnext standalone launcher, generated by pnext build (src/compat/export/standalone.ts).
49
+ import { spawn } from 'node:child_process';
61
50
 
62
- /**
63
- * Boots `pnext start` under Bun against the original build root, on the
64
- * PORT/HOSTNAME the standalone contract passes through the environment. The
65
- * child's `- Local:` banner is inherited onto this process's stdout, so the
66
- * harness's readiness regex fires directly. Signals are forwarded so the
67
- * harness's `killApp` (SIGTERM) tears the whole tree down and lets pending
68
- * after() callbacks drain.
69
- */
70
- function standaloneServerSource(config: ResolvedConfig): string {
71
- const cliPath = path.resolve(import.meta.dirname, '..', '..', 'cli', 'index.ts')
72
- return `'use strict';
73
- // pnext standalone launcher. Generated by pnext build; see
74
- // src/compat/export/standalone.ts. Spawned as \`node server.js\` from the moved
75
- // standalone directory; re-execs the real pnext server under Bun.
76
- const { spawn } = require('node:child_process');
77
-
78
- const PNEXT_CLI = ${JSON.stringify(cliPath)};
79
- const PNEXT_ROOT = ${JSON.stringify(config.root)};
80
- const port = String(process.env.PORT || '3000');
51
+ const port = Number(process.env.PORT || '3000');
81
52
  const hostname = process.env.HOSTNAME || '0.0.0.0';
53
+ const entry = new URL('./server/entry.js', import.meta.url).href;
54
+ const boot = \`const { start } = await import(\${JSON.stringify(entry)}); await start(\${JSON.stringify({ port, hostname })});\`;
82
55
 
83
- const child = spawn(
84
- 'bun',
85
- ['--conditions=react-server', PNEXT_CLI, 'start', '--port', port, '--hostname', hostname],
86
- {
87
- cwd: PNEXT_ROOT,
88
- env: {
89
- ...process.env,
90
- // Suppresses \`pnext start\`'s "output: standalone" warning (Next gates the
91
- // same warning on this private env being set by its standalone server).
92
- __NEXT_PRIVATE_STANDALONE_CONFIG: '1',
93
- PNEXT_COMPAT: 'next',
94
- BROWSER: 'none',
95
- NODE_ENV: 'production',
96
- PORT: port,
97
- HOSTNAME: hostname,
98
- },
99
- stdio: ['ignore', 'inherit', 'inherit'],
100
- }
101
- );
56
+ const child = spawn('bun', ['--conditions=react-server', '-e', boot], {
57
+ env: {
58
+ ...process.env,
59
+ // Suppresses \`pnext start\`'s "output: standalone" warning (Next gates the
60
+ // same warning on this private env being set by its standalone server).
61
+ __NEXT_PRIVATE_STANDALONE_CONFIG: '1',
62
+ BROWSER: 'none',
63
+ NODE_ENV: 'production',
64
+ },
65
+ stdio: ['ignore', 'inherit', 'inherit'],
66
+ });
102
67
 
103
68
  child.on('error', (error) => {
104
69
  console.error('pnext standalone: failed to start the pnext server:', error);
@@ -378,7 +378,7 @@ async function fetchUpstream(
378
378
  }
379
379
  const publicFile = await readPublicFile(
380
380
  [
381
- path.join(coreConfig.outPath, 'public'),
381
+ path.join(coreConfig.outPath, 'static'),
382
382
  coreConfig.publicPath,
383
383
  path.join(coreConfig.root, 'public'),
384
384
  ],
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { getWorkUnit } from '../../request/context'
12
12
  import { getRequestRuntime } from '../../routing/request-environment'
13
- import { selectRouteForRequest } from '../../routing/routes'
13
+ import { selectRouteForRequest } from '../../routing/match'
14
14
  import path from 'node:path'
15
15
  import type { RouteManifestEntry } from '../../types'
16
16
  import type { RequestErrorContext, RequestErrorInfo } from '../../extensions'
@@ -23,6 +23,7 @@ import { build } from '../../utils/esbuild'
23
23
  import type { ResolvedConfig } from '../../config'
24
24
  import { getNextConfig } from '../next/config-loader'
25
25
  import { instrumentationLookupRoots } from './instrumentation'
26
+ import { outCachePath } from '../../out-paths'
26
27
 
27
28
  const CLIENT_BASENAMES = [
28
29
  'instrumentation-client.ts',
@@ -90,10 +91,10 @@ export async function buildInstrumentationClient(config: ResolvedConfig): Promis
90
91
  const userFile = findInstrumentationClient(config)
91
92
  if (!userFile) return
92
93
  const modules = [...injectEntries(config), userFile]
93
- const workDir = path.join(config.outPath, 'cache', 'instrumentation-client')
94
+ const workDir = path.join(outCachePath(config.outPath), 'instrumentation-client')
94
95
  mkdirSync(workDir, { recursive: true })
95
96
  const entryFile = path.join(workDir, 'entry.ts')
96
- const outfile = path.join(config.outPath, 'public', INSTRUMENTATION_CLIENT_ASSET)
97
+ const outfile = path.join(config.outPath, 'static', INSTRUMENTATION_CLIENT_ASSET)
97
98
  mkdirSync(path.dirname(outfile), { recursive: true })
98
99
  await Bun.write(entryFile, entrySource(modules))
99
100
  const tsconfig = path.join(config.root, 'tsconfig.json')
@@ -116,7 +117,7 @@ let bundlePresence: boolean | undefined
116
117
 
117
118
  /** Whether the built instrumentation-client bundle exists (cached). */
118
119
  function hasInstrumentationClientBundle(config: ResolvedConfig): boolean {
119
- bundlePresence ??= existsSync(path.join(config.outPath, 'public', INSTRUMENTATION_CLIENT_ASSET))
120
+ bundlePresence ??= existsSync(path.join(config.outPath, 'static', INSTRUMENTATION_CLIENT_ASSET))
120
121
  return bundlePresence
121
122
  }
122
123
 
@@ -12,7 +12,15 @@
12
12
  // separate module state) and calling its register() with NEXT_RUNTIME='edge' set for the duration.
13
13
  // Apps without edge entities never get the pass.
14
14
 
15
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs'
15
+ import {
16
+ existsSync,
17
+ mkdirSync,
18
+ readFileSync,
19
+ readdirSync,
20
+ rmSync,
21
+ statSync,
22
+ writeFileSync,
23
+ } from 'node:fs'
16
24
  import path from 'node:path'
17
25
  import { pathToFileURL } from 'node:url'
18
26
  import { createRequire } from 'node:module'
@@ -21,6 +29,7 @@ import { build } from '../../utils/esbuild'
21
29
  import type { ResolvedConfig } from '../../config'
22
30
  import { resolveExternalLoadTarget } from '../../resolve/imports'
23
31
  import { compatAliases, reactServerLayerAliases } from '../aliases'
32
+ import { productionRelease } from '../../runtime/production'
24
33
 
25
34
  const INSTRUMENTATION_BASENAMES = [
26
35
  'instrumentation.ts',
@@ -136,38 +145,89 @@ async function importInstrumentation(
136
145
  config: ResolvedConfig,
137
146
  suffix = '',
138
147
  ): Promise<InstrumentationModule> {
139
- const root = config.root
140
148
  // Under config.outPath, so a dev server's scratch bundle lives in its own
141
149
  // subtree and a concurrent build never removes it mid-import.
142
150
  const outDir = path.join(config.outPath, 'instrumentation')
143
151
  mkdirSync(outDir, { recursive: true })
144
152
  const outfile = path.join(outDir, `instrumentation.${Date.now()}${suffix}.mjs`)
145
- const tsconfig = path.join(root, 'tsconfig.json')
146
153
  try {
147
- await build({
148
- entryPoints: [file],
149
- outfile,
150
- bundle: true,
151
- platform: 'node',
152
- format: 'esm',
153
- target: 'esnext',
154
- packages: 'external',
155
- plugins: [instrumentationPlugin(root, config)],
156
- ...(existsSync(tsconfig) ? { tsconfig } : {}),
157
- define: {
158
- __dirname: JSON.stringify(path.dirname(file)),
159
- __filename: JSON.stringify(file),
160
- 'import.meta.url': JSON.stringify(pathToFileURL(file).href),
161
- },
162
- logLevel: 'silent',
163
- sourcemap: false,
164
- })
154
+ await bundleInstrumentation(file, config, outfile)
165
155
  return (await import(pathToFileURL(outfile).href)) as InstrumentationModule
166
156
  } finally {
167
157
  rmSync(outfile, { force: true })
168
158
  }
169
159
  }
170
160
 
161
+ /**
162
+ * Bundle instrumentation for a production release: the node instance, plus a separate edge instance
163
+ * file when the app has edge entities. Out-relative paths, or undefined when the app has none.
164
+ */
165
+ export async function emitReleaseInstrumentation(config: ResolvedConfig, outDir: string) {
166
+ const file = findInstrumentation(config)
167
+ if (!file) return undefined
168
+ const node = await releaseInstrumentation(file, config, outDir, 'instrumentation')
169
+ if (!appHasEdgeEntities(config)) return { file: path.relative(config.outPath, node) }
170
+ const edge = await releaseInstrumentation(file, config, outDir, 'instrumentation.edge')
171
+ return { file: path.relative(config.outPath, node), edge: path.relative(config.outPath, edge) }
172
+ }
173
+
174
+ /** One release instance: located relative to itself, named by its content (a new generation never reuses a module). */
175
+ async function releaseInstrumentation(
176
+ file: string,
177
+ config: ResolvedConfig,
178
+ outDir: string,
179
+ name: string,
180
+ ) {
181
+ const temporary = path.join(outDir, `${name}.${process.pid}.tmp.mjs`)
182
+ await bundleInstrumentation(file, config, temporary, outDir)
183
+ const { relocateReleaseBundle } = await import('../../runtime/modules')
184
+ const contents = relocateReleaseBundle(config, temporary, readFileSync(temporary, 'utf8'))
185
+ rmSync(temporary, { force: true })
186
+ const outfile = path.join(outDir, `${name}.${Bun.hash(contents).toString(36)}.mjs`)
187
+ writeFileSync(outfile, contents)
188
+ return outfile
189
+ }
190
+
191
+ async function bundleInstrumentation(
192
+ file: string,
193
+ config: ResolvedConfig,
194
+ outfile: string,
195
+ releaseDir?: string,
196
+ ) {
197
+ const root = config.root
198
+ const tsconfig = path.join(root, 'tsconfig.json')
199
+ // A release instance finds its source relative to itself, never at the build machine's path.
200
+ const sourceUrl = releaseDir
201
+ ? `new URL(${JSON.stringify(path.relative(releaseDir, file).split(path.sep).join('/'))}, import.meta.url).href`
202
+ : JSON.stringify(pathToFileURL(file).href)
203
+ await build({
204
+ entryPoints: [file],
205
+ outfile,
206
+ bundle: true,
207
+ platform: 'node',
208
+ format: 'esm',
209
+ target: 'esnext',
210
+ packages: 'external',
211
+ plugins: [instrumentationPlugin(root, config)],
212
+ ...(existsSync(tsconfig) ? { tsconfig } : {}),
213
+ banner: {
214
+ js:
215
+ 'import { fileURLToPath as __pnextFileURLToPath } from "node:url";\n' +
216
+ 'import { dirname as __pnextDirname } from "node:path";\n' +
217
+ `const __pnextSourceUrl = ${sourceUrl};\n` +
218
+ 'const __pnextSourceFile = __pnextFileURLToPath(__pnextSourceUrl);\n' +
219
+ 'const __pnextSourceDir = __pnextDirname(__pnextSourceFile);',
220
+ },
221
+ define: {
222
+ __dirname: '__pnextSourceDir',
223
+ __filename: '__pnextSourceFile',
224
+ 'import.meta.url': '__pnextSourceUrl',
225
+ },
226
+ logLevel: 'silent',
227
+ sourcemap: false,
228
+ })
229
+ }
230
+
171
231
  /**
172
232
  * Load instrumentation and run register() once. Idempotent: repeated calls
173
233
  * (build + start both run init hooks) reuse the first load. Returns immediately;
@@ -175,6 +235,11 @@ async function importInstrumentation(
175
235
  */
176
236
  export function loadInstrumentation(config: ResolvedConfig): void {
177
237
  if (readyGate) return
238
+ const released = productionRelease(config)
239
+ if (released) {
240
+ readyGate = registerReleased(released.instrumentation)
241
+ return
242
+ }
178
243
  const file = findInstrumentation(config)
179
244
  if (!file) {
180
245
  readyGate = Promise.resolve()
@@ -197,21 +262,49 @@ export function loadInstrumentation(config: ResolvedConfig): void {
197
262
  })()
198
263
  }
199
264
 
265
+ /** A release's prebuilt instrumentation: register() on the node instance, then the edge instance. */
266
+ async function registerReleased(instrumentation: { file?: string; edge?: string } | undefined) {
267
+ if (!instrumentation?.file) return
268
+ try {
269
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
270
+ if (!process.env.NEXT_RUNTIME) process.env.NEXT_RUNTIME = 'nodejs'
271
+ loaded = (await import(pathToFileURL(instrumentation.file).href)) as InstrumentationModule
272
+ await loaded.register?.()
273
+ if (instrumentation.edge) {
274
+ await withEdgeRuntime(async () => {
275
+ const edgeInstance = (await import(
276
+ pathToFileURL(instrumentation.edge!).href
277
+ )) as InstrumentationModule
278
+ await edgeInstance.register?.()
279
+ }, 'instrumentation register() (edge pass) failed:')
280
+ }
281
+ } catch (error) {
282
+ console.error('instrumentation register() failed:', error)
283
+ }
284
+ }
285
+
200
286
  /**
201
287
  * The edge boot pass: a fresh instrumentation instance registered with NEXT_RUNTIME='edge' for the
202
288
  * duration of the call. A double provider registration is harmless - @opentelemetry/api rejects a second
203
289
  * global registration and keeps the first.
204
290
  */
205
291
  async function runEdgeRegisterPass(file: string, config: ResolvedConfig): Promise<void> {
292
+ await withEdgeRuntime(async () => {
293
+ const edgeInstance = await importInstrumentation(file, config, '.edge')
294
+ await edgeInstance.register?.()
295
+ }, 'instrumentation register() (edge pass) failed:')
296
+ }
297
+
298
+ /** Run `task` with NEXT_RUNTIME='edge', restoring the previous value. */
299
+ async function withEdgeRuntime(task: () => Promise<void>, failure: string): Promise<void> {
206
300
  // eslint-disable-next-line turbo/no-undeclared-env-vars
207
301
  const previous = process.env.NEXT_RUNTIME
208
302
  // eslint-disable-next-line turbo/no-undeclared-env-vars
209
303
  process.env.NEXT_RUNTIME = 'edge'
210
304
  try {
211
- const edgeInstance = await importInstrumentation(file, config, '.edge')
212
- await edgeInstance.register?.()
305
+ await task()
213
306
  } catch (error) {
214
- console.error('instrumentation register() (edge pass) failed:', error)
307
+ console.error(failure, error)
215
308
  } finally {
216
309
  if (previous === undefined) {
217
310
  // eslint-disable-next-line turbo/no-undeclared-env-vars