@wular/pnext 0.1.5 → 0.1.7

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 (302) hide show
  1. package/bin/pnext +9 -2
  2. package/dist/server/_chunks/aliases-j47a10ce.js +44 -0
  3. package/dist/server/_chunks/analyze-arswqe6s.js +445 -0
  4. package/dist/server/_chunks/boot-af0mv3dy.js +36 -0
  5. package/dist/server/_chunks/build-spy3dxp4.js +84 -0
  6. package/dist/server/_chunks/build-t9h0hec3.js +3550 -0
  7. package/dist/server/_chunks/build-tier-m7yvqz61.js +2472 -0
  8. package/dist/server/_chunks/compile-53j02j27.js +22 -0
  9. package/dist/server/_chunks/config-1yzwexq6.js +27 -0
  10. package/dist/server/_chunks/config-n2z7yxps.js +22 -0
  11. package/dist/server/_chunks/create-meec9hs6.js +134 -0
  12. package/dist/server/_chunks/dev-fjmqg6en.js +220 -0
  13. package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
  14. package/dist/server/_chunks/generate-n9sh6hzt.js +21 -0
  15. package/dist/server/_chunks/index-0374j2b9.js +66 -0
  16. package/dist/server/_chunks/index-06nyqntx.js +213 -0
  17. package/dist/server/_chunks/index-1011cscr.js +5263 -0
  18. package/dist/server/_chunks/index-11sfk4hb.js +170 -0
  19. package/dist/server/_chunks/index-20ppawv7.js +297 -0
  20. package/dist/server/_chunks/index-2dv4y4wd.js +1065 -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-357na4a8.js +109 -0
  24. package/dist/server/_chunks/index-3b3at80h.js +109 -0
  25. package/dist/server/_chunks/index-3tf1xjsp.js +6166 -0
  26. package/dist/server/_chunks/index-3xbr4eyv.js +105 -0
  27. package/dist/server/_chunks/index-4399bvd2.js +18 -0
  28. package/dist/server/_chunks/index-4hgjrqh7.js +851 -0
  29. package/dist/server/_chunks/index-4q2gbfwk.js +2530 -0
  30. package/dist/server/_chunks/index-4t92hdxd.js +172 -0
  31. package/dist/server/_chunks/index-4zzebszd.js +10 -0
  32. package/dist/server/_chunks/index-5gyr4emz.js +107 -0
  33. package/dist/server/_chunks/index-5hr4062s.js +70 -0
  34. package/dist/server/_chunks/index-68m3mwbd.js +84 -0
  35. package/dist/server/_chunks/index-6b009mt0.js +56 -0
  36. package/dist/server/_chunks/index-6e4fqv30.js +588 -0
  37. package/dist/server/_chunks/index-6gex3pfa.js +15 -0
  38. package/dist/server/_chunks/index-6kp9r0d9.js +11 -0
  39. package/dist/server/_chunks/index-6q7zvrap.js +134 -0
  40. package/dist/server/_chunks/index-73z4esab.js +38 -0
  41. package/dist/server/_chunks/index-79ngvam8.js +60 -0
  42. package/dist/server/_chunks/index-7wc9n8be.js +25 -0
  43. package/dist/server/_chunks/index-8k7pfejn.js +1599 -0
  44. package/dist/server/_chunks/index-8kcxdfya.js +206 -0
  45. package/dist/server/_chunks/index-91x12ykq.js +274 -0
  46. package/dist/server/_chunks/index-9ch50vtq.js +18 -0
  47. package/dist/server/_chunks/index-9hx2641q.js +28 -0
  48. package/dist/server/_chunks/index-9n2914ww.js +535 -0
  49. package/dist/server/_chunks/index-9ych9ma8.js +207 -0
  50. package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
  51. package/dist/server/_chunks/index-bcws5eh2.js +35 -0
  52. package/dist/server/_chunks/index-c0g2wf3z.js +92 -0
  53. package/dist/server/_chunks/index-cf4rvxv6.js +95 -0
  54. package/dist/server/_chunks/index-cjgqs13g.js +65 -0
  55. package/dist/server/_chunks/index-ckkdpht5.js +41 -0
  56. package/dist/server/_chunks/index-cyfednwb.js +87 -0
  57. package/dist/server/_chunks/index-danncfhy.js +311 -0
  58. package/dist/server/_chunks/index-dg0f05zs.js +199 -0
  59. package/dist/server/_chunks/index-e3a6yx84.js +18 -0
  60. package/dist/server/_chunks/index-e88tyv35.js +45 -0
  61. package/dist/server/_chunks/index-eajaspjk.js +1253 -0
  62. package/dist/server/_chunks/index-eeaanp2e.js +483 -0
  63. package/dist/server/_chunks/index-egykcqg3.js +101 -0
  64. package/dist/server/_chunks/index-en7n9f7m.js +22 -0
  65. package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
  66. package/dist/server/_chunks/index-fkp000p1.js +590 -0
  67. package/dist/server/_chunks/index-fn1chkkd.js +214 -0
  68. package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
  69. package/dist/server/_chunks/index-ggk2j965.js +9432 -0
  70. package/dist/server/_chunks/index-grta8ap3.js +238 -0
  71. package/dist/server/_chunks/index-h2a70tg4.js +7 -0
  72. package/dist/server/_chunks/index-h4xy648y.js +51 -0
  73. package/dist/server/_chunks/index-hkdfj58q.js +92 -0
  74. package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
  75. package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
  76. package/dist/server/_chunks/index-jepjvcrf.js +198 -0
  77. package/dist/server/_chunks/index-jks0x0a1.js +14 -0
  78. package/dist/server/_chunks/index-jx7bqvw1.js +57 -0
  79. package/dist/server/_chunks/index-k1g3epw0.js +1299 -0
  80. package/dist/server/_chunks/index-knp0s912.js +7 -0
  81. package/dist/server/_chunks/index-kq8kpzye.js +472 -0
  82. package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
  83. package/dist/server/_chunks/index-kresp2n8.js +445 -0
  84. package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
  85. package/dist/server/_chunks/index-n4hxpng2.js +11 -0
  86. package/dist/server/_chunks/index-ncnes8c6.js +17 -0
  87. package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
  88. package/dist/server/_chunks/index-nyrba2kr.js +4130 -0
  89. package/dist/server/_chunks/index-p77b4kcx.js +296 -0
  90. package/dist/server/_chunks/index-pym1cezq.js +39 -0
  91. package/dist/server/_chunks/index-q738kkhj.js +100 -0
  92. package/dist/server/_chunks/index-qjj6gddn.js +54 -0
  93. package/dist/server/_chunks/index-qm6kmhqs.js +110 -0
  94. package/dist/server/_chunks/index-qn4z5s3c.js +819 -0
  95. package/dist/server/_chunks/index-qpwsgt3a.js +158 -0
  96. package/dist/server/_chunks/index-r97ptz1q.js +97 -0
  97. package/dist/server/_chunks/index-ras0sdab.js +26 -0
  98. package/dist/server/_chunks/index-rcs322t1.js +19 -0
  99. package/dist/server/_chunks/index-rn8dyr1f.js +126 -0
  100. package/dist/server/_chunks/index-rt9qq1va.js +4 -0
  101. package/dist/server/_chunks/index-rthrsed5.js +356 -0
  102. package/dist/server/_chunks/index-sctcgyvv.js +185 -0
  103. package/dist/server/_chunks/index-sjpckxqn.js +213 -0
  104. package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
  105. package/dist/server/_chunks/index-stbk3jhn.js +31 -0
  106. package/dist/server/_chunks/index-sv8m6789.js +266 -0
  107. package/dist/server/_chunks/index-tgcakdxc.js +125 -0
  108. package/dist/server/_chunks/index-tgsfjmep.js +74 -0
  109. package/dist/server/_chunks/index-tq0pc7eg.js +154 -0
  110. package/dist/server/_chunks/index-tq1f96v7.js +51 -0
  111. package/dist/server/_chunks/index-tqn2qnnn.js +241 -0
  112. package/dist/server/_chunks/index-tvqkq6e6.js +128 -0
  113. package/dist/server/_chunks/index-v1ags1na.js +132 -0
  114. package/dist/server/_chunks/index-vvmmknm1.js +1431 -0
  115. package/dist/server/_chunks/index-vvq7rqtn.js +329 -0
  116. package/dist/server/_chunks/index-vz0km5qk.js +33 -0
  117. package/dist/server/_chunks/index-w6h8m6vp.js +138 -0
  118. package/dist/server/_chunks/index-web361b8.js +138 -0
  119. package/dist/server/_chunks/index-wpfhfbs4.js +194 -0
  120. package/dist/server/_chunks/index-x0y005nh.js +283 -0
  121. package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
  122. package/dist/server/_chunks/index-x93hv0sc.js +271 -0
  123. package/dist/server/_chunks/index-xvj80c3a.js +30 -0
  124. package/dist/server/_chunks/index-xywjg2vc.js +100 -0
  125. package/dist/server/_chunks/index-y2jr2n72.js +152 -0
  126. package/dist/server/_chunks/index-y36a0ybw.js +57 -0
  127. package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
  128. package/dist/server/_chunks/index-yhxkxghe.js +56 -0
  129. package/dist/server/_chunks/index-ym67zqxp.js +130 -0
  130. package/dist/server/_chunks/index-ytjvpbm2.js +736 -0
  131. package/dist/server/_chunks/index-yz54pxrh.js +960 -0
  132. package/dist/server/_chunks/index-z4z9zv3b.js +733 -0
  133. package/dist/server/_chunks/index-z84ab1w0.js +1270 -0
  134. package/dist/server/_chunks/index-z88qv16t.js +11 -0
  135. package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
  136. package/dist/server/_chunks/loader-7yw0j98z.js +78 -0
  137. package/dist/server/_chunks/modules-jn8bmbn8.js +74 -0
  138. package/dist/server/_chunks/pipeline-yy9jhydf.js +832 -0
  139. package/dist/server/_chunks/production-jar9ztm3.js +44 -0
  140. package/dist/server/_chunks/proxy-config-j84dgksn.js +21 -0
  141. package/dist/server/_chunks/proxy-w1d0wmsn.js +33 -0
  142. package/dist/server/_chunks/router-k1reahfn.js +498 -0
  143. package/dist/server/_chunks/routes-2s277fc6.js +64 -0
  144. package/dist/server/_chunks/run-pvd8bs1t.js +696 -0
  145. package/dist/server/_chunks/server-mt4a994c.js +1835 -0
  146. package/dist/server/_chunks/tsconfig-defaults-1nb3308g.js +20 -0
  147. package/dist/server/_chunks/typegen-0qr0459b.js +35 -0
  148. package/dist/server/api/client-cache.js +14 -0
  149. package/dist/server/cli/index.js +378 -0
  150. package/dist/server/cli/start.js +137 -0
  151. package/dist/server/client/reference.js +16 -0
  152. package/dist/server/compat/next/cache.js +56 -0
  153. package/dist/server/compat/next/canonical-url.js +13 -0
  154. package/dist/server/compat/next/client-cache.js +57 -0
  155. package/dist/server/compat/next/client-navigation.js +232 -0
  156. package/dist/server/compat/next/client-only.js +1 -0
  157. package/dist/server/compat/next/client-script.js +178 -0
  158. package/dist/server/compat/next/client-server.js +34 -0
  159. package/dist/server/compat/next/config-headers.js +32 -0
  160. package/dist/server/compat/next/config-loader.js +25 -0
  161. package/dist/server/compat/next/config.js +10 -0
  162. package/dist/server/compat/next/constants.js +18 -0
  163. package/dist/server/compat/next/custom-server.js +10 -0
  164. package/dist/server/compat/next/dist/client/components/app-router-headers.js +48 -0
  165. package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +27 -0
  166. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +25 -0
  167. package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +25 -0
  168. package/dist/server/compat/next/dynamic.js +32 -0
  169. package/dist/server/compat/next/error.js +112 -0
  170. package/dist/server/compat/next/font/cache.js +14 -0
  171. package/dist/server/compat/next/font/google.js +3 -0
  172. package/dist/server/compat/next/font/index.js +1 -0
  173. package/dist/server/compat/next/font/local.js +22 -0
  174. package/dist/server/compat/next/font/runtime-client.js +33 -0
  175. package/dist/server/compat/next/font/runtime.js +41 -0
  176. package/dist/server/compat/next/font/shared.js +22 -0
  177. package/dist/server/compat/next/form.js +129 -0
  178. package/dist/server/compat/next/head.js +10 -0
  179. package/dist/server/compat/next/headers.js +30 -0
  180. package/dist/server/compat/next/image/client.js +11 -0
  181. package/dist/server/compat/next/image/config.js +18 -0
  182. package/dist/server/compat/next/image/optimizer.js +25 -0
  183. package/dist/server/compat/next/image/patterns.js +12 -0
  184. package/dist/server/compat/next/image/props.js +16 -0
  185. package/dist/server/compat/next/image/shared.js +86 -0
  186. package/dist/server/compat/next/image/static-metadata.js +32 -0
  187. package/dist/server/compat/next/image/validate.js +8 -0
  188. package/dist/server/compat/next/image.js +59 -0
  189. package/dist/server/compat/next/index.js +1 -0
  190. package/dist/server/compat/next/legacy-image.js +78 -0
  191. package/dist/server/compat/next/legacy-request-apis.js +24 -0
  192. package/dist/server/compat/next/link-transform.js +14 -0
  193. package/dist/server/compat/next/link.js +14 -0
  194. package/dist/server/compat/next/navigation.js +190 -0
  195. package/dist/server/compat/next/offline.js +45 -0
  196. package/dist/server/compat/next/og.js +8 -0
  197. package/dist/server/compat/next/preferred-region.js +8 -0
  198. package/dist/server/compat/next/redirects.js +30 -0
  199. package/dist/server/compat/next/resource-hints.js +30 -0
  200. package/dist/server/compat/next/rewrites.js +41 -0
  201. package/dist/server/compat/next/root-params.js +121 -0
  202. package/dist/server/compat/next/route-state.js +23 -0
  203. package/dist/server/compat/next/router.js +103 -0
  204. package/dist/server/compat/next/script.js +37 -0
  205. package/dist/server/compat/next/server-only.js +1 -0
  206. package/dist/server/compat/next/server.js +131 -0
  207. package/dist/server/compat/next/svgr.js +14 -0
  208. package/dist/server/compat/next/telemetry.js +10 -0
  209. package/dist/server/compat/next/usage.js +13 -0
  210. package/dist/server/compat/next/user-agent.js +12 -0
  211. package/dist/server/compat/next/web-vitals.js +35 -0
  212. package/dist/server/compat/react/action-state.js +11 -0
  213. package/dist/server/compat/react/client-lite.js +83 -0
  214. package/dist/server/compat/react/client.js +131 -0
  215. package/dist/server/compat/react/compiler-runtime.js +42 -0
  216. package/dist/server/compat/react/dom-client.js +106 -0
  217. package/dist/server/compat/react/dom-react-server.js +37 -0
  218. package/dist/server/compat/react/dom-server.js +40 -0
  219. package/dist/server/compat/react/dom.js +52 -0
  220. package/dist/server/compat/react/hooks-extra.js +12 -0
  221. package/dist/server/compat/react/internals.js +14 -0
  222. package/dist/server/compat/react/parity.js +11 -0
  223. package/dist/server/compat/react/preact.js +113 -0
  224. package/dist/server/compat/react/react-server.js +133 -0
  225. package/dist/server/compat/react/router-shim.js +8 -0
  226. package/dist/server/compat/react/server-component-use.js +14 -0
  227. package/dist/server/compat/react/server-inserted-html.js +14 -0
  228. package/dist/server/compat/react/server.js +194 -0
  229. package/dist/server/compat/react/style-values.js +10 -0
  230. package/dist/server/compat/react/use.js +10 -0
  231. package/dist/server/compat/react/view-transition.js +12 -0
  232. package/dist/server/generation.json +1 -0
  233. package/package.json +4 -1
  234. package/src/api/dynamic.tsx +2 -0
  235. package/src/api/server.ts +24 -6
  236. package/src/cli/adapters/vercel-warm.ts +315 -61
  237. package/src/cli/adapters/vercel.ts +348 -71
  238. package/src/cli/build.ts +58 -33
  239. package/src/cli/index.ts +23 -10
  240. package/src/cli/serve/entry.ts +21 -159
  241. package/src/cli/serve/immutable.ts +31 -0
  242. package/src/cli/serve/pipeline.ts +82 -84
  243. package/src/cli/start.ts +3 -1
  244. package/src/client/chunk-fold.ts +19 -6
  245. package/src/client/react-tier.ts +3 -2
  246. package/src/client/reference-stub.ts +21 -2
  247. package/src/compat/actions/client-plugin.ts +4 -2
  248. package/src/compat/actions/config.ts +6 -5
  249. package/src/compat/actions/extensions.ts +2 -3
  250. package/src/compat/aliases.ts +8 -1
  251. package/src/compat/bundler/config.ts +88 -0
  252. package/src/compat/bundler/optimize-package-imports.ts +86 -26
  253. package/src/compat/bundler/relay-transform.ts +15 -3
  254. package/src/compat/cache-control.ts +16 -8
  255. package/src/compat/css/modules.ts +18 -5
  256. package/src/compat/lifecycle/error-funnel.ts +1 -1
  257. package/src/compat/lifecycle/instrumentation.ts +117 -24
  258. package/src/compat/lifecycle/testmode.ts +3 -2
  259. package/src/compat/next/config-loader.ts +137 -13
  260. package/src/compat/next/font/runtime.ts +22 -2
  261. package/src/compat/next/og.ts +3 -3
  262. package/src/compat/next/usage.ts +3 -2
  263. package/src/compat/pages/api.ts +83 -14
  264. package/src/compat/pages/router.ts +30 -14
  265. package/src/compat/register/bundler.ts +3 -1
  266. package/src/compat/register/middleware.ts +1 -1
  267. package/src/compat/register/otel.ts +5 -9
  268. package/src/compat/register/render.ts +3 -1
  269. package/src/compat/register/use-cache.ts +3 -1
  270. package/src/compat/release.ts +46 -0
  271. package/src/compat/segment/serve.ts +1 -1
  272. package/src/compat/validation/validate.ts +10 -7
  273. package/src/config.ts +51 -7
  274. package/src/css/assets.ts +213 -0
  275. package/src/css/build.ts +174 -238
  276. package/src/dev/server.ts +27 -8
  277. package/src/extensions.ts +4 -0
  278. package/src/render/hooks.ts +13 -0
  279. package/src/render/renderer.ts +52 -32
  280. package/src/request/context.ts +9 -0
  281. package/src/resolve/dynamic.ts +3 -1
  282. package/src/resolve/imports.ts +12 -52
  283. package/src/resolve/package-json.ts +56 -0
  284. package/src/resolve/scan-facts.ts +65 -0
  285. package/src/routing/href.ts +10 -3
  286. package/src/routing/match.ts +287 -0
  287. package/src/routing/metadata-files.ts +19 -5
  288. package/src/routing/proxy-config.ts +137 -0
  289. package/src/routing/proxy.ts +35 -134
  290. package/src/routing/routes.ts +22 -281
  291. package/src/runtime/fingerprint.ts +23 -8
  292. package/src/runtime/load.ts +170 -0
  293. package/src/runtime/loader.ts +215 -43
  294. package/src/runtime/module-cache.ts +13 -0
  295. package/src/runtime/modules.ts +370 -81
  296. package/src/runtime/prebundle.ts +81 -0
  297. package/src/runtime/production.ts +391 -0
  298. package/src/runtime/release-graph.ts +334 -0
  299. package/src/runtime/vendor-build.ts +724 -13
  300. package/src/runtime/vendor.ts +16 -3
  301. package/src/types.ts +2 -0
  302. package/src/utils/fs.ts +89 -3
@@ -3,7 +3,6 @@ import { stat, unlink, writeFile } from 'node:fs/promises'
3
3
  import { basename, dirname, join, relative, sep } from 'node:path'
4
4
  import { createHash } from 'node:crypto'
5
5
  import { AsyncLocalStorage } from 'node:async_hooks'
6
- import { drainPreplanBuilds } from '../runtime/vendor'
7
6
  import {
8
7
  Component,
9
8
  Fragment,
@@ -29,10 +28,13 @@ import { markErrorLogged } from '../utils/error-log'
29
28
  import {
30
29
  devClientModuleHref,
31
30
  devServerModuleHref,
31
+ drainPreplanBuilds,
32
32
  importDevModule,
33
33
  importModuleOnce,
34
- } from '../runtime/modules'
35
- import { serverBundleTargetForRuntime } from '../runtime/loader'
34
+ registerServerRuntime,
35
+ serverBundleTargetForRuntime,
36
+ } from '../runtime/load'
37
+ import { conventionFileExists, productionRelease, servedConventions } from '../runtime/production'
36
38
  import {
37
39
  getActionModuleExtensions,
38
40
  getActiveMetadataExtensions,
@@ -63,7 +65,7 @@ import {
63
65
  routeCssAssetNames,
64
66
  routeCssHref,
65
67
  withAssetPrefix,
66
- } from '../css/build'
68
+ } from '../css/assets'
67
69
  import { dynamicReferenceSymbol, type DynamicReference } from '../api/dynamic'
68
70
  import {
69
71
  isForbiddenError,
@@ -98,13 +100,11 @@ import {
98
100
  trackPrerenderDynamic,
99
101
  } from './ppr'
100
102
  import {
101
- collectFileCss,
102
- collectNotFoundCss,
103
103
  findConventionFiles,
104
104
  findGlobalError,
105
105
  findLayouts,
106
106
  serverActionsUnsupportedMessage,
107
- } from '../routing/routes'
107
+ } from '../routing/match'
108
108
  import { matchSegments, pathnameSegments, slotDirectoriesIn } from '../routing/slots'
109
109
  import { getRequestRuntime } from '../routing/request-environment'
110
110
  import { toNextRequest } from '../api/server'
@@ -121,7 +121,6 @@ import {
121
121
  ISLAND_BOUNDARY_ERROR_MESSAGE_ATTRIBUTE,
122
122
  } from './boundary-error'
123
123
  import { captureCacheScope, runWithCacheScope } from '../request/cache'
124
- import { registerServerRuntime } from '../runtime/loader'
125
124
  import { readText } from '../utils/fs'
126
125
  import { traceEnabled } from '../utils/trace-flags'
127
126
  import {
@@ -804,7 +803,7 @@ function moduleHref(file: string, options: ModuleLoadOptions) {
804
803
  ? devServerModuleHref(options.config, file, options.devImportVersion ?? String(Date.now()), {
805
804
  conditionTarget,
806
805
  })
807
- : reactCompatEnabled(options.config)
806
+ : reactCompatEnabled(options.config) || productionRelease(options.config)
808
807
  ? devServerModuleHref(options.config, file, 'build', { conditionTarget })
809
808
  : pathToFileHref(file)
810
809
  }
@@ -851,6 +850,17 @@ async function importClientModule(file: string, options: ModuleLoadOptions) {
851
850
  return importModuleOnce<Record<string, unknown>>(href)
852
851
  }
853
852
 
853
+ /** A synthetic route's CSS: in a release, the stylesheet its build emitted; else a source scan. */
854
+ async function syntheticRouteCss(
855
+ config: ResolvedConfig,
856
+ id: string,
857
+ scan: (routes: typeof import('../routing/routes')) => Promise<string[]>,
858
+ ) {
859
+ if (!productionRelease(config)) return scan(await import('../routing/routes'))
860
+ const name = `${id}.css`
861
+ return emittedAssetName(config, name) === name ? [] : [name]
862
+ }
863
+
854
864
  function clientScriptPath(route: RouteManifestEntry, dev?: boolean) {
855
865
  return dev
856
866
  ? `/__pnext/client/${route.id}.js`
@@ -1530,7 +1540,7 @@ async function serializablePageProps(props: PageProps, omitSearchParams = false)
1530
1540
 
1531
1541
  export async function staticParamsFor(config: ResolvedConfig, route: RouteManifestEntry) {
1532
1542
  const moduleFiles = [
1533
- ...findLayouts(config.appPath, route.file).filter(file => existsSync(file)),
1543
+ ...findLayouts(config.appPath, route.file).filter(conventionFileExists),
1534
1544
  route.file,
1535
1545
  ]
1536
1546
  let paramSets: Record<string, RouteParamValue>[] = [{}]
@@ -1615,7 +1625,7 @@ export async function renderPage(options: RenderOptions) {
1615
1625
  export async function renderPageWithStatus(
1616
1626
  options: RenderOptions,
1617
1627
  ): Promise<{ html: string; status: number; location?: string }> {
1618
- registerServerRuntime(options.config, options.route.sourceFiles)
1628
+ await registerServerRuntime(options.config, options.route.sourceFiles)
1619
1629
  await seedPrerenderSidecar(options)
1620
1630
  const request = renderScopeRequest(options)
1621
1631
  return runWithRequest(
@@ -1648,7 +1658,7 @@ export async function renderPageWithStatus(
1648
1658
  }
1649
1659
 
1650
1660
  export async function renderPageStream(options: RenderOptions) {
1651
- registerServerRuntime(options.config, options.route.sourceFiles)
1661
+ await registerServerRuntime(options.config, options.route.sourceFiles)
1652
1662
  await seedPrerenderSidecar(options)
1653
1663
 
1654
1664
  const request = renderScopeRequest(options)
@@ -1666,7 +1676,7 @@ export async function renderPageStream(options: RenderOptions) {
1666
1676
  }
1667
1677
 
1668
1678
  export async function renderPageResponse(options: RenderOptions) {
1669
- registerServerRuntime(options.config, options.route.sourceFiles)
1679
+ await registerServerRuntime(options.config, options.route.sourceFiles)
1670
1680
  const method = options.request?.method.toUpperCase() ?? 'GET'
1671
1681
  if (method !== 'GET' && method !== 'HEAD') {
1672
1682
  return new Response('Method Not Allowed', {
@@ -1871,7 +1881,7 @@ const rejectedPprShellUpgrades = new Set<string>()
1871
1881
  * when dynamic data escapes every boundary - the route must then be served fully dynamically.
1872
1882
  */
1873
1883
  export async function renderPartialShell(options: RenderOptions): Promise<PrebuiltShell | null> {
1874
- registerServerRuntime(options.config, options.route.sourceFiles)
1884
+ await registerServerRuntime(options.config, options.route.sourceFiles)
1875
1885
  const sidecar = getRenderExtensions().prerenderSidecar
1876
1886
  const sidecarContext = prerenderSidecarContext(options)
1877
1887
  await sidecar.begin(sidecarContext)
@@ -1960,7 +1970,7 @@ export async function renderPartialShell(options: RenderOptions): Promise<Prebui
1960
1970
  export async function renderRuntimePrefetchDocument(
1961
1971
  options: RenderOptions,
1962
1972
  ): Promise<{ html: string; postponed: boolean } | null> {
1963
- registerServerRuntime(options.config, options.route.sourceFiles)
1973
+ await registerServerRuntime(options.config, options.route.sourceFiles)
1964
1974
  // Seed (read-only) the resume-data sidecar so 'use cache' scopes resolve
1965
1975
  // their build-time fills; unlike renderPartialShell nothing is collected or
1966
1976
  // persisted back.
@@ -2512,7 +2522,7 @@ function hasParamFreeRootLayout(config: ResolvedConfig): boolean {
2512
2522
  const cached = paramFreeRootLayoutCache.get(config.appPath)
2513
2523
  if (cached !== undefined) return cached
2514
2524
  const found = ['tsx', 'jsx', 'ts', 'js'].some(ext =>
2515
- existsSync(join(config.appPath, `layout.${ext}`)),
2525
+ conventionFileExists(join(config.appPath, `layout.${ext}`)),
2516
2526
  )
2517
2527
  paramFreeRootLayoutCache.set(config.appPath, found)
2518
2528
  return found
@@ -2636,7 +2646,7 @@ export async function renderGlobalNotFoundResponse(
2636
2646
  ) {
2637
2647
  const globalFile = ['tsx', 'ts', 'jsx', 'js']
2638
2648
  .map(ext => `${options.config.appPath}/global-not-found.${ext}`)
2639
- .find(candidate => existsSync(candidate))
2649
+ .find(conventionFileExists)
2640
2650
  // Render as GET regardless of the original method: the 404 page is what a
2641
2651
  // browser should see for a stray POST too (Next's MPA fallback), not a 405.
2642
2652
  const notFoundRequest = options.request
@@ -2664,9 +2674,9 @@ export async function renderGlobalNotFoundResponse(
2664
2674
  // Include CSS shared with the root layout: this document replaces the
2665
2675
  // whole page (no root layout render), so the global sheet isn't linked
2666
2676
  // and anything it covered must ship in this route's own chunk.
2667
- cssImports: await collectFileCss(options.config.appPath, [globalFile], {
2668
- includeGlobalCss: true,
2669
- }),
2677
+ cssImports: await syntheticRouteCss(options.config, 'global-not-found', routes =>
2678
+ routes.collectFileCss(options.config.appPath, [globalFile], { includeGlobalCss: true }),
2679
+ ),
2670
2680
  sourceFiles: [globalFile],
2671
2681
  synthetic: true,
2672
2682
  }
@@ -2706,7 +2716,9 @@ export async function renderGlobalNotFoundResponse(
2706
2716
  clientReferences: rootRoute?.clientReferences ?? [],
2707
2717
  // Root not-found ships its own CSS (`/assets/not-found.css`) alongside the
2708
2718
  // root-layout global sheet (`/assets/global.css`, emitted via globalCssHref).
2709
- cssImports: await collectNotFoundCss(options.config.appPath),
2719
+ cssImports: await syntheticRouteCss(options.config, 'not-found', routes =>
2720
+ routes.collectNotFoundCss(options.config.appPath),
2721
+ ),
2710
2722
  sourceFiles: rootRoute?.sourceFiles ?? [anchor],
2711
2723
  ...(rootRoute?.needsRouterEntry ? { needsRouterEntry: true } : {}),
2712
2724
  // Serve the ROOT route's built client entry: the synthetic id has no
@@ -2863,6 +2875,8 @@ const documentLayoutDefaults = new Map<string, { mtimeMs: number; hasDefault: bo
2863
2875
  async function routeHasDocumentLayout(layoutFiles: string[]) {
2864
2876
  const rootLayout = layoutFiles[0]
2865
2877
  if (!rootLayout) return false
2878
+ const served = servedConventions()
2879
+ if (served) return served.documentLayouts.has(rootLayout)
2866
2880
  let mtimeMs: number
2867
2881
  try {
2868
2882
  mtimeMs = statSync(rootLayout).mtimeMs
@@ -2923,7 +2937,7 @@ async function pageRender(options: RenderOptions, stream: boolean): Promise<Page
2923
2937
  // Drop the non-existent anchor file so we don't try to import it; the
2924
2938
  // layout's `children` is supplied by the `@children` slot instead (see
2925
2939
  // childrenFromSlots).
2926
- if (pageFile && !existsSync(pageFile)) pageFile = undefined
2940
+ if (pageFile && !conventionFileExists(pageFile)) pageFile = undefined
2927
2941
  }
2928
2942
  const props = profileRenderSyncStep(profile, 'create page props', () =>
2929
2943
  createPageProps(options),
@@ -3254,7 +3268,7 @@ async function renderAuthInterruptPage(
3254
3268
  const fallbackTree = httpAccessFallbackTree(status, message)
3255
3269
  const file = nearestConventionFile(options, `${kind}.tsx`)
3256
3270
  const boundaryOptions = optionsForNotFoundBoundary(options, file)
3257
- if (!file || !existsSync(file)) {
3271
+ if (!file || !conventionFileExists(file)) {
3258
3272
  return renderTreeOrFallback(
3259
3273
  boundaryOptions,
3260
3274
  fallbackTree,
@@ -3322,7 +3336,7 @@ async function renderGlobalErrorPage(
3322
3336
  // global-error.js is a client component ('use client'): import it through
3323
3337
  // the client module loader and SSR it to a full document. Server components
3324
3338
  // (no directive) load through the server loader.
3325
- const clientDirective = hasUseClientDirective(await readText(file))
3339
+ const clientDirective = await conventionUsesClient(file)
3326
3340
  const module = (
3327
3341
  clientDirective ? await importClientModule(file, options) : await importModule(file, options)
3328
3342
  ) as ErrorModule
@@ -3956,7 +3970,7 @@ async function renderTreeInFrame(
3956
3970
  // global-not-found replaces the document without the root layout, so the
3957
3971
  // root layout's global sheet must not load (its CSS ships in the route's
3958
3972
  // own chunk, built with includeGlobalCss).
3959
- options.route.id === 'global-not-found' ? undefined : globalCssHref(options.config)
3973
+ options.route.id === 'global-not-found' ? undefined : await globalCssHref(options.config)
3960
3974
  let routeStylesheet = routeCssHref(options.route, options.config)
3961
3975
  // File names, not logical ones: the inline path READS these off disk, so it has
3962
3976
  // to spell them the way the build emitted them (content-hashed in production).
@@ -4378,7 +4392,7 @@ async function buildLoadingBoundaries(
4378
4392
  options.route.file,
4379
4393
  'loading.tsx',
4380
4394
  )) {
4381
- if (!existsSync(loadingFile)) continue
4395
+ if (!conventionFileExists(loadingFile)) continue
4382
4396
  const fallback = await buildLoadingFallback(options, loadingFile)
4383
4397
  if (fallback) fallbackByDir.set(dirname(loadingFile), fallback)
4384
4398
  }
@@ -4485,7 +4499,7 @@ async function renderNotFoundPage(
4485
4499
  // A `'use client'` not-found.js with a discovered client reference renders as an island (same as
4486
4500
  // error.js) so it HYDRATES - its hooks must work on the 404 document. Server-rendering it would
4487
4501
  // serialize child event-handler props as dead action stubs.
4488
- const clientDirective = hasUseClientDirective(await readText(file))
4502
+ const clientDirective = await conventionUsesClient(file)
4489
4503
  const clientReference = clientDirective
4490
4504
  ? conventionDefaultReference(notFoundOptions, file)
4491
4505
  : undefined
@@ -4598,7 +4612,7 @@ async function renderErrorPage(options: RenderOptions, error: unknown, stream: b
4598
4612
  if (!file) return renderGenericErrorPage(options, resolvedError, stream)
4599
4613
 
4600
4614
  try {
4601
- const clientDirective = hasUseClientDirective(await readText(file))
4615
+ const clientDirective = await conventionUsesClient(file)
4602
4616
  const module = (
4603
4617
  clientDirective ? await importClientModule(file, options) : await importModule(file, options)
4604
4618
  ) as ErrorModule
@@ -4867,10 +4881,16 @@ function generatedFallbackPage(
4867
4881
  }
4868
4882
  }
4869
4883
 
4884
+ /** Whether a convention file is `'use client'`: the frozen fact in production, its source otherwise. */
4885
+ async function conventionUsesClient(file: string) {
4886
+ const served = servedConventions()
4887
+ return served ? served.client.has(file) : hasUseClientDirective(await readText(file))
4888
+ }
4889
+
4870
4890
  function nearestConventionFile(options: RenderOptions, name: string) {
4871
4891
  return [...findConventionFiles(options.config.appPath, options.route.file, name)]
4872
4892
  .reverse()
4873
- .find(file => existsSync(file))
4893
+ .find(conventionFileExists)
4874
4894
  }
4875
4895
 
4876
4896
  /**
@@ -6541,7 +6561,7 @@ function dynamicTargetReference(
6541
6561
  const target = dynamicReference.target
6542
6562
  if (!target) return undefined
6543
6563
  const stored = state.clientReferences.get(
6544
- `c-${clientReferenceId(target.file, target.exportName)}`,
6564
+ target.id ?? `c-${clientReferenceId(target.file, target.exportName)}`,
6545
6565
  )
6546
6566
  if (stored) return stored
6547
6567
  for (const reference of state.clientReferences.values()) {
@@ -7616,7 +7636,7 @@ async function applyLayouts(
7616
7636
  await profileRenderStep(profile, 'import layouts', async () => {
7617
7637
  for (const layoutPath of options.layoutFiles ??
7618
7638
  findLayouts(options.config.appPath, options.route.file)) {
7619
- if (!options.moduleLoader && !existsSync(layoutPath)) continue
7639
+ if (!options.moduleLoader && !conventionFileExists(layoutPath)) continue
7620
7640
  const layoutModule = (await importModule(layoutPath, options)) as LayoutModule
7621
7641
  markServerReference(layoutModule.default)
7622
7642
  // Cheap leaf span for the segment-module resolution (nested under the
@@ -8025,7 +8045,7 @@ function fullSegmentPrefetchSkipsSharedLayout(options: RenderOptions): boolean {
8025
8045
  options.config.appPath,
8026
8046
  options.route.file,
8027
8047
  'layout.tsx',
8028
- ).filter(file => existsSync(file))
8048
+ ).filter(conventionFileExists)
8029
8049
  const documentDir = layoutFiles[0] ? dirname(layoutFiles[0]) : undefined
8030
8050
  return layoutFiles
8031
8051
  .slice(1)
@@ -8,6 +8,7 @@ import {
8
8
  PostponeError,
9
9
  } from '../render/ppr'
10
10
  import type { NextRequest, RouteParamValue } from '../types'
11
+ import type { Release } from '../runtime/production'
11
12
 
12
13
  // Request work-unit store (CORE seam for compat after()/onRequestError).
13
14
  //
@@ -46,6 +47,8 @@ export interface WorkUnit {
46
47
  * grows no dependency on compat.
47
48
  */
48
49
  compat?: Record<symbol, unknown>
50
+ /** The production release this request serves from; dev and build leave it unset. */
51
+ release?: Release
49
52
  }
50
53
 
51
54
  // Anchored on globalThis (like requestStorage below) so compiled compat
@@ -67,6 +70,12 @@ export function getWorkUnit(): WorkUnit | undefined {
67
70
  return workUnitStorage.getStore()
68
71
  }
69
72
 
73
+ /** Mark the current work unit as serving `release` (production reads its app facts from it). */
74
+ export function setWorkUnitRelease(release: Release): void {
75
+ const unit = workUnitStorage.getStore()
76
+ if (unit) unit.release = release
77
+ }
78
+
70
79
  /** Set the current work-unit phase (compat toggles render/action/handler/...). */
71
80
  export function setPhase(phase: WorkUnitPhase): void {
72
81
  const unit = workUnitStorage.getStore()
@@ -1,3 +1,4 @@
1
+ import { clientReferenceId } from '../client/reference'
1
2
  import { dynamicCallFacts, rewriteFacts, type DynamicCallFact } from './scan-facts'
2
3
 
3
4
  export interface DynamicCall {
@@ -132,7 +133,8 @@ export function rewriteDynamicCallTargets(
132
133
  const args = source.slice(call.open + 1, call.close)
133
134
  const argCount = topLevelArgCount(args)
134
135
  if (argCount < 1 || argCount > 2) continue
135
- const literal = JSON.stringify({ file, exportName: target.exportName })
136
+ const id = `c-${clientReferenceId(file, target.exportName)}`
137
+ const literal = JSON.stringify({ file, exportName: target.exportName, id })
136
138
  const separator = /,\s*$/.test(args) ? '' : ', '
137
139
  edits.push({
138
140
  at: call.close,
@@ -10,6 +10,9 @@ import {
10
10
  tsConfigFileFor,
11
11
  } from './engine'
12
12
  import { onExtensionHostReset } from '../extensions'
13
+ import { findWorkspaceRoot, readPackageJson, type PackageJson } from './package-json'
14
+
15
+ export { findWorkspaceRoot }
13
16
 
14
17
  // `Bun` through globalThis: pnext ships TypeScript source, so app compilers without
15
18
  // bun-types typecheck this file and a bare `Bun` identifier would fail them.
@@ -45,6 +48,8 @@ export interface ExternalPackagePolicy {
45
48
  * transpile.
46
49
  */
47
50
  transpiled?: () => readonly string[]
51
+ /** Kept out of a release's server dependency graph beyond `external`: Next's built-in list. */
52
+ releaseExternal?: (packageName: string) => boolean
48
53
  /** ESM external mode for package exports interop. */
49
54
  esmExternals: () => boolean | 'loose'
50
55
  }
@@ -94,9 +99,14 @@ export function resolveExternalLoadTarget(context: ExternalLoadContext): string
94
99
  // `#imports` resolve from the importer's OWN package, so they are not
95
100
  // shareable across a resolution root; they stay uncached.
96
101
  if (context.specifier.startsWith('#')) return externalLoadResolver(context)
97
- const key = `${context.target}\0${resolutionRootForFile(context.root, context.fromFile)}\0${context.specifier}`
102
+ // A node_modules importer resolves from its own directory, as Node does: a version nested under
103
+ // its package wins over the hoisted one.
104
+ const resolveContext = context.fromFile.includes(`${path.sep}node_modules${path.sep}`)
105
+ ? { ...context, root: path.dirname(context.fromFile) }
106
+ : context
107
+ const key = `${context.target}\0${resolutionRootForFile(resolveContext.root, context.fromFile)}\0${context.specifier}`
98
108
  if (externalLoadTargets.has(key)) return externalLoadTargets.get(key)
99
- const target = externalLoadResolver(context)
109
+ const target = externalLoadResolver(resolveContext)
100
110
  externalLoadTargets.set(key, target)
101
111
  return target
102
112
  }
@@ -143,22 +153,6 @@ export function setTsConfigPath(file: string | undefined): void {
143
153
  clearVendorPackageResolutions()
144
154
  }
145
155
 
146
- interface PackageJson {
147
- name?: string
148
- type?: string
149
- browser?: string
150
- main?: string
151
- module?: string
152
- source?: string
153
- exports?: PackageExport
154
- imports?: Record<string, PackageExport>
155
- dependencies?: Record<string, string>
156
- devDependencies?: Record<string, string>
157
- optionalDependencies?: Record<string, string>
158
- peerDependencies?: Record<string, string>
159
- workspaces?: string[] | { packages?: string[] }
160
- }
161
-
162
156
  // Compat (or any bundler extension) registers its bare-specifier to file aliases here (e.g. `next/form` ->
163
157
  // `src/compat/next/form.tsx`). Core's own route scanner has no static knowledge of those aliases otherwise,
164
158
  // so a server component importing a 'use client' compat module would never be walked into and its client
@@ -886,25 +880,6 @@ export function commonJsModuleHasDefaultExport(source: string, file: string) {
886
880
  )
887
881
  }
888
882
 
889
- export function findWorkspaceRoot(root: string) {
890
- const key = path.resolve(root)
891
- if (workspaceRootCache.has(key)) return workspaceRootCache.get(key)
892
-
893
- let dir = key
894
- while (true) {
895
- if (readPackageJson(dir).workspaces || existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
896
- workspaceRootCache.set(key, dir)
897
- return dir
898
- }
899
- const parent = path.dirname(dir)
900
- if (parent === dir) {
901
- workspaceRootCache.set(key, undefined)
902
- return undefined
903
- }
904
- dir = parent
905
- }
906
- }
907
-
908
883
  function isInside(root: string, file: string) {
909
884
  const relative = path.relative(root, file)
910
885
  return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
@@ -962,8 +937,6 @@ function readPnpmWorkspacePatterns(root: string) {
962
937
  }
963
938
 
964
939
  const tsConfigCache = new Map<string, TsConfig>()
965
- const packageJsonCache = new Map<string, PackageJson>()
966
- const workspaceRootCache = new Map<string, string | undefined>()
967
940
  const workspacePackageCache = new Map<string, Map<string, string>>()
968
941
 
969
942
  function readTsConfig(root: string) {
@@ -975,14 +948,6 @@ function readTsConfig(root: string) {
975
948
  return config
976
949
  }
977
950
 
978
- function readPackageJson(root: string) {
979
- const existing = packageJsonCache.get(root)
980
- if (existing) return existing
981
- const config = readJson<PackageJson>(path.join(root, 'package.json')) ?? {}
982
- packageJsonCache.set(root, config)
983
- return config
984
- }
985
-
986
951
  function workspacePackages(root: string) {
987
952
  const existing = workspacePackageCache.get(root)
988
953
  if (existing) return existing
@@ -1027,11 +992,6 @@ export function workspacePackageRoots(root: string) {
1027
992
  return [...workspacePackages(path.resolve(root)).values()].sort()
1028
993
  }
1029
994
 
1030
- function readJson<T>(file: string) {
1031
- if (!existsSync(file)) return undefined
1032
- return JSON.parse(readFileSync(file, 'utf8')) as T
1033
- }
1034
-
1035
995
  function readJsonc<T>(file: string) {
1036
996
  if (!existsSync(file)) return undefined
1037
997
  return JSON.parse(stripTrailingCommas(stripJsonComments(readFileSync(file, 'utf8')))) as T
@@ -0,0 +1,56 @@
1
+ // package.json reads and workspace-root discovery, which config loading needs without the resolver.
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import path from 'node:path'
4
+
5
+ type PackageExport = unknown
6
+
7
+ export interface PackageJson {
8
+ name?: string
9
+ type?: string
10
+ browser?: string
11
+ main?: string
12
+ module?: string
13
+ source?: string
14
+ exports?: PackageExport
15
+ imports?: Record<string, PackageExport>
16
+ dependencies?: Record<string, string>
17
+ devDependencies?: Record<string, string>
18
+ optionalDependencies?: Record<string, string>
19
+ peerDependencies?: Record<string, string>
20
+ workspaces?: string[] | { packages?: string[] }
21
+ }
22
+
23
+ const packageJsonCache = new Map<string, PackageJson>()
24
+ const workspaceRootCache = new Map<string, string | undefined>()
25
+
26
+ export function readPackageJson(root: string) {
27
+ const existing = packageJsonCache.get(root)
28
+ if (existing) return existing
29
+ const config = readJson<PackageJson>(path.join(root, 'package.json')) ?? {}
30
+ packageJsonCache.set(root, config)
31
+ return config
32
+ }
33
+
34
+ export function findWorkspaceRoot(root: string) {
35
+ const key = path.resolve(root)
36
+ if (workspaceRootCache.has(key)) return workspaceRootCache.get(key)
37
+
38
+ let dir = key
39
+ while (true) {
40
+ if (readPackageJson(dir).workspaces || existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
41
+ workspaceRootCache.set(key, dir)
42
+ return dir
43
+ }
44
+ const parent = path.dirname(dir)
45
+ if (parent === dir) {
46
+ workspaceRootCache.set(key, undefined)
47
+ return undefined
48
+ }
49
+ dir = parent
50
+ }
51
+ }
52
+
53
+ function readJson<T>(file: string) {
54
+ if (!existsSync(file)) return undefined
55
+ return JSON.parse(readFileSync(file, 'utf8')) as T
56
+ }
@@ -581,6 +581,71 @@ function child(node: AstNode | undefined, key: string): AstNode | undefined {
581
581
  return isNode(value) ? value : undefined
582
582
  }
583
583
 
584
+ /**
585
+ * Drop `type: "json"` attributes from the import declarations and `import()` calls whose target
586
+ * `compiled` claims: the target is JS, which a native load would parse as JSON.
587
+ */
588
+ export function stripJsonImportAttributes(
589
+ source: string,
590
+ file: string,
591
+ compiled: (specifier: string) => boolean,
592
+ ) {
593
+ const result = parseSync(file, source, { lang: 'js', sourceType: 'module' })
594
+ if (result.errors.length > 0) return source
595
+ const edits: { start: number; end: number; value: string }[] = []
596
+ const visit = (node: AstNode) => {
597
+ const target = child(node, 'source')
598
+ const specifier = stringValue(target)
599
+ if (target && specifier !== undefined && compiled(specifier)) {
600
+ const attributes = node.attributes as AstNode[] | undefined
601
+ const options = child(node, 'options')
602
+ if (attributes?.length && attributes.every(jsonTypeAttribute)) {
603
+ const end = source.indexOf('}', attributes.at(-1)!.end) + 1
604
+ edits.push({ start: target.end, end, value: '' })
605
+ } else if (node.type === 'ImportExpression' && options && jsonImportOptions(options)) {
606
+ edits.push({ start: target.end, end: options.end, value: '' })
607
+ }
608
+ }
609
+ for (const value of Object.values(node)) {
610
+ if (isNode(value)) visit(value)
611
+ else if (Array.isArray(value)) for (const item of value) if (isNode(item)) visit(item)
612
+ }
613
+ }
614
+ visit(result.program as unknown as AstNode)
615
+ return edits.length > 0 ? spliceSource(source, edits) : source
616
+ }
617
+
618
+ function propertyName(node: AstNode | undefined) {
619
+ if (!node) return undefined
620
+ return node.type === 'Identifier' ? (node.name as string) : stringValue(node)
621
+ }
622
+
623
+ function jsonTypeAttribute(node: AstNode) {
624
+ return (
625
+ !node.computed &&
626
+ propertyName(child(node, 'key')) === 'type' &&
627
+ stringValue(child(node, 'value')) === 'json'
628
+ )
629
+ }
630
+
631
+ /** `{ with: { type: 'json' } }` (or `assert`) and nothing else. */
632
+ function jsonImportOptions(options: AstNode) {
633
+ const [property, ...rest] = (options.properties as AstNode[] | undefined) ?? []
634
+ const attributes = child(property, 'value')
635
+ const entries = attributes?.properties as AstNode[] | undefined
636
+ return (
637
+ options.type === 'ObjectExpression' &&
638
+ !!property &&
639
+ rest.length === 0 &&
640
+ property.type === 'Property' &&
641
+ !property.computed &&
642
+ ['with', 'assert'].includes(propertyName(child(property, 'key')) ?? '') &&
643
+ attributes?.type === 'ObjectExpression' &&
644
+ !!entries?.length &&
645
+ entries.every(entry => entry.type === 'Property' && jsonTypeAttribute(entry))
646
+ )
647
+ }
648
+
584
649
  function collectDynamicCalls(program: AstNode): DynamicCallFact[] {
585
650
  const calls: DynamicCallFact[] = []
586
651
  const visit = (value: unknown) => {
@@ -198,7 +198,16 @@ export function canonicalTrailingSlashPath(pathname: string, trailingSlash: bool
198
198
  if (/\.[^/]+$/.test(pathname)) return pathname
199
199
  const slashed = pathname.endsWith('/')
200
200
  if (trailingSlash ? slashed : !slashed) return pathname
201
- return trailingSlash ? `${pathname.replace(/\/+$/, '')}/` : pathname.replace(/\/+$/, '') || '/'
201
+ // Collapsed, so a rewritten path can never read as protocol-relative (`//host`).
202
+ const bare = pathname.replace(/\/{2,}/g, '/').replace(/\/+$/, '')
203
+ return trailingSlash ? `${bare}/` : bare || '/'
204
+ }
205
+
206
+ /** Next 308s a path with repeated slashes to its collapsed form, for every method, before anything else. */
207
+ export function repeatedSlashRedirect(url: URL): Response | null {
208
+ if (!url.pathname.includes('//')) return null
209
+ const target = `${url.pathname.replace(/\/{2,}/g, '/')}${url.search}`
210
+ return new Response(target, { status: 308, headers: { location: target } })
202
211
  }
203
212
 
204
213
  /**
@@ -210,13 +219,11 @@ export function canonicalTrailingSlashPath(pathname: string, trailingSlash: bool
210
219
  export function trailingSlashRedirect(
211
220
  config: { trailingSlash?: boolean; skipTrailingSlashRedirect?: boolean },
212
221
  url: URL,
213
- method: string,
214
222
  ): Response | null {
215
223
  // skipTrailingSlashRedirect: the app serves both slashed and unslashed URLs
216
224
  // as-is, so no canonical redirect is ever emitted (route matching already
217
225
  // tolerates a trailing slash via normalizePathname).
218
226
  if (config.skipTrailingSlashRedirect) return null
219
- if (method !== 'GET' && method !== 'HEAD') return null
220
227
  const pathname = url.pathname
221
228
  if (pathname === '/' || pathname === '') return null
222
229
  if (pathname.startsWith('/__pnext') || isBuildAssetPathname(pathname)) return null