@wular/pnext 0.0.3 → 0.0.6

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 (392) hide show
  1. package/README.md +11 -11
  2. package/bin/pnext +1 -1
  3. package/config/lint/base.js +7 -7
  4. package/config/ts/base.json +2 -4
  5. package/config/ts/react.json +2 -6
  6. package/package.json +23 -2
  7. package/reference/compat.md +1 -1
  8. package/reference/config.md +2 -2
  9. package/reference/css.md +13 -9
  10. package/reference/dev.md +1 -1
  11. package/reference/metadata.md +7 -7
  12. package/reference/navigation.md +35 -28
  13. package/reference/performance.md +70 -70
  14. package/reference/rendering.md +17 -17
  15. package/reference/routing.md +17 -17
  16. package/reference/typegen.md +12 -8
  17. package/src/api/cache.ts +36 -37
  18. package/src/api/client-cache.ts +2 -2
  19. package/src/api/client-navigation.ts +113 -109
  20. package/src/api/dynamic.tsx +58 -55
  21. package/src/api/link.tsx +49 -52
  22. package/src/api/navigation.ts +59 -58
  23. package/src/api/server.ts +152 -144
  24. package/src/api/suspense.ts +4 -4
  25. package/src/cli/adapters/vercel-warm.ts +126 -121
  26. package/src/cli/adapters/vercel.ts +437 -443
  27. package/src/cli/analyze.ts +333 -144
  28. package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
  29. package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
  30. package/src/cli/build.ts +976 -978
  31. package/src/cli/create.ts +71 -63
  32. package/src/cli/dev.ts +116 -116
  33. package/src/cli/index.ts +88 -85
  34. package/src/cli/migrate/package-json.ts +73 -71
  35. package/src/cli/migrate/report.ts +32 -33
  36. package/src/cli/migrate/{index.ts → run.ts} +48 -49
  37. package/src/cli/migrate/scan.ts +46 -46
  38. package/src/cli/migrate/spinner.ts +9 -9
  39. package/src/cli/migrate/tsconfig.ts +35 -35
  40. package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
  41. package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
  42. package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
  43. package/src/cli/start.ts +65 -66
  44. package/src/{typegen.ts → cli/typegen.ts} +59 -59
  45. package/src/client/build.ts +794 -768
  46. package/src/client/chunk-fold.ts +245 -240
  47. package/src/client/{paths.ts → chunk-name.ts} +6 -6
  48. package/src/client/entry.ts +162 -147
  49. package/src/client/prebuilt.ts +231 -223
  50. package/src/client/profile.ts +29 -29
  51. package/src/client/react-compiler.ts +20 -15
  52. package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
  53. package/src/client/reference-stub.ts +51 -51
  54. package/src/client/reference.ts +19 -19
  55. package/src/{api → client}/router/events.ts +17 -17
  56. package/src/{api → client}/router/history.ts +16 -16
  57. package/src/{api → client}/router/hub.ts +52 -53
  58. package/src/{api/router.ts → client/router/index.ts} +61 -60
  59. package/src/{api → client}/router/policies.ts +24 -24
  60. package/src/{api → client}/router/runtime.ts +1988 -1961
  61. package/src/{api → client}/router/types.ts +93 -98
  62. package/src/compat/actions/client-plugin.ts +42 -43
  63. package/src/compat/actions/client-stub.ts +14 -14
  64. package/src/compat/actions/{action-client.ts → client.ts} +194 -193
  65. package/src/compat/actions/config.ts +63 -64
  66. package/src/compat/actions/detect.ts +68 -68
  67. package/src/compat/actions/discovery.ts +105 -105
  68. package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
  69. package/src/compat/actions/early-submit.ts +1 -1
  70. package/src/compat/actions/endpoint.ts +198 -198
  71. package/src/compat/actions/extensions.ts +811 -0
  72. package/src/compat/actions/flight.ts +23 -23
  73. package/src/compat/actions/form-state.ts +34 -34
  74. package/src/compat/actions/hoist.ts +382 -236
  75. package/src/compat/actions/ids.ts +7 -12
  76. package/src/compat/actions/index.ts +8 -8
  77. package/src/compat/actions/instances.ts +46 -46
  78. package/src/compat/actions/origin.ts +47 -48
  79. package/src/compat/actions/protocol.ts +22 -22
  80. package/src/compat/actions/registry.ts +19 -19
  81. package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
  82. package/src/compat/actions/rewrite.ts +125 -125
  83. package/src/compat/actions/{action-router.ts → router.ts} +10 -10
  84. package/src/compat/actions/serve.ts +132 -136
  85. package/src/compat/actions/server-tag.ts +4 -4
  86. package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
  87. package/src/compat/actions/unrecognized-error.ts +4 -4
  88. package/src/compat/{index.ts → aliases.ts} +112 -109
  89. package/src/compat/bundler/bun-externals.ts +18 -18
  90. package/src/compat/bundler/cjs-exports.ts +271 -223
  91. package/src/compat/bundler/config.ts +116 -111
  92. package/src/compat/bundler/externals.ts +12 -12
  93. package/src/compat/bundler/import-meta-url.ts +19 -19
  94. package/src/compat/bundler/modularize-imports.ts +36 -33
  95. package/src/compat/bundler/new-url-asset.ts +22 -24
  96. package/src/compat/bundler/optimize-package-imports.ts +111 -107
  97. package/src/compat/bundler/polyfill.ts +28 -28
  98. package/src/compat/bundler/react-compiler.ts +35 -29
  99. package/src/compat/bundler/react-profiler.tsx +11 -11
  100. package/src/compat/bundler/relay-transform.ts +48 -47
  101. package/src/compat/bundler/require-context.ts +119 -113
  102. package/src/compat/bundler/resolve-extensions.ts +19 -19
  103. package/src/compat/bundler/source-cache.ts +25 -25
  104. package/src/compat/bundler/static-imports.ts +7 -7
  105. package/src/compat/bundler/symlink-imports.ts +46 -46
  106. package/src/compat/bundler/tsconfig-paths.ts +13 -15
  107. package/src/compat/bundler/wasm.ts +58 -60
  108. package/src/compat/bundler/webpack-loaders.ts +254 -241
  109. package/src/compat/bundler/worker.ts +101 -104
  110. package/src/compat/cache/build-flags.ts +29 -29
  111. package/src/compat/cache/build-prerender-errors.ts +40 -44
  112. package/src/compat/cache/custom-handler.ts +60 -53
  113. package/src/compat/cache/fetch-patch.ts +240 -240
  114. package/src/compat/cache/handler.ts +27 -31
  115. package/src/compat/cache/modern-handler.ts +148 -126
  116. package/src/compat/cache/resume-data-cache.ts +47 -45
  117. package/src/compat/cache/revalidate.ts +233 -232
  118. package/src/compat/cache/runtime-error.ts +35 -35
  119. package/src/compat/cache/use-cache-transform.ts +442 -417
  120. package/src/compat/cache/use-cache.ts +650 -614
  121. package/src/compat/cache-control.ts +140 -142
  122. package/src/compat/client/base-path.ts +21 -20
  123. package/src/compat/client/css-order.ts +18 -18
  124. package/src/compat/client/errors/bare-boundary.ts +11 -11
  125. package/src/compat/client/errors/control-flow.ts +23 -23
  126. package/src/compat/client/errors/error-boundary.ts +61 -61
  127. package/src/compat/client/errors/global-error.ts +101 -91
  128. package/src/compat/client/errors/install.ts +71 -72
  129. package/src/compat/client/errors/lazy.ts +23 -23
  130. package/src/compat/client/errors/primitive-throw.ts +47 -45
  131. package/src/compat/client/errors/soft-refresh.ts +4 -4
  132. package/src/compat/client/link-status.ts +33 -33
  133. package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
  134. package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
  135. package/src/compat/client/navigation-scroll.ts +63 -63
  136. package/src/compat/client/optimistic-routing.ts +93 -88
  137. package/src/compat/client/prefetch-cache.ts +23 -24
  138. package/src/compat/client/route-announcer.ts +35 -35
  139. package/src/compat/client/segment-cache-policy.ts +20 -20
  140. package/src/compat/client/segment-cache.ts +309 -315
  141. package/src/compat/client/segment-prefetch.ts +127 -132
  142. package/src/compat/client/trailing-slash.ts +5 -4
  143. package/src/compat/css/chunking.ts +113 -116
  144. package/src/compat/css/inline-css.ts +21 -21
  145. package/src/compat/css/lightningcss.ts +37 -38
  146. package/src/compat/css/modules.ts +161 -175
  147. package/src/compat/css/nonce.ts +7 -7
  148. package/src/compat/css/sass-plugin.ts +18 -21
  149. package/src/compat/css/sass.ts +150 -152
  150. package/src/compat/css/styled-jsx-runtime.ts +27 -27
  151. package/src/compat/css/styled-jsx.ts +21 -21
  152. package/src/compat/edge-runtime.ts +27 -27
  153. package/src/compat/{adapter → export}/build-complete.ts +72 -75
  154. package/src/compat/export/client.ts +29 -31
  155. package/src/compat/export/{index.ts → emit.ts} +111 -110
  156. package/src/compat/export/standalone.ts +62 -54
  157. package/src/compat/image-optimizer/cache.ts +51 -49
  158. package/src/compat/image-optimizer/detect.ts +52 -52
  159. package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
  160. package/src/compat/image-optimizer/source.ts +77 -80
  161. package/src/compat/lifecycle/after-scope.ts +26 -26
  162. package/src/compat/lifecycle/after.ts +48 -45
  163. package/src/compat/lifecycle/error-funnel.ts +98 -102
  164. package/src/compat/lifecycle/error-serialize.ts +30 -26
  165. package/src/compat/lifecycle/error-ui.ts +67 -32
  166. package/src/compat/lifecycle/instrumentation-client.ts +36 -38
  167. package/src/compat/lifecycle/instrumentation.ts +85 -85
  168. package/src/compat/lifecycle/node-console.ts +11 -11
  169. package/src/compat/lifecycle/testmode.ts +160 -132
  170. package/src/compat/mdx/compile.ts +60 -57
  171. package/src/compat/mdx/plugin.ts +11 -11
  172. package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
  173. package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
  174. package/src/compat/metadata.ts +75 -81
  175. package/src/compat/next/cache.ts +68 -69
  176. package/src/compat/next/canonical-url.ts +9 -9
  177. package/src/compat/next/client-cache.ts +19 -19
  178. package/src/compat/next/client-navigation.ts +112 -116
  179. package/src/compat/next/client-only.ts +1 -1
  180. package/src/compat/next/client-script.tsx +99 -93
  181. package/src/compat/next/client-server.ts +10 -10
  182. package/src/compat/next/config-loader.ts +176 -173
  183. package/src/compat/next/config.ts +7 -7
  184. package/src/compat/next/constants.cjs +6 -6
  185. package/src/compat/next/constants.ts +6 -6
  186. package/src/compat/next/custom-server.ts +26 -24
  187. package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
  188. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
  189. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
  190. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
  191. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
  192. package/src/compat/next/dynamic.tsx +21 -18
  193. package/src/compat/next/error.tsx +52 -52
  194. package/src/compat/next/font/cache.ts +74 -74
  195. package/src/compat/next/font/google.ts +2 -2
  196. package/src/compat/next/font/index.ts +1 -1
  197. package/src/compat/next/font/local.ts +3 -3
  198. package/src/compat/next/font/runtime-client.ts +17 -15
  199. package/src/compat/next/font/runtime.ts +443 -408
  200. package/src/compat/next/font/shared.ts +120 -108
  201. package/src/compat/next/form.tsx +63 -63
  202. package/src/compat/next/head.tsx +2 -2
  203. package/src/compat/next/headers.ts +103 -95
  204. package/src/compat/next/image/client.tsx +220 -0
  205. package/src/compat/next/image/config.ts +56 -58
  206. package/src/compat/next/image/optimizer.ts +40 -36
  207. package/src/compat/next/image/patterns.ts +37 -40
  208. package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
  209. package/src/compat/next/image/shared.ts +65 -57
  210. package/src/compat/next/image/static-metadata.ts +98 -107
  211. package/src/compat/next/image/validate.ts +79 -88
  212. package/src/compat/next/image.tsx +19 -23
  213. package/src/compat/next/index.ts +1 -1
  214. package/src/compat/next/legacy-image.tsx +59 -60
  215. package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
  216. package/src/compat/next/link.tsx +158 -158
  217. package/src/compat/next/navigation.cjs +12 -3
  218. package/src/compat/next/navigation.ts +48 -50
  219. package/src/compat/next/offline.ts +27 -27
  220. package/src/compat/next/og.ts +121 -124
  221. package/src/compat/next/preferred-region.ts +13 -14
  222. package/src/compat/next/redirects.ts +58 -56
  223. package/src/compat/next/resource-hints.ts +73 -76
  224. package/src/compat/next/rewrites.ts +130 -133
  225. package/src/compat/next/root-params.ts +45 -45
  226. package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
  227. package/src/compat/next/router.cjs +4 -2
  228. package/src/compat/next/router.ts +58 -61
  229. package/src/compat/next/script.tsx +108 -108
  230. package/src/compat/next/server-only.ts +1 -1
  231. package/src/compat/next/server.ts +19 -19
  232. package/src/compat/next/svgr.ts +18 -17
  233. package/src/compat/next/telemetry.ts +24 -24
  234. package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
  235. package/src/compat/next/user-agent.ts +53 -49
  236. package/src/compat/next/web-vitals.ts +22 -24
  237. package/src/compat/otel/api.ts +41 -41
  238. package/src/compat/otel/client-trace-metadata.ts +25 -27
  239. package/src/compat/otel/fetch-span.ts +29 -29
  240. package/src/compat/otel/tracer.ts +331 -331
  241. package/src/compat/pages/api.ts +456 -0
  242. package/src/compat/pages/client-plugin.ts +36 -36
  243. package/src/compat/pages/router-state.ts +34 -34
  244. package/src/compat/pages/{index.ts → router.ts} +130 -135
  245. package/src/compat/ppr/io.ts +12 -12
  246. package/src/compat/ppr/missing-root-params.ts +34 -38
  247. package/src/compat/ppr/root-params-scan.ts +66 -66
  248. package/src/compat/ppr/root-params-transform.ts +24 -26
  249. package/src/compat/ppr/root-params.ts +30 -30
  250. package/src/compat/ppr/segment-config-incompat.ts +6 -7
  251. package/src/compat/protocol.ts +71 -70
  252. package/src/compat/react/action-state.ts +47 -48
  253. package/src/compat/react/client-lite.ts +15 -15
  254. package/src/compat/react/client.ts +4 -4
  255. package/src/compat/react/compiler-runtime.ts +11 -11
  256. package/src/compat/react/dom-client.ts +44 -44
  257. package/src/compat/react/dom-react-server.ts +10 -16
  258. package/src/compat/react/dom-server.ts +10 -10
  259. package/src/compat/react/dom.ts +52 -52
  260. package/src/compat/react/hooks-extra.ts +34 -35
  261. package/src/compat/react/parity.ts +64 -61
  262. package/src/compat/react/preact.ts +81 -82
  263. package/src/compat/react/react-server.ts +28 -28
  264. package/src/compat/react/router-shim.ts +1 -1
  265. package/src/compat/react/server-component-use.ts +8 -8
  266. package/src/compat/react/server-inserted-html.ts +30 -31
  267. package/src/compat/react/server.ts +53 -55
  268. package/src/compat/react/use.ts +32 -32
  269. package/src/compat/react/view-transition.ts +20 -20
  270. package/src/compat/register/actions.ts +35 -824
  271. package/src/compat/register/boot.ts +41 -41
  272. package/src/compat/register/build-tier.ts +5 -5
  273. package/src/compat/register/build.ts +74 -70
  274. package/src/compat/register/bundler.ts +161 -156
  275. package/src/compat/register/cache.ts +20 -20
  276. package/src/compat/register/client-errors.ts +3 -3
  277. package/src/compat/register/config.ts +6 -6
  278. package/src/compat/register/css-extras.ts +30 -34
  279. package/src/compat/register/edge-runtime.ts +3 -3
  280. package/src/compat/register/errors.ts +10 -12
  281. package/src/compat/register/export.ts +16 -16
  282. package/src/compat/register/font.ts +11 -11
  283. package/src/compat/register/hooks.ts +2 -2
  284. package/src/compat/register/image.ts +40 -40
  285. package/src/compat/register/index.ts +59 -62
  286. package/src/compat/register/instrumentation-client.ts +10 -10
  287. package/src/compat/register/lifecycle.ts +25 -28
  288. package/src/compat/register/mdx.ts +10 -10
  289. package/src/compat/register/middleware.ts +226 -16
  290. package/src/compat/register/otel.ts +80 -88
  291. package/src/compat/register/pages-api.ts +10 -463
  292. package/src/compat/register/ppr.ts +16 -16
  293. package/src/compat/register/protocol.ts +17 -18
  294. package/src/compat/register/proxy.ts +49 -51
  295. package/src/compat/register/render.ts +79 -76
  296. package/src/compat/register/routing.ts +162 -159
  297. package/src/compat/register/segment.ts +24 -1887
  298. package/src/compat/register/static-image.ts +3 -3
  299. package/src/compat/register/{misc.ts → taint.ts} +9 -9
  300. package/src/compat/register/typed-routes.ts +14 -14
  301. package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
  302. package/src/compat/register/validation.ts +18 -18
  303. package/src/compat/segment/loading-boundary.ts +43 -45
  304. package/src/compat/segment/page-slot.ts +69 -69
  305. package/src/compat/segment/serve.ts +1884 -0
  306. package/src/compat/segment/tree.ts +113 -112
  307. package/src/compat/segment/vary-key.ts +38 -38
  308. package/src/compat/segment/vary-params.ts +138 -142
  309. package/src/compat/static-params.ts +14 -12
  310. package/src/compat/tsconfig-defaults.ts +87 -91
  311. package/src/compat/typecheck/{index.ts → check.ts} +234 -212
  312. package/src/compat/typecheck/worker.ts +15 -12
  313. package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
  314. package/src/compat/typed-routes/manifest.ts +174 -170
  315. package/src/compat/typed-routes/typegen.ts +127 -110
  316. package/src/compat/validation/errors.ts +16 -19
  317. package/src/compat/validation/prerender-diagnostics.ts +521 -504
  318. package/src/compat/validation/{index.ts → validate.ts} +647 -648
  319. package/src/compat-bootstrap.ts +16 -16
  320. package/src/config.ts +69 -69
  321. package/src/css/build.ts +230 -227
  322. package/src/css/postcss.ts +79 -80
  323. package/src/css/worker.ts +14 -15
  324. package/src/dev/client-actions.ts +10 -10
  325. package/src/dev/client-chunk-store.ts +27 -27
  326. package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
  327. package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
  328. package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
  329. package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
  330. package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
  331. package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
  332. package/src/dev/server.ts +804 -820
  333. package/src/env.ts +46 -43
  334. package/src/extensions.ts +491 -478
  335. package/src/index.ts +8 -8
  336. package/src/internal.ts +20 -23
  337. package/src/{islands → render}/boundary-error.ts +3 -3
  338. package/src/render/hooks.ts +71 -71
  339. package/src/render/island-context.ts +14 -14
  340. package/src/render/metadata.ts +310 -310
  341. package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
  342. package/src/{ppr.ts → render/ppr.ts} +244 -245
  343. package/src/render/renderer.ts +2076 -2082
  344. package/src/render/resource-hints.ts +16 -17
  345. package/src/render/slots.tsx +224 -235
  346. package/src/{islands → render}/static-children.ts +9 -12
  347. package/src/{islands → render}/static-slots.ts +37 -37
  348. package/src/{cache/context.ts → request/cache.ts} +20 -20
  349. package/src/request/context.ts +107 -107
  350. package/src/{dynamic/source.ts → resolve/dynamic.ts} +152 -143
  351. package/src/resolve/engine.ts +90 -77
  352. package/src/resolve/imports.ts +475 -463
  353. package/src/resolve/scan-facts.ts +442 -186
  354. package/src/resolve/source-text.ts +37 -37
  355. package/src/{dynamic → resolve}/tree-shake.ts +132 -128
  356. package/src/routing/forwarded.ts +19 -19
  357. package/src/routing/handler.ts +84 -91
  358. package/src/routing/href.ts +69 -70
  359. package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
  360. package/src/{proxy.ts → routing/proxy.ts} +306 -312
  361. package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
  362. package/src/routing/routes.ts +833 -848
  363. package/src/routing/slots.ts +164 -160
  364. package/src/runtime/loader.ts +952 -0
  365. package/src/{dev → runtime}/module-cache.ts +309 -287
  366. package/src/{dev → runtime}/module-generations.ts +9 -9
  367. package/src/{dev → runtime}/module-transform.ts +81 -72
  368. package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
  369. package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
  370. package/src/runtime/vendor.ts +425 -404
  371. package/src/styles.d.ts +9 -0
  372. package/src/types.ts +320 -335
  373. package/src/utils/ansi.ts +5 -5
  374. package/src/utils/{source.ts → code.ts} +15 -12
  375. package/src/utils/content-type.ts +3 -3
  376. package/src/utils/decode.ts +2 -2
  377. package/src/utils/dev-profile.ts +13 -13
  378. package/src/utils/error-log.ts +6 -6
  379. package/src/utils/esbuild.ts +18 -18
  380. package/src/utils/fs-cache.ts +13 -13
  381. package/src/utils/fs.ts +57 -49
  382. package/src/utils/html.ts +20 -24
  383. package/src/utils/native-require.ts +8 -8
  384. package/src/utils/serialize.ts +139 -146
  385. package/src/utils/verbose.ts +18 -18
  386. package/src/cli/analyze-print.ts +0 -181
  387. package/src/compat/middleware/manifest.ts +0 -210
  388. package/src/compat/next/image-client.tsx +0 -215
  389. package/src/compat/next/link-usage.ts +0 -29
  390. package/src/css/index.ts +0 -2
  391. package/src/render/index.ts +0 -1
  392. package/src/style-modules.d.ts +0 -9
@@ -1,12 +1,12 @@
1
- import path from 'node:path';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { listFiles, listFilesSync, readDirListing, toPosixPath, withDirCache } from '../utils/fs';
4
- import { safeDecode } from '../utils/decode';
1
+ import path from 'node:path'
2
+ import { existsSync, readFileSync } from 'node:fs'
3
+ import { listFiles, listFilesSync, readDirListing, toPosixPath, withDirCache } from '../utils/fs'
4
+ import { safeDecode } from '../utils/decode'
5
5
  import {
6
6
  dynamicCallsFromSource,
7
7
  pnextDynamicImportNames,
8
8
  rewriteLiteralDynamicCalls,
9
- } from '../dynamic/source';
9
+ } from '../resolve/dynamic'
10
10
  import {
11
11
  commonJsModuleHasDefaultExport,
12
12
  packageNameForFile,
@@ -14,10 +14,10 @@ import {
14
14
  resolveImport,
15
15
  resolveModuleAlias,
16
16
  resolvePackageSpecifier,
17
- } from '../resolve/imports';
18
- import { scanFacts } from '../resolve/scan-facts';
19
- import { readSourceSync } from '../resolve/source-text';
20
- import { globalCssSourcesForPaths } from '../css';
17
+ } from '../resolve/imports'
18
+ import { dynamicCallFacts, scanFacts, type DynamicCallFact } from '../resolve/scan-facts'
19
+ import { readSourceSync } from '../resolve/source-text'
20
+ import { globalCssSourcesForPaths } from '../css/build'
21
21
  import {
22
22
  childrenDefaultFile,
23
23
  interceptionMarkerLevels,
@@ -26,7 +26,7 @@ import {
26
26
  isSlotSegment,
27
27
  slotConventionFile,
28
28
  slotDirectoriesIn,
29
- } from './slots';
29
+ } from './slots'
30
30
  import type {
31
31
  ClientEntryReason,
32
32
  MetadataRouteEntry,
@@ -37,13 +37,13 @@ import type {
37
37
  RouteMode,
38
38
  RouteParamValue,
39
39
  RouteSegmentConfig,
40
- } from '../types';
40
+ } from '../types'
41
41
  import {
42
42
  clientReferenceId,
43
43
  ssrClientReference,
44
44
  type ClientDynamicReference,
45
45
  type ClientReference,
46
- } from '../client/reference';
46
+ } from '../client/reference'
47
47
  import {
48
48
  alwaysClientEntryReasons,
49
49
  classifyRouteDependencies,
@@ -53,7 +53,7 @@ import {
53
53
  getCssExtensions,
54
54
  sourceClientEntryReasons,
55
55
  sourceUsesRegisteredRequestApi,
56
- } from '../extensions';
56
+ } from '../extensions'
57
57
 
58
58
  // Single source of truth for the page/convention extension list. `.tsx`/`.ts` stay first so an all-.tsx app
59
59
  // resolves on the first candidate and produces the identical file as before; .js/.jsx/.mjs are additive
@@ -61,153 +61,153 @@ import {
61
61
  // pageExtensions()/routeHandlerExtensions() LAZILY, computed on first use and memoized, so the compat
62
62
  // registration that runs at bootstrap - before scanRoutes - is honored; a module-level const regex is baked
63
63
  // at import time, which is too early.
64
- const BASE_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mjs'] as const;
65
- const BASE_ROUTE_HANDLER_EXTENSIONS = ['ts', 'tsx', 'js', 'mjs'] as const;
64
+ const BASE_PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mjs'] as const
65
+ const BASE_ROUTE_HANDLER_EXTENSIONS = ['ts', 'tsx', 'js', 'mjs'] as const
66
66
 
67
67
  /** Base + compat-registered page extensions, de-duplicated in order. */
68
68
  function pageExtensions(): string[] {
69
- return [...new Set([...BASE_PAGE_EXTENSIONS, ...extraPageExtensions()])];
69
+ return [...new Set([...BASE_PAGE_EXTENSIONS, ...extraPageExtensions()])]
70
70
  }
71
71
 
72
72
  /** Route-handler extensions (route.*); compat page extensions do not apply. */
73
73
  function routeHandlerExtensions(): readonly string[] {
74
- return BASE_ROUTE_HANDLER_EXTENSIONS;
74
+ return BASE_ROUTE_HANDLER_EXTENSIONS
75
75
  }
76
76
 
77
77
  /** Union of page + route-handler extensions (metadata + trailing-file regexes). */
78
78
  function allFileExtensions(): string[] {
79
- return [...new Set([...pageExtensions(), ...routeHandlerExtensions()])];
79
+ return [...new Set([...pageExtensions(), ...routeHandlerExtensions()])]
80
80
  }
81
81
 
82
- const componentConventions = ['loading', 'error', 'not-found'] as const;
82
+ const componentConventions = ['loading', 'error', 'not-found'] as const
83
83
  // Extra boundary conventions (e.g. Next's authInterrupts forbidden/unauthorized)
84
84
  // are registered by compat via the routeConventions seam and merged in here.
85
85
  function boundaryConventions(): string[] {
86
- return [...componentConventions, ...extraBoundaryConventionNames()];
86
+ return [...componentConventions, ...extraBoundaryConventionNames()]
87
87
  }
88
88
 
89
- const globalErrorBase = 'global-error';
89
+ const globalErrorBase = 'global-error'
90
90
 
91
91
  // The extension list is only known after compat bootstrap runs (which may
92
92
  // registerPageExtensions), and scanRoutes always runs after bootstrap. Each
93
93
  // pattern is computed on first use and memoized so the compat additions land
94
94
  // while repeat calls stay cheap. Kept as accessors (not module-level consts)
95
95
  // precisely to defer baking past bootstrap.
96
- let memoPageFile: RegExp | undefined;
97
- let memoRouteFilePattern: RegExp | undefined;
98
- let memoDefaultConventionPattern: RegExp | undefined;
99
- let memoMetadataCodeFilePattern: RegExp | undefined;
100
- let memoPageOrRouteTrailing: RegExp | undefined;
101
- let memoSlotConventionPattern: RegExp | undefined;
96
+ let memoPageFile: RegExp | undefined
97
+ let memoRouteFilePattern: RegExp | undefined
98
+ let memoDefaultConventionPattern: RegExp | undefined
99
+ let memoMetadataCodeFilePattern: RegExp | undefined
100
+ let memoPageOrRouteTrailing: RegExp | undefined
101
+ let memoSlotConventionPattern: RegExp | undefined
102
102
 
103
103
  // Memos must not outlive a later registerPageExtensions call: Pages-compat
104
104
  // materialization scans routes during config resolution, BEFORE
105
105
  // registerMdxExtensions runs, and a pattern baked then would lock `.mdx` out.
106
- let memoExtensionsKey: string | undefined;
106
+ let memoExtensionsKey: string | undefined
107
107
  function freshMemos(): void {
108
- const key = extraPageExtensions().join(',');
109
- if (key === memoExtensionsKey) return;
110
- memoExtensionsKey = key;
111
- memoPageFile = undefined;
112
- memoRouteFilePattern = undefined;
113
- memoDefaultConventionPattern = undefined;
114
- memoMetadataCodeFilePattern = undefined;
115
- memoPageOrRouteTrailing = undefined;
116
- memoSlotConventionPattern = undefined;
108
+ const key = extraPageExtensions().join(',')
109
+ if (key === memoExtensionsKey) return
110
+ memoExtensionsKey = key
111
+ memoPageFile = undefined
112
+ memoRouteFilePattern = undefined
113
+ memoDefaultConventionPattern = undefined
114
+ memoMetadataCodeFilePattern = undefined
115
+ memoPageOrRouteTrailing = undefined
116
+ memoSlotConventionPattern = undefined
117
117
  }
118
118
 
119
119
  function pageFile(): RegExp {
120
- freshMemos();
121
- return (memoPageFile ??= extensionRegex('page', pageExtensions()));
120
+ freshMemos()
121
+ return (memoPageFile ??= extensionRegex('page', pageExtensions()))
122
122
  }
123
123
 
124
124
  function routeFilePattern(): RegExp {
125
- freshMemos();
126
- return (memoRouteFilePattern ??= extensionRegex('route', routeHandlerExtensions()));
125
+ freshMemos()
126
+ return (memoRouteFilePattern ??= extensionRegex('route', routeHandlerExtensions()))
127
127
  }
128
128
 
129
129
  function defaultConventionPattern(): RegExp {
130
- freshMemos();
131
- return (memoDefaultConventionPattern ??= extensionRegex('default', pageExtensions()));
130
+ freshMemos()
131
+ return (memoDefaultConventionPattern ??= extensionRegex('default', pageExtensions()))
132
132
  }
133
133
 
134
134
  function metadataCodeFilePattern(): RegExp {
135
- freshMemos();
135
+ freshMemos()
136
136
  return (memoMetadataCodeFilePattern ??= new RegExp(
137
137
  `(^|/)(robots|sitemap|manifest|icon\\d*|apple-icon\\d*|opengraph-image\\d*|twitter-image\\d*)\\.(${allFileExtensions().join(
138
138
  '|',
139
139
  )})$`,
140
- ));
140
+ ))
141
141
  }
142
142
 
143
143
  function pageOrRouteTrailing(): RegExp {
144
- freshMemos();
144
+ freshMemos()
145
145
  return (memoPageOrRouteTrailing ??= new RegExp(
146
146
  `/?(page|route|layout|template|loading|error|not-found|forbidden|unauthorized|default|global-error)\\.(${allFileExtensions().join(
147
147
  '|',
148
148
  )})$`,
149
- ));
149
+ ))
150
150
  }
151
151
 
152
152
  function slotConventionPattern(): RegExp {
153
- freshMemos();
153
+ freshMemos()
154
154
  return (memoSlotConventionPattern ??= new RegExp(
155
155
  `(^|/)(page|layout|template|loading|error|default|not-found)\\.(${pageExtensions().join('|')})$`,
156
- ));
156
+ ))
157
157
  }
158
158
 
159
159
  function extensionRegex(base: string, extensions: readonly string[]) {
160
- return new RegExp(`(^|/)${escapeRegex(base)}\\.(${extensions.join('|')})$`);
160
+ return new RegExp(`(^|/)${escapeRegex(base)}\\.(${extensions.join('|')})$`)
161
161
  }
162
162
 
163
163
  /** Nearest existing file named `<base>.<ext>` for the convention extensions. */
164
164
  function conventionFileName(dir: string, base: string) {
165
- const { files } = readDirListing(dir);
165
+ const { files } = readDirListing(dir)
166
166
  for (const extension of pageExtensions()) {
167
- if (files.has(`${base}.${extension}`)) return path.join(dir, `${base}.${extension}`);
167
+ if (files.has(`${base}.${extension}`)) return path.join(dir, `${base}.${extension}`)
168
168
  }
169
- return undefined;
169
+ return undefined
170
170
  }
171
171
 
172
172
  interface RouteParts {
173
- route: string;
174
- pattern: string;
175
- params: string[];
176
- catchAll?: string;
177
- catchAllOptional?: boolean;
178
- interception?: Omit<RouteInterception, 'slotDir'>;
173
+ route: string
174
+ pattern: string
175
+ params: string[]
176
+ catchAll?: string
177
+ catchAllOptional?: boolean
178
+ interception?: Omit<RouteInterception, 'slotDir'>
179
179
  }
180
180
 
181
181
  interface ModuleEdge {
182
- file: string;
183
- exports: string[];
184
- dynamic?: ClientDynamicReference;
182
+ file: string
183
+ exports: string[]
184
+ dynamic?: ClientDynamicReference
185
185
  /** `export * from` the target: its named exports are re-exported by this module. */
186
- star?: boolean;
186
+ star?: boolean
187
187
  }
188
188
 
189
189
  interface ScanContext {
190
- root: string;
191
- appPath: string;
192
- sources: Map<string, string>;
193
- edges: Map<string, ModuleEdge[]>;
194
- requestImportUsage: Map<string, boolean>;
190
+ root: string
191
+ appPath: string
192
+ sources: Map<string, string>
193
+ edges: Map<string, ModuleEdge[]>
194
+ requestImportUsage: Map<string, boolean>
195
195
  /**
196
196
  * Per-FILE facts, path-keyed. Every route re-walks its whole source closure and each visit
197
197
  * re-derived these from the text. Same lifetime and staleness rules as `sources`.
198
198
  */
199
- exists: Map<string, boolean>;
200
- useClient: Map<string, boolean>;
201
- usesLink: Map<string, boolean>;
202
- usesNavigation: Map<string, boolean>;
203
- entryReasons: Map<string, ClientEntryReason[]>;
199
+ exists: Map<string, boolean>
200
+ useClient: Map<string, boolean>
201
+ usesLink: Map<string, boolean>
202
+ usesNavigation: Map<string, boolean>
203
+ entryReasons: Map<string, ClientEntryReason[]>
204
204
  /** Root-layout CSS closure — itself a content walk, so it resolves with the first CSS fact. */
205
- globalCssImports(): Set<string>;
205
+ globalCssImports(): Set<string>
206
206
  }
207
207
 
208
208
  function createScanContext(appPath: string, globalCss?: () => Set<string>): ScanContext {
209
- const root = rootFromAppPath(appPath);
210
- let memo: Set<string> | undefined;
209
+ const root = rootFromAppPath(appPath)
210
+ let memo: Set<string> | undefined
211
211
  return {
212
212
  root,
213
213
  appPath,
@@ -221,20 +221,20 @@ function createScanContext(appPath: string, globalCss?: () => Set<string>): Scan
221
221
  entryReasons: new Map(),
222
222
  globalCssImports:
223
223
  globalCss ?? (() => (memo ??= new Set(globalCssSourcesForPaths(root, appPath)))),
224
- };
224
+ }
225
225
  }
226
226
 
227
227
  /** `existsSync` once per path per scan — the closure walk asks the same paths per route. */
228
228
  function fileExists(context: ScanContext, file: string) {
229
- let hit = context.exists.get(file);
229
+ let hit = context.exists.get(file)
230
230
  if (hit === undefined) {
231
- hit = existsSync(file);
232
- context.exists.set(file, hit);
231
+ hit = existsSync(file)
232
+ context.exists.set(file, hit)
233
233
  }
234
- return hit;
234
+ return hit
235
235
  }
236
236
 
237
- const routeHandlerMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const;
237
+ const routeHandlerMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'] as const
238
238
 
239
239
  /**
240
240
  * The entry fields that need file CONTENT. Boot never reads them: the route table is built from path
@@ -242,21 +242,21 @@ const routeHandlerMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'O
242
242
  * compile - through one memoized scan-facts pass per file, which that compile then re-uses.
243
243
  */
244
244
  export interface RouteFacts {
245
- mode: RouteMode;
246
- hasStaticParams: boolean;
247
- usesRequest: boolean;
248
- handlerUsesRevalidationApi?: true;
249
- dynamicErrorApi?: string;
250
- client: boolean;
251
- clientReferences: ClientReference[];
252
- cssImports: string[];
253
- sourceFiles: string[];
254
- stream?: RouteManifestEntry['stream'];
255
- maxDuration?: number;
256
- ppr?: boolean;
257
- segmentConfig?: RouteSegmentConfig;
258
- needsRouterEntry?: true;
259
- clientEntryReasons?: ClientEntryReason[];
245
+ mode: RouteMode
246
+ hasStaticParams: boolean
247
+ usesRequest: boolean
248
+ handlerUsesRevalidationApi?: true
249
+ dynamicErrorApi?: string
250
+ client: boolean
251
+ clientReferences: ClientReference[]
252
+ cssImports: string[]
253
+ sourceFiles: string[]
254
+ stream?: RouteManifestEntry['stream']
255
+ maxDuration?: number
256
+ ppr?: boolean
257
+ segmentConfig?: RouteSegmentConfig
258
+ needsRouterEntry?: true
259
+ clientEntryReasons?: ClientEntryReason[]
260
260
  }
261
261
 
262
262
  const deferredFields = [
@@ -275,21 +275,21 @@ const deferredFields = [
275
275
  'segmentConfig',
276
276
  'needsRouterEntry',
277
277
  'clientEntryReasons',
278
- ] as const satisfies readonly (keyof RouteFacts)[];
278
+ ] as const satisfies readonly (keyof RouteFacts)[]
279
279
 
280
280
  /** Path-only entry fields; everything in RouteFacts is layered on lazily. */
281
- type RoutePathEntry = Omit<RouteManifestEntry, keyof RouteFacts>;
281
+ type RoutePathEntry = Omit<RouteManifestEntry, keyof RouteFacts>
282
282
 
283
- const resolvedRoutes = new WeakSet<RouteManifestEntry>();
284
- let factsVersion = 0;
283
+ const resolvedRoutes = new WeakSet<RouteManifestEntry>()
284
+ let factsVersion = 0
285
285
 
286
286
  /** Bumped each time a route resolves its deferred facts (dev re-keys watch roots off it). */
287
287
  export function routeFactsVersion() {
288
- return factsVersion;
288
+ return factsVersion
289
289
  }
290
290
 
291
291
  export function routeFactsResolved(route: RouteManifestEntry) {
292
- return resolvedRoutes.has(route);
292
+ return resolvedRoutes.has(route)
293
293
  }
294
294
 
295
295
  /**
@@ -302,10 +302,10 @@ export function materializeRouteFacts<T extends RouteManifestEntry[]>(
302
302
  onRoute?: (route: RouteManifestEntry) => void,
303
303
  ): T {
304
304
  for (const route of routes) {
305
- void route.sourceFiles;
306
- onRoute?.(route);
305
+ void route.sourceFiles
306
+ onRoute?.(route)
307
307
  }
308
- return routes;
308
+ return routes
309
309
  }
310
310
 
311
311
  /**
@@ -318,10 +318,10 @@ export async function materializeRouteFactsPaced<T extends RouteManifestEntry[]>
318
318
  pause: () => Promise<void>,
319
319
  ): Promise<T> {
320
320
  for (const route of routes) {
321
- await pause();
322
- void route.sourceFiles;
321
+ await pause()
322
+ void route.sourceFiles
323
323
  }
324
- return routes;
324
+ return routes
325
325
  }
326
326
 
327
327
  /**
@@ -330,14 +330,14 @@ export async function materializeRouteFactsPaced<T extends RouteManifestEntry[]>
330
330
  * still has the content it was recorded with; `save` is called once, with the facts a fresh walk produced.
331
331
  */
332
332
  export interface RouteFactsStore {
333
- load(routeId: string): RouteFacts | undefined;
334
- save(routeId: string, facts: RouteFacts): void;
333
+ load(routeId: string): RouteFacts | undefined
334
+ save(routeId: string, facts: RouteFacts): void
335
335
  }
336
336
 
337
- let factsStore: RouteFactsStore | undefined;
337
+ let factsStore: RouteFactsStore | undefined
338
338
 
339
339
  export function setRouteFactsStore(store: RouteFactsStore | undefined) {
340
- factsStore = store;
340
+ factsStore = store
341
341
  }
342
342
 
343
343
  function withDeferredFacts(
@@ -345,81 +345,81 @@ function withDeferredFacts(
345
345
  base: RoutePathEntry,
346
346
  compute: () => RouteFacts,
347
347
  ): RouteManifestEntry {
348
- const entry = base as RouteManifestEntry;
349
- let facts: RouteFacts | undefined;
348
+ const entry = base as RouteManifestEntry
349
+ let facts: RouteFacts | undefined
350
350
  // Consumers still assign some of these (the build marks needsRouterEntry), so
351
351
  // each field keeps a setter that wins over the computed value without forcing
352
352
  // the scan.
353
- const overrides = new Map<string, unknown>();
353
+ const overrides = new Map<string, unknown>()
354
354
 
355
355
  for (const field of deferredFields) {
356
356
  Object.defineProperty(entry, field, {
357
357
  enumerable: true,
358
358
  configurable: true,
359
359
  get() {
360
- if (overrides.has(field)) return overrides.get(field);
360
+ if (overrides.has(field)) return overrides.get(field)
361
361
  if (!facts) {
362
362
  // Route ids repeat across apps ('page:/' everywhere), so the app the
363
363
  // facts were derived in is part of the key - otherwise one app's
364
364
  // record answers another app's lookup in the same process.
365
- const key = `${appPath}\0${base.kind}:${base.id}`;
366
- const stored = factsStore?.load(key);
367
- facts = stored ?? withDirCache(compute);
368
- if (!stored) factsStore?.save(key, facts);
369
- resolvedRoutes.add(entry);
370
- factsVersion += 1;
365
+ const key = `${appPath}\0${base.kind}:${base.id}`
366
+ const stored = factsStore?.load(key)
367
+ facts = stored ?? withDirCache(compute)
368
+ if (!stored) factsStore?.save(key, facts)
369
+ resolvedRoutes.add(entry)
370
+ factsVersion += 1
371
371
  }
372
- return facts[field];
372
+ return facts[field]
373
373
  },
374
374
  set(value: unknown) {
375
- overrides.set(field, value);
375
+ overrides.set(field, value)
376
376
  },
377
- });
377
+ })
378
378
  }
379
379
 
380
- return entry;
380
+ return entry
381
381
  }
382
382
 
383
383
  export async function scanRoutes(appPath: string): Promise<RouteManifestEntry[]> {
384
- const files = await listFiles(appPath);
385
- return withDirCache(() => buildRouteTable(appPath, files));
384
+ const files = await listFiles(appPath)
385
+ return withDirCache(() => buildRouteTable(appPath, files))
386
386
  }
387
387
 
388
388
  /** The path pass: file paths in, route table out — no file content is read. */
389
389
  function buildRouteTable(appPath: string, files: string[]): RouteManifestEntry[] {
390
- const context = createScanContext(appPath);
391
- const routes: RouteManifestEntry[] = [];
392
- const slotPageFiles: { file: string; relative: string }[] = [];
393
- const defaultPageFiles: { file: string; relative: string }[] = [];
390
+ const context = createScanContext(appPath)
391
+ const routes: RouteManifestEntry[] = []
392
+ const slotPageFiles: { file: string; relative: string }[] = []
393
+ const defaultPageFiles: { file: string; relative: string }[] = []
394
394
  // Parallel/intercepting-route semantics only exist on client navigations, so
395
395
  // when the app uses them every page ships the router runtime. Both facts are
396
396
  // path-shaped, but the whole table must be walked before they are known —
397
397
  // hence a holder the per-route needsRouterEntry closures read later.
398
- const table = { parallelRoutes: false };
398
+ const table = { parallelRoutes: false }
399
399
 
400
400
  for (const file of files) {
401
- const relative = toPosixPath(path.relative(appPath, file));
402
- const routeFile = path.join(appPath, relative);
401
+ const relative = toPosixPath(path.relative(appPath, file))
402
+ const routeFile = path.join(appPath, relative)
403
403
  if (
404
404
  defaultConventionPattern().test(relative) &&
405
405
  !relative.split('/').some(segment => segment.startsWith('@'))
406
406
  ) {
407
- defaultPageFiles.push({ file, relative });
407
+ defaultPageFiles.push({ file, relative })
408
408
  }
409
409
  // App segments beginning with `_` are private (Next ignores them); the
410
410
  // route is dropped so requesting it 404s. A directory literally named
411
411
  // `%5Ffoo` decodes to a routable `/_foo` (handled in routeParts), so it is
412
412
  // NOT private. Applies to pages, handlers, and metadata routes alike.
413
- if (isPrivateAppPath(relative)) continue;
413
+ if (isPrivateAppPath(relative)) continue
414
414
  if (!pageFile().test(relative) && !routeFilePattern().test(relative)) {
415
- if (!metadataCodeFilePattern().test(relative)) continue;
415
+ if (!metadataCodeFilePattern().test(relative)) continue
416
416
  // The one content read boot cannot defer: a metadata file's generateSitemaps/
417
417
  // generateImageMetadata export decides whether its URL carries an id segment,
418
418
  // so it shapes the PATTERN. Bounded to metadata files (a handful per app) and
419
419
  // to their own source — no module-graph walk.
420
- const source = readSource(context, file);
421
- const metadataParts = dynamicMetadataRouteParts(relative, source);
422
- if (!metadataParts) continue;
420
+ const source = readSource(context, file)
421
+ const metadataParts = dynamicMetadataRouteParts(relative, source)
422
+ if (!metadataParts) continue
423
423
  routes.push(
424
424
  withDeferredFacts(
425
425
  appPath,
@@ -436,38 +436,38 @@ function buildRouteTable(appPath: string, files: string[]): RouteManifestEntry[]
436
436
  },
437
437
  () => metadataRouteFacts(context, routeFile, metadataParts),
438
438
  ),
439
- );
440
- continue;
439
+ )
440
+ continue
441
441
  }
442
442
  // Pages inside `@slot` dirs primarily render as slot content of their
443
443
  // owning segment; they also derive standalone entries (synthetic slot URLs
444
444
  // and slot interception targets) in a second pass below.
445
445
  if (relative.split('/').some(segment => segment.startsWith('@'))) {
446
- if (pageFile().test(relative)) slotPageFiles.push({ file, relative });
447
- continue;
446
+ if (pageFile().test(relative)) slotPageFiles.push({ file, relative })
447
+ continue
448
448
  }
449
449
 
450
- const kind = pageFile().test(relative) ? 'page' : 'handler';
451
- const routeDir = relative.replace(pageOrRouteTrailing(), '');
452
- const parts = routeParts(routeDir);
453
- const conventions = conventionPaths(appPath, routeFile, kind);
454
- const templatePaths = conventions.templateFiles.filter(item => existsSync(item));
450
+ const kind = pageFile().test(relative) ? 'page' : 'handler'
451
+ const routeDir = relative.replace(pageOrRouteTrailing(), '')
452
+ const parts = routeParts(routeDir)
453
+ const conventions = conventionPaths(appPath, routeFile, kind)
454
+ const templatePaths = conventions.templateFiles.filter(item => existsSync(item))
455
455
  // authInterrupt boundary files (forbidden/unauthorized) are only discovered
456
456
  // when compat registered those conventions via the routeConventions seam.
457
- const extraBoundaries = new Set(extraBoundaryConventionNames());
457
+ const extraBoundaries = new Set(extraBoundaryConventionNames())
458
458
  const forbiddenPaths =
459
459
  kind === 'page' && extraBoundaries.has('forbidden')
460
460
  ? findConventionFiles(appPath, routeFile, 'forbidden').filter(item => existsSync(item))
461
- : [];
461
+ : []
462
462
  const unauthorizedPaths =
463
463
  kind === 'page' && extraBoundaries.has('unauthorized')
464
464
  ? findConventionFiles(appPath, routeFile, 'unauthorized').filter(item => existsSync(item))
465
- : [];
465
+ : []
466
466
 
467
467
  // An interception entry's route/pattern is the TARGET path it responds
468
468
  // to; it only matches soft navigations (see matchInterception), renders
469
469
  // per request, and never prerenders.
470
- const interception = parts.interception;
470
+ const interception = parts.interception
471
471
  routes.push(
472
472
  withDeferredFacts(
473
473
  appPath,
@@ -488,22 +488,22 @@ function buildRouteTable(appPath: string, files: string[]): RouteManifestEntry[]
488
488
  },
489
489
  () => pageOrHandlerFacts(context, { kind, routeFile, parts, conventions, table }),
490
490
  ),
491
- );
491
+ )
492
492
  }
493
493
 
494
- appendSlotDerivedRoutes(context, routes, slotPageFiles, table);
495
- appendDefaultDerivedRoutes(context, routes, defaultPageFiles, table);
496
- table.parallelRoutes = routes.some(route => route.slotDirs?.length || route.interception);
494
+ appendSlotDerivedRoutes(context, routes, slotPageFiles, table)
495
+ appendDefaultDerivedRoutes(context, routes, defaultPageFiles, table)
496
+ table.parallelRoutes = routes.some(route => route.slotDirs?.length || route.interception)
497
497
 
498
- return routes.sort((a, b) => routeRank(a).localeCompare(routeRank(b)));
498
+ return routes.sort((a, b) => routeRank(a).localeCompare(routeRank(b)))
499
499
  }
500
500
 
501
501
  /** Convention files reachable from a route file — all decided by paths on disk. */
502
502
  function conventionPaths(appPath: string, routeFile: string, kind: 'page' | 'handler') {
503
503
  if (kind !== 'page') {
504
- return { layoutFiles: [], templateFiles: [], specialFiles: [], slotDirs: [], slotFiles: [] };
504
+ return { layoutFiles: [], templateFiles: [], specialFiles: [], slotDirs: [], slotFiles: [] }
505
505
  }
506
- const slotDirs = chainSlotDirs(appPath, routeFile);
506
+ const slotDirs = chainSlotDirs(appPath, routeFile)
507
507
  return {
508
508
  layoutFiles: findLayouts(appPath, routeFile),
509
509
  templateFiles: findConventionFiles(appPath, routeFile, 'template'),
@@ -512,14 +512,14 @@ function conventionPaths(appPath: string, routeFile: string, kind: 'page' | 'han
512
512
  ),
513
513
  slotDirs,
514
514
  slotFiles: slotTreeFiles(slotDirs),
515
- };
515
+ }
516
516
  }
517
517
 
518
- type ConventionPaths = ReturnType<typeof conventionPaths>;
518
+ type ConventionPaths = ReturnType<typeof conventionPaths>
519
519
 
520
520
  /** Set once the whole table is known; the closures below read it, never at boot. */
521
521
  interface TableFlags {
522
- parallelRoutes: boolean;
522
+ parallelRoutes: boolean
523
523
  }
524
524
 
525
525
  function metadataRouteFacts(
@@ -527,19 +527,22 @@ function metadataRouteFacts(
527
527
  routeFile: string,
528
528
  parts: DynamicMetadataRouteParts,
529
529
  ): RouteFacts {
530
- const source = readSource(context, routeFile);
531
- const sourceFiles = collectSourceFiles(context, [routeFile]);
532
- const dependency = routeDependencyClassification(context, 'handler', sourceFiles);
530
+ const source = readSource(context, routeFile)
531
+ const sourceFiles = collectSourceFiles(context, [routeFile])
532
+ const dependency = routeDependencyClassification(context, 'handler', sourceFiles)
533
533
  const usesRequest =
534
534
  metadataRouteUsesRequest(source) ||
535
535
  usesRevalidationApis(source) ||
536
536
  dependency.usesRequest === true ||
537
- usesRequestImportDataForFiles(context, sourceFiles.filter(file => !isAssetFile(file)));
537
+ usesRequestImportDataForFiles(
538
+ context,
539
+ sourceFiles.filter(file => !isAssetFile(file)),
540
+ )
538
541
  const segmentConfig = segmentConfigFromEntries(
539
542
  context.appPath,
540
543
  [{ file: routeFile, source }],
541
544
  parts,
542
- );
545
+ )
543
546
  return {
544
547
  mode: inferRouteMode(parts, usesRequest, segmentConfig),
545
548
  hasStaticParams: Boolean(parts.metadataRoute.generatedParam),
@@ -550,67 +553,69 @@ function metadataRouteFacts(
550
553
  sourceFiles,
551
554
  maxDuration: maxDurationFromSources([source]),
552
555
  ...(segmentConfig ? { segmentConfig } : {}),
553
- };
556
+ }
554
557
  }
555
558
 
556
559
  function pageOrHandlerFacts(
557
560
  context: ScanContext,
558
561
  options: {
559
- kind: 'page' | 'handler';
560
- routeFile: string;
561
- parts: RouteParts;
562
- conventions: ConventionPaths;
563
- table: TableFlags;
562
+ kind: 'page' | 'handler'
563
+ routeFile: string
564
+ parts: RouteParts
565
+ conventions: ConventionPaths
566
+ table: TableFlags
564
567
  },
565
568
  ): RouteFacts {
566
- const { appPath } = context;
567
- const { kind, routeFile, parts, conventions, table } = options;
568
- const { layoutFiles, templateFiles, specialFiles, slotFiles } = conventions;
569
- const source = readScanSource(context, routeFile);
569
+ const { appPath } = context
570
+ const { kind, routeFile, parts, conventions, table } = options
571
+ const { layoutFiles, templateFiles, specialFiles, slotFiles } = conventions
572
+ const source = readScanSource(context, routeFile)
570
573
  const layoutSources = [...layoutFiles, ...templateFiles, ...specialFiles]
571
574
  .filter(item => existsSync(item))
572
- .map(item => readScanSource(context, item));
573
- const entryFiles = [routeFile, ...layoutFiles, ...templateFiles, ...specialFiles, ...slotFiles];
574
- const sourceFiles = collectSourceFiles(context, entryFiles);
575
- const dependency = routeDependencyClassification(context, kind, sourceFiles);
576
- const handlerUsesRevalidationApi = kind === 'handler' && usesRevalidationApis(source);
575
+ .map(item => readScanSource(context, item))
576
+ const entryFiles = [routeFile, ...layoutFiles, ...templateFiles, ...specialFiles, ...slotFiles]
577
+ const sourceFiles = collectSourceFiles(context, entryFiles)
578
+ const dependency = routeDependencyClassification(context, kind, sourceFiles)
579
+ const handlerUsesRevalidationApi = kind === 'handler' && usesRevalidationApis(source)
577
580
  const usesRequest =
578
581
  kind === 'handler'
579
- ? routeHandlerUsesRequest(source) || handlerUsesRevalidationApi || dependency.usesRequest === true
580
- : [source, ...layoutSources].some(usesRequestData) || dependency.usesRequest === true;
582
+ ? routeHandlerUsesRequest(source) ||
583
+ handlerUsesRevalidationApi ||
584
+ dependency.usesRequest === true
585
+ : [source, ...layoutSources].some(usesRequestData) || dependency.usesRequest === true
581
586
  // Layout sources only (leaf-first, page first): segment config exports in
582
587
  // templates/special files have no route-level meaning.
583
- const existingLayoutFiles = layoutFiles.filter(item => existsSync(item));
588
+ const existingLayoutFiles = layoutFiles.filter(item => existsSync(item))
584
589
  const configEntries = [
585
590
  { file: routeFile, source },
586
591
  ...[...existingLayoutFiles].reverse().map(item => ({
587
592
  file: item,
588
593
  source: readScanSource(context, item),
589
594
  })),
590
- ];
591
- const segmentConfig = segmentConfigFromEntries(appPath, configEntries, parts);
595
+ ]
596
+ const segmentConfig = segmentConfigFromEntries(appPath, configEntries, parts)
592
597
  if (segmentConfig?.dynamicParamsFalse?.length) {
593
598
  // `dynamicParams = false` only 404s unlisted params when the route is fully statically enumerable -
594
599
  // every dynamic segment must be generated by a generateStaticParams somewhere in the chain. If a dynamic
595
600
  // segment has no static params, the route renders on demand and the ancestor's dynamicParams config no
596
601
  // longer restricts it (Next treats the whole path as dynamic).
597
- const allDynamicParams = [...parts.params, ...(parts.catchAll ? [parts.catchAll] : [])];
598
- const coveredParams = new Set<string>();
602
+ const allDynamicParams = [...parts.params, ...(parts.catchAll ? [parts.catchAll] : [])]
603
+ const coveredParams = new Set<string>()
599
604
  for (const entry of configEntries) {
600
- if (!hasStaticParamsExport(entry.source)) continue;
601
- const covered = paramOfOwnSegment(appPath, entry.file, allDynamicParams);
602
- if (covered) coveredParams.add(covered);
605
+ if (!hasStaticParamsExport(entry.source)) continue
606
+ const covered = paramOfOwnSegment(appPath, entry.file, allDynamicParams)
607
+ if (covered) coveredParams.add(covered)
603
608
  }
604
609
  if (allDynamicParams.some(param => !coveredParams.has(param))) {
605
- delete segmentConfig.dynamicParamsFalse;
610
+ delete segmentConfig.dynamicParamsFalse
606
611
  } else if (hasStaticParamsExport(source)) {
607
- segmentConfig.strictDynamicParams = true;
612
+ segmentConfig.strictDynamicParams = true
608
613
  }
609
614
  }
610
615
  const hasStaticParams =
611
616
  hasStaticParamsExport(source) ||
612
617
  (kind === 'page' &&
613
- existingLayoutFiles.map(item => readScanSource(context, item)).some(hasStaticParamsExport));
618
+ existingLayoutFiles.map(item => readScanSource(context, item)).some(hasStaticParamsExport))
614
619
  // `dynamic = 'error'` forbids dynamic data: a static reference to one is a
615
620
  // build failure (enforced in the prerender pass). Restricted to routes with
616
621
  // no dynamic params and no generateStaticParams: those prerender a single
@@ -625,15 +630,15 @@ function pageOrHandlerFacts(
625
630
  !parts.catchAll &&
626
631
  !hasStaticParams
627
632
  ? dynamicApiLabel(kind === 'handler' ? [source] : [source, ...layoutSources], kind)
628
- : undefined;
633
+ : undefined
629
634
  const ppr =
630
635
  kind === 'page' &&
631
636
  usesRequest &&
632
637
  parts.params.length === 0 &&
633
638
  !parts.catchAll &&
634
- exportsExperimentalPpr([source, ...layoutSources]);
639
+ exportsExperimentalPpr([source, ...layoutSources])
635
640
 
636
- const directClient = kind === 'page' && hasUseClientDirective(routeFile, source);
641
+ const directClient = kind === 'page' && hasUseClientDirective(routeFile, source)
637
642
  const clientReferences =
638
643
  kind === 'page'
639
644
  ? collectClientReferences(
@@ -642,14 +647,16 @@ function pageOrHandlerFacts(
642
647
  ? [...layoutFiles, ...templateFiles, ...specialFiles, ...slotFiles]
643
648
  : entryFiles,
644
649
  )
645
- : [];
650
+ : []
646
651
  const client =
647
652
  directClient ||
648
- clientReferences.some(reference => reference.file === routeFile && reference.exportName === 'default');
653
+ clientReferences.some(
654
+ reference => reference.file === routeFile && reference.exportName === 'default',
655
+ )
649
656
  const stream =
650
657
  kind === 'page'
651
658
  ? routeStreamMetadata({ context, layoutFiles, templateFiles, specialFiles })
652
- : undefined;
659
+ : undefined
653
660
  const cssImports =
654
661
  kind === 'page'
655
662
  ? collectCssImports(
@@ -657,10 +664,10 @@ function pageOrHandlerFacts(
657
664
  cssEntryOrder({ routeFile, layoutFiles, templateFiles, specialFiles, slotFiles }),
658
665
  { route: true },
659
666
  )
660
- : [];
661
- if (kind === 'page') attachLazyReferenceCss(context, clientReferences);
667
+ : []
668
+ if (kind === 'page') attachLazyReferenceCss(context, clientReferences)
662
669
 
663
- const interception = parts.interception;
670
+ const interception = parts.interception
664
671
  return {
665
672
  mode: interception ? 'dynamic' : inferRouteMode(parts, usesRequest, segmentConfig),
666
673
  hasStaticParams: interception ? false : hasStaticParams,
@@ -684,7 +691,7 @@ function pageOrHandlerFacts(
684
691
  slotInterceptor: false,
685
692
  table,
686
693
  }),
687
- };
694
+ }
688
695
  }
689
696
 
690
697
  /**
@@ -699,38 +706,38 @@ function pageOrHandlerFacts(
699
706
  function routerEntryFact(
700
707
  context: ScanContext,
701
708
  options: {
702
- kind: 'page' | 'handler';
703
- client: boolean;
704
- clientReferences: ClientReference[];
705
- sourceFiles: string[];
706
- slotInterceptor: boolean;
707
- table: TableFlags;
709
+ kind: 'page' | 'handler'
710
+ client: boolean
711
+ clientReferences: ClientReference[]
712
+ sourceFiles: string[]
713
+ slotInterceptor: boolean
714
+ table: TableFlags
708
715
  },
709
716
  ): { needsRouterEntry?: true; clientEntryReasons?: ClientEntryReason[] } {
710
- if (options.kind !== 'page') return {};
711
- const reasons: ClientEntryReason[] = [];
717
+ if (options.kind !== 'page') return {}
718
+ const reasons: ClientEntryReason[] = []
712
719
  // Slot interceptors never render standalone (the origin page hosts them);
713
720
  // children interceptors render directly and need the runtime.
714
- if (options.table.parallelRoutes && !options.slotInterceptor) reasons.push('parallel-routes');
721
+ if (options.table.parallelRoutes && !options.slotInterceptor) reasons.push('parallel-routes')
715
722
  // Compat parity: Next ships its router bootstrap on every app page, so a page
716
723
  // with no client code of its own still hydrates (and runs
717
724
  // instrumentation-client before hydration).
718
- reasons.push(...alwaysClientEntryReasons());
725
+ reasons.push(...alwaysClientEntryReasons())
719
726
  // A page with client code of its own already ships an entry, so the <Link> scan only exists to catch the
720
727
  // pages that otherwise would not. The remaining reasons gate feature regions INSIDE the entry (the action
721
728
  // runtime), so they are scanned for every page - a page's inline `'use server'` action is usually handed to
722
729
  // an island it also renders.
723
730
  if (!options.client && options.clientReferences.length === 0) {
724
- if (routeUsesLinkComponent(context, options.sourceFiles)) reasons.push('link');
731
+ if (routeUsesLinkComponent(context, options.sourceFiles)) reasons.push('link')
725
732
  } else if (routeUsesNavigation(context, options.sourceFiles)) {
726
733
  // The complement of the branch above: a page that already ships an entry
727
734
  // never needed a reason before, so nothing here changes whether the entry
728
735
  // exists — it decides whether that entry carries the router at all.
729
- reasons.push('router-api');
736
+ reasons.push('router-api')
730
737
  }
731
- reasons.push(...routeClientEntryReasons(context, options.sourceFiles));
732
- if (reasons.length === 0) return {};
733
- return { needsRouterEntry: true, clientEntryReasons: [...new Set(reasons)] };
738
+ reasons.push(...routeClientEntryReasons(context, options.sourceFiles))
739
+ if (reasons.length === 0) return {}
740
+ return { needsRouterEntry: true, clientEntryReasons: [...new Set(reasons)] }
734
741
  }
735
742
 
736
743
  /**
@@ -739,19 +746,21 @@ function routerEntryFact(
739
746
  * discovery has run). Keeps `needsRouterEntry` in step with the reason list.
740
747
  */
741
748
  export function addClientEntryReason(route: RouteManifestEntry, reason: ClientEntryReason): void {
742
- const reasons = route.clientEntryReasons ?? [];
743
- if (!reasons.includes(reason)) route.clientEntryReasons = [...reasons, reason];
744
- route.needsRouterEntry = true;
749
+ const reasons = route.clientEntryReasons ?? []
750
+ if (!reasons.includes(reason)) route.clientEntryReasons = [...reasons, reason]
751
+ route.needsRouterEntry = true
745
752
  }
746
753
 
747
754
  function routeStreamMetadata(options: {
748
- context: ScanContext;
749
- layoutFiles: string[];
750
- templateFiles: string[];
751
- specialFiles: string[];
755
+ context: ScanContext
756
+ layoutFiles: string[]
757
+ templateFiles: string[]
758
+ specialFiles: string[]
752
759
  }) {
753
- const existingSpecialFiles = options.specialFiles.filter(item => existsSync(item));
754
- const hasLoadingBoundary = existingSpecialFiles.some(file => /(^|[/\\])loading\.[^.]+$/.test(file));
760
+ const existingSpecialFiles = options.specialFiles.filter(item => existsSync(item))
761
+ const hasLoadingBoundary = existingSpecialFiles.some(file =>
762
+ /(^|[/\\])loading\.[^.]+$/.test(file),
763
+ )
755
764
  // Compute the ancestor client-reference closure for EVERY page, not just loading.js routes. Streaming
756
765
  // isolates each Suspense boundary and drops client-provider context from ancestor layouts, so the
757
766
  // render-time stream gate keys off this list - a route with in-page user Suspense boundaries but no
@@ -759,45 +768,45 @@ function routeStreamMetadata(options: {
759
768
  const ancestorClientReferences = collectClientReferences(options.context, [
760
769
  ...options.layoutFiles,
761
770
  ...options.templateFiles,
762
- ]);
771
+ ])
763
772
  return {
764
773
  ...(hasLoadingBoundary ? { hasLoadingBoundary } : {}),
765
774
  ...(ancestorClientReferences.length > 0
766
775
  ? { ancestorClientReferences: ancestorClientReferences.map(reference => reference.id) }
767
776
  : {}),
768
- };
777
+ }
769
778
  }
770
779
 
771
780
  function hasStaticParamsExport(source: string) {
772
- const searchable = stripCommentsAndStrings(source);
781
+ const searchable = stripCommentsAndStrings(source)
773
782
  return (
774
783
  /\bexport\s+(?:async\s+)?function\s+(?:params|generateStaticParams)\b/.test(searchable) ||
775
784
  /\bexport\s+const\s+(?:params|generateStaticParams)\b/.test(searchable)
776
- );
785
+ )
777
786
  }
778
787
 
779
788
  /** All `@slot` directories on the walk from the app root to the route's dir. */
780
789
  function chainSlotDirs(appPath: string, routeFilePath: string) {
781
- const dirs: string[] = [];
782
- let dir = path.dirname(routeFilePath);
790
+ const dirs: string[] = []
791
+ let dir = path.dirname(routeFilePath)
783
792
  while (dir.startsWith(appPath)) {
784
- dirs.push(...slotDirectoriesIn(dir));
785
- if (dir === appPath) break;
786
- dir = path.dirname(dir);
793
+ dirs.push(...slotDirectoriesIn(dir))
794
+ if (dir === appPath) break
795
+ dir = path.dirname(dir)
787
796
  }
788
- return dirs.reverse();
797
+ return dirs.reverse()
789
798
  }
790
799
 
791
800
  /** Every convention file in the given slot trees (recursive). */
792
801
  function slotTreeFiles(slotDirs: string[]) {
793
- const files: string[] = [];
794
- const pattern = slotConventionPattern();
802
+ const files: string[] = []
803
+ const pattern = slotConventionPattern()
795
804
  for (const dir of slotDirs) {
796
805
  for (const file of listFilesSync(dir)) {
797
- if (pattern.test(toPosixPath(file))) files.push(file);
806
+ if (pattern.test(toPosixPath(file))) files.push(file)
798
807
  }
799
808
  }
800
- return files;
809
+ return files
801
810
  }
802
811
 
803
812
  /**
@@ -814,19 +823,19 @@ function appendSlotDerivedRoutes(
814
823
  slotPages: { file: string; relative: string }[],
815
824
  table: TableFlags,
816
825
  ) {
817
- const appPath = context.appPath;
818
- const patterns = new Set(routes.filter(route => !route.interception).map(route => route.pattern));
826
+ const appPath = context.appPath
827
+ const patterns = new Set(routes.filter(route => !route.interception).map(route => route.pattern))
819
828
  const interceptionKeys = new Set(
820
829
  routes
821
830
  .filter(route => route.interception)
822
831
  .map(route => `${route.pattern}#${route.interception!.base}`),
823
- );
832
+ )
824
833
 
825
834
  const candidates = slotPages
826
835
  .map(({ file, relative }) => {
827
- const segments = relative.split('/');
828
- const slotIndex = segments.findIndex(isSlotSegment);
829
- const slotDir = path.join(appPath, ...segments.slice(0, slotIndex + 1));
836
+ const segments = relative.split('/')
837
+ const slotIndex = segments.findIndex(isSlotSegment)
838
+ const slotDir = path.join(appPath, ...segments.slice(0, slotIndex + 1))
830
839
  return {
831
840
  file,
832
841
  relative,
@@ -836,18 +845,18 @@ function appendSlotDerivedRoutes(
836
845
  innerNames: segments
837
846
  .slice(slotIndex + 1, -1)
838
847
  .filter(name => !isSlotSegment(name) && !isGroupSegment(name)),
839
- };
848
+ }
840
849
  })
841
850
  // Deepest owner first: a URL reachable through several slots anchors its
842
851
  // layout chain at the most specific segment.
843
- .sort((a, b) => b.ownerDir.length - a.ownerDir.length);
852
+ .sort((a, b) => b.ownerDir.length - a.ownerDir.length)
844
853
 
845
854
  for (const candidate of candidates) {
846
- const { parts } = candidate;
855
+ const { parts } = candidate
847
856
  if (parts.interception) {
848
- const key = `${parts.pattern}#${parts.interception.base}`;
849
- if (interceptionKeys.has(key)) continue;
850
- interceptionKeys.add(key);
857
+ const key = `${parts.pattern}#${parts.interception.base}`
858
+ if (interceptionKeys.has(key)) continue
859
+ interceptionKeys.add(key)
851
860
  // Never rendered directly: a matching soft navigation renders the
852
861
  // CURRENT page (the from-path's entry) and this slot resolves the
853
862
  // interceptor during slot rendering. Kept minimal on purpose.
@@ -877,14 +886,14 @@ function appendSlotDerivedRoutes(
877
886
  }),
878
887
  }),
879
888
  ),
880
- );
881
- continue;
889
+ )
890
+ continue
882
891
  }
883
892
 
884
- if (patterns.has(parts.pattern)) continue;
885
- patterns.add(parts.pattern);
893
+ if (patterns.has(parts.pattern)) continue
894
+ patterns.add(parts.pattern)
886
895
 
887
- const childrenDefault = childrenDefaultFile(candidate.ownerDir, candidate.innerNames);
896
+ const childrenDefault = childrenDefaultFile(candidate.ownerDir, candidate.innerNames)
888
897
  // The anchor only positions the entry in the app tree (layouts, templates,
889
898
  // boundaries, slot chain). Prefer a real file: the children default, then an
890
899
  // explicit `@children` page, then the owner's own page. A non-existent
@@ -894,24 +903,22 @@ function appendSlotDerivedRoutes(
894
903
  childrenDefault ??
895
904
  slotConventionFile(path.join(candidate.ownerDir, '@children'), 'page') ??
896
905
  slotConventionFile(candidate.ownerDir, 'page') ??
897
- candidate.file;
898
- const layoutFiles = findLayouts(appPath, anchor);
899
- const templateFiles = findConventionFiles(appPath, anchor, 'template');
906
+ candidate.file
907
+ const layoutFiles = findLayouts(appPath, anchor)
908
+ const templateFiles = findConventionFiles(appPath, anchor, 'template')
900
909
  const specialFiles = boundaryConventions().flatMap(name =>
901
910
  findConventionFiles(appPath, anchor, name),
902
- );
903
- const slotDirs = chainSlotDirs(appPath, anchor);
904
- const slotFiles = slotTreeFiles(slotDirs);
905
- const extraBoundaries = new Set(extraBoundaryConventionNames());
906
- const forbiddenPaths =
907
- extraBoundaries.has('forbidden')
908
- ? findConventionFiles(appPath, anchor, 'forbidden').filter(item => existsSync(item))
909
- : [];
910
- const unauthorizedPaths =
911
- extraBoundaries.has('unauthorized')
912
- ? findConventionFiles(appPath, anchor, 'unauthorized').filter(item => existsSync(item))
913
- : [];
914
- const templatePaths = templateFiles.filter(item => existsSync(item));
911
+ )
912
+ const slotDirs = chainSlotDirs(appPath, anchor)
913
+ const slotFiles = slotTreeFiles(slotDirs)
914
+ const extraBoundaries = new Set(extraBoundaryConventionNames())
915
+ const forbiddenPaths = extraBoundaries.has('forbidden')
916
+ ? findConventionFiles(appPath, anchor, 'forbidden').filter(item => existsSync(item))
917
+ : []
918
+ const unauthorizedPaths = extraBoundaries.has('unauthorized')
919
+ ? findConventionFiles(appPath, anchor, 'unauthorized').filter(item => existsSync(item))
920
+ : []
921
+ const templatePaths = templateFiles.filter(item => existsSync(item))
915
922
 
916
923
  routes.push(
917
924
  withDeferredFacts(
@@ -944,7 +951,7 @@ function appendSlotDerivedRoutes(
944
951
  table,
945
952
  }),
946
953
  ),
947
- );
954
+ )
948
955
  }
949
956
  }
950
957
 
@@ -957,7 +964,7 @@ const emptyDerivedFacts = {
957
964
  clientReferences: [],
958
965
  cssImports: [],
959
966
  sourceFiles: [],
960
- } satisfies RouteFacts;
967
+ } satisfies RouteFacts
961
968
 
962
969
  /**
963
970
  * Content facts of a synthetic slot / default entry. Its tree is the convention
@@ -967,22 +974,22 @@ function derivedRouteFacts(
967
974
  context: ScanContext,
968
975
  options: {
969
976
  /** Files that lead the entry tree (children default) or ARE it (a default.tsx). */
970
- leadFiles: string[];
971
- layoutFiles: string[];
972
- templateFiles: string[];
973
- specialFiles: string[];
974
- slotFiles: string[];
977
+ leadFiles: string[]
978
+ layoutFiles: string[]
979
+ templateFiles: string[]
980
+ specialFiles: string[]
981
+ slotFiles: string[]
975
982
  /** File whose `'use client'` directive decides the entry's own `client` flag. */
976
- clientFile?: string;
983
+ clientFile?: string
977
984
  /** Lead file also participates in CSS order as the route file (default entries). */
978
- leadIsRouteFile?: boolean;
979
- table: TableFlags;
985
+ leadIsRouteFile?: boolean
986
+ table: TableFlags
980
987
  },
981
988
  ): RouteFacts {
982
- const { leadFiles, layoutFiles, templateFiles, specialFiles, slotFiles } = options;
983
- const entryFiles = [...leadFiles, ...layoutFiles, ...templateFiles, ...specialFiles, ...slotFiles];
984
- const sourceFiles = collectSourceFiles(context, entryFiles);
985
- const clientReferences = collectClientReferences(context, entryFiles);
989
+ const { leadFiles, layoutFiles, templateFiles, specialFiles, slotFiles } = options
990
+ const entryFiles = [...leadFiles, ...layoutFiles, ...templateFiles, ...specialFiles, ...slotFiles]
991
+ const sourceFiles = collectSourceFiles(context, entryFiles)
992
+ const clientReferences = collectClientReferences(context, entryFiles)
986
993
  const cssImports = collectCssImports(
987
994
  context,
988
995
  cssEntryOrder({
@@ -993,9 +1000,9 @@ function derivedRouteFacts(
993
1000
  slotFiles,
994
1001
  }),
995
1002
  { route: true },
996
- );
997
- attachLazyReferenceCss(context, clientReferences);
998
- const client = options.clientFile ? isClientModule(context, options.clientFile) : false;
1003
+ )
1004
+ attachLazyReferenceCss(context, clientReferences)
1005
+ const client = options.clientFile ? isClientModule(context, options.clientFile) : false
999
1006
 
1000
1007
  return {
1001
1008
  ...emptyDerivedFacts,
@@ -1011,7 +1018,7 @@ function derivedRouteFacts(
1011
1018
  slotInterceptor: false,
1012
1019
  table: options.table,
1013
1020
  }),
1014
- };
1021
+ }
1015
1022
  }
1016
1023
 
1017
1024
  /**
@@ -1029,35 +1036,33 @@ function appendDefaultDerivedRoutes(
1029
1036
  defaultPages: { file: string; relative: string }[],
1030
1037
  table: TableFlags,
1031
1038
  ) {
1032
- const appPath = context.appPath;
1033
- const patterns = new Set(routes.filter(route => !route.interception).map(route => route.pattern));
1039
+ const appPath = context.appPath
1040
+ const patterns = new Set(routes.filter(route => !route.interception).map(route => route.pattern))
1034
1041
 
1035
1042
  for (const { file, relative } of defaultPages) {
1036
- const dir = path.dirname(file);
1043
+ const dir = path.dirname(file)
1037
1044
  // A sibling page.tsx means the segment already has a normal entry.
1038
- if (conventionFileName(dir, 'page')) continue;
1039
- const parts = routeParts(relative.replace(pageOrRouteTrailing(), ''));
1040
- if (parts.interception) continue;
1041
- if (patterns.has(parts.pattern)) continue;
1042
- patterns.add(parts.pattern);
1043
-
1044
- const layoutFiles = findLayouts(appPath, file);
1045
- const templateFiles = findConventionFiles(appPath, file, 'template');
1045
+ if (conventionFileName(dir, 'page')) continue
1046
+ const parts = routeParts(relative.replace(pageOrRouteTrailing(), ''))
1047
+ if (parts.interception) continue
1048
+ if (patterns.has(parts.pattern)) continue
1049
+ patterns.add(parts.pattern)
1050
+
1051
+ const layoutFiles = findLayouts(appPath, file)
1052
+ const templateFiles = findConventionFiles(appPath, file, 'template')
1046
1053
  const specialFiles = boundaryConventions().flatMap(name =>
1047
1054
  findConventionFiles(appPath, file, name),
1048
- );
1049
- const slotDirs = chainSlotDirs(appPath, file);
1050
- const slotFiles = slotTreeFiles(slotDirs);
1051
- const extraBoundaries = new Set(extraBoundaryConventionNames());
1052
- const forbiddenPaths =
1053
- extraBoundaries.has('forbidden')
1054
- ? findConventionFiles(appPath, file, 'forbidden').filter(item => existsSync(item))
1055
- : [];
1056
- const unauthorizedPaths =
1057
- extraBoundaries.has('unauthorized')
1058
- ? findConventionFiles(appPath, file, 'unauthorized').filter(item => existsSync(item))
1059
- : [];
1060
- const templatePaths = templateFiles.filter(item => existsSync(item));
1055
+ )
1056
+ const slotDirs = chainSlotDirs(appPath, file)
1057
+ const slotFiles = slotTreeFiles(slotDirs)
1058
+ const extraBoundaries = new Set(extraBoundaryConventionNames())
1059
+ const forbiddenPaths = extraBoundaries.has('forbidden')
1060
+ ? findConventionFiles(appPath, file, 'forbidden').filter(item => existsSync(item))
1061
+ : []
1062
+ const unauthorizedPaths = extraBoundaries.has('unauthorized')
1063
+ ? findConventionFiles(appPath, file, 'unauthorized').filter(item => existsSync(item))
1064
+ : []
1065
+ const templatePaths = templateFiles.filter(item => existsSync(item))
1061
1066
 
1062
1067
  routes.push(
1063
1068
  withDeferredFacts(
@@ -1088,12 +1093,12 @@ function appendDefaultDerivedRoutes(
1088
1093
  table,
1089
1094
  }),
1090
1095
  ),
1091
- );
1096
+ )
1092
1097
  }
1093
1098
  }
1094
1099
 
1095
1100
  export function findLayouts(appPath: string, routeFilePath: string) {
1096
- return findConventionFiles(appPath, routeFilePath, 'layout');
1101
+ return findConventionFiles(appPath, routeFilePath, 'layout')
1097
1102
  }
1098
1103
 
1099
1104
  /**
@@ -1105,9 +1110,9 @@ export function findLayouts(appPath: string, routeFilePath: string) {
1105
1110
  * Returns [] when the app has no root not-found file (built-in fallback).
1106
1111
  */
1107
1112
  export async function collectNotFoundCss(appPath: string): Promise<string[]> {
1108
- const notFoundFile = conventionFileName(appPath, 'not-found');
1109
- if (!notFoundFile) return [];
1110
- return collectFileCss(appPath, [notFoundFile]);
1113
+ const notFoundFile = conventionFileName(appPath, 'not-found')
1114
+ if (!notFoundFile) return []
1115
+ return collectFileCss(appPath, [notFoundFile])
1111
1116
  }
1112
1117
 
1113
1118
  /**
@@ -1123,13 +1128,13 @@ export async function collectFileCss(
1123
1128
  files: string[],
1124
1129
  options: { includeGlobalCss?: boolean } = {},
1125
1130
  ): Promise<string[]> {
1126
- const existing = files.filter(file => existsSync(file));
1127
- if (existing.length === 0) return [];
1131
+ const existing = files.filter(file => existsSync(file))
1132
+ if (existing.length === 0) return []
1128
1133
  const context = createScanContext(
1129
1134
  appPath,
1130
1135
  options.includeGlobalCss ? () => new Set<string>() : undefined,
1131
- );
1132
- return collectCssImports(context, existing);
1136
+ )
1137
+ return collectCssImports(context, existing)
1133
1138
  }
1134
1139
 
1135
1140
  /**
@@ -1140,52 +1145,52 @@ export async function collectFileCss(
1140
1145
  * byte-identical behavior.
1141
1146
  */
1142
1147
  export function findConventionFiles(appPath: string, routeFilePath: string, name: string) {
1143
- const base = conventionBase(name);
1144
- const files: string[] = [];
1145
- let dir = path.dirname(routeFilePath);
1148
+ const base = conventionBase(name)
1149
+ const files: string[] = []
1150
+ let dir = path.dirname(routeFilePath)
1146
1151
 
1147
1152
  while (dir.startsWith(appPath)) {
1148
- files.push(conventionFileName(dir, base) ?? path.join(dir, `${base}.tsx`));
1149
- if (dir === appPath) break;
1150
- dir = path.dirname(dir);
1153
+ files.push(conventionFileName(dir, base) ?? path.join(dir, `${base}.tsx`))
1154
+ if (dir === appPath) break
1155
+ dir = path.dirname(dir)
1151
1156
  }
1152
1157
 
1153
- return files.reverse();
1158
+ return files.reverse()
1154
1159
  }
1155
1160
 
1156
1161
  function conventionBase(name: string) {
1157
- const match = new RegExp(`\\.(${pageExtensions().join('|')})$`).exec(name);
1158
- return match ? name.slice(0, -match[0].length) : name;
1162
+ const match = new RegExp(`\\.(${pageExtensions().join('|')})$`).exec(name)
1163
+ return match ? name.slice(0, -match[0].length) : name
1159
1164
  }
1160
1165
 
1161
1166
  /** App-root `global-error.*` file, if present (BuildManifest.globalErrorFile). */
1162
1167
  export function findGlobalError(appPath: string) {
1163
- return conventionFileName(appPath, globalErrorBase);
1168
+ return conventionFileName(appPath, globalErrorBase)
1164
1169
  }
1165
1170
 
1166
1171
  export function matchRoute(routes: RouteManifestEntry[], pathname: string) {
1167
1172
  // Trailing-slash tolerance: '/route/' matches the same entry as '/route'
1168
1173
  // (Next redirects these away; static file serving already accepts both).
1169
- const normalized = normalizePathname(pathname);
1174
+ const normalized = normalizePathname(pathname)
1170
1175
  for (const route of routes) {
1171
1176
  // Interception entries never match a plain (hard) request; they apply
1172
1177
  // only through matchInterception on soft navigations.
1173
- if (route.interception) continue;
1174
- const match = routeRegex(route).exec(normalized);
1175
- if (!match) continue;
1176
- return { route, params: routeMatchParams(route, match) };
1178
+ if (route.interception) continue
1179
+ const match = routeRegex(route).exec(normalized)
1180
+ if (!match) continue
1181
+ return { route, params: routeMatchParams(route, match) }
1177
1182
  }
1178
- return null;
1183
+ return null
1179
1184
  }
1180
1185
 
1181
1186
  export function normalizePathname(pathname: string) {
1182
- return pathname.length > 1 ? pathname.replace(/\/+$/, '') || '/' : pathname;
1187
+ return pathname.length > 1 ? pathname.replace(/\/+$/, '') || '/' : pathname
1183
1188
  }
1184
1189
 
1185
1190
  function routeMatchParams(route: RouteManifestEntry, match: RegExpExecArray) {
1186
- const params: Record<string, RouteParamValue> = {};
1191
+ const params: Record<string, RouteParamValue> = {}
1187
1192
  route.params.forEach((name, index) => {
1188
- const decoded = safeDecode(match[index + 1] ?? '');
1193
+ const decoded = safeDecode(match[index + 1] ?? '')
1189
1194
  // A runtime request whose segment IS the dynamic placeholder for its own
1190
1195
  // param (`[slug]`, whether sent raw or as `%5Bslug%5D`) is Next's "params
1191
1196
  // placeholder": it must surface the ENCODED placeholder (`%5Bslug%5D`), not
@@ -1194,20 +1199,20 @@ function routeMatchParams(route: RouteManifestEntry, match: RegExpExecArray) {
1194
1199
  // boundary); Next keeps it URL-encoded to render at runtime instead.
1195
1200
  // Fallback-shell generation injects its placeholder params through a
1196
1201
  // separate build path, so this only affects live requests.
1197
- params[name] = decoded === `[${name}]` ? encodeURIComponent(decoded) : decoded;
1198
- });
1202
+ params[name] = decoded === `[${name}]` ? encodeURIComponent(decoded) : decoded
1203
+ })
1199
1204
  if (route.catchAll) {
1200
1205
  params[route.catchAll] = (match[route.params.length + 1] ?? '')
1201
1206
  .split('/')
1202
1207
  .filter(Boolean)
1203
- .map(safeDecode);
1208
+ .map(safeDecode)
1204
1209
  }
1205
- return params;
1210
+ return params
1206
1211
  }
1207
1212
 
1208
1213
  export interface InterceptionRouteMatch {
1209
- route: RouteManifestEntry;
1210
- params: Record<string, RouteParamValue>;
1214
+ route: RouteManifestEntry
1215
+ params: Record<string, RouteParamValue>
1211
1216
  }
1212
1217
 
1213
1218
  /**
@@ -1222,34 +1227,34 @@ export function matchInterception(
1222
1227
  pathname: string,
1223
1228
  fromPath: string | undefined,
1224
1229
  ): InterceptionRouteMatch | null {
1225
- if (!fromPath) return null;
1226
- const target = normalizePathname(pathname);
1227
- const from = normalizePathname(fromPath);
1228
- if (target === from) return null;
1229
- let best: InterceptionRouteMatch | null = null;
1230
- let bestDepth = -1;
1230
+ if (!fromPath) return null
1231
+ const target = normalizePathname(pathname)
1232
+ const from = normalizePathname(fromPath)
1233
+ if (target === from) return null
1234
+ let best: InterceptionRouteMatch | null = null
1235
+ let bestDepth = -1
1231
1236
  for (const route of routes) {
1232
- const interception = route.interception;
1233
- if (!interception) continue;
1234
- const match = routeRegex(route).exec(target);
1235
- if (!match) continue;
1236
- const basePattern = interception.basePattern ? `/${interception.basePattern}` : '';
1237
- if (!new RegExp(`^${basePattern}(?:/.*)?$`).test(from)) continue;
1238
- const depth = interception.base.split('/').filter(Boolean).length;
1239
- if (depth <= bestDepth) continue;
1240
- bestDepth = depth;
1241
- best = { route, params: routeMatchParams(route, match) };
1237
+ const interception = route.interception
1238
+ if (!interception) continue
1239
+ const match = routeRegex(route).exec(target)
1240
+ if (!match) continue
1241
+ const basePattern = interception.basePattern ? `/${interception.basePattern}` : ''
1242
+ if (!new RegExp(`^${basePattern}(?:/.*)?$`).test(from)) continue
1243
+ const depth = interception.base.split('/').filter(Boolean).length
1244
+ if (depth <= bestDepth) continue
1245
+ bestDepth = depth
1246
+ best = { route, params: routeMatchParams(route, match) }
1242
1247
  }
1243
- return best;
1248
+ return best
1244
1249
  }
1245
1250
 
1246
1251
  export interface RouteRenderSelection {
1247
- route: RouteManifestEntry;
1248
- params: Record<string, RouteParamValue>;
1252
+ route: RouteManifestEntry
1253
+ params: Record<string, RouteParamValue>
1249
1254
  /** Pathname the children tree renders from (normalized). */
1250
- childrenPath: string;
1255
+ childrenPath: string
1251
1256
  /** Internal pathname after rewrites, used to resolve interception slots. */
1252
- targetPath: string;
1257
+ targetPath: string
1253
1258
  }
1254
1259
 
1255
1260
  /**
@@ -1263,9 +1268,9 @@ export function selectRouteForRequest(
1263
1268
  pathname: string,
1264
1269
  nav?: import('../types').NavState,
1265
1270
  ): RouteRenderSelection | null {
1266
- const target = normalizePathname(pathname);
1271
+ const target = normalizePathname(pathname)
1267
1272
  if (nav) {
1268
- const intercepted = matchInterception(routes, target, nav.children);
1273
+ const intercepted = matchInterception(routes, target, nav.children)
1269
1274
  if (intercepted) {
1270
1275
  if (!intercepted.route.interception?.slotDir) {
1271
1276
  return {
@@ -1273,22 +1278,24 @@ export function selectRouteForRequest(
1273
1278
  params: intercepted.params,
1274
1279
  childrenPath: target,
1275
1280
  targetPath: target,
1276
- };
1281
+ }
1277
1282
  }
1278
- const host = hostMatch(routes, nav.children);
1279
- if (host) return { ...host, targetPath: target };
1283
+ const host = hostMatch(routes, nav.children)
1284
+ if (host) return { ...host, targetPath: target }
1280
1285
  }
1281
1286
  }
1282
- const matched = matchRoute(routes, target);
1283
- if (!matched) return null;
1287
+ const matched = matchRoute(routes, target)
1288
+ if (!matched) return null
1284
1289
  if (nav && matched.route.synthetic && nav.children) {
1285
- const primary = matchRoute(routes.filter(route => !route.synthetic), target);
1290
+ const primary = matchRoute(
1291
+ routes.filter(route => !route.synthetic),
1292
+ target,
1293
+ )
1286
1294
  const syntheticOwner = matched.route.syntheticSlotDir
1287
1295
  ? path.dirname(matched.route.syntheticSlotDir)
1288
- : undefined;
1289
- const primaryFromOwner = primary && syntheticOwner
1290
- ? path.relative(syntheticOwner, primary.route.file)
1291
- : undefined;
1296
+ : undefined
1297
+ const primaryFromOwner =
1298
+ primary && syntheticOwner ? path.relative(syntheticOwner, primary.route.file) : undefined
1292
1299
  if (
1293
1300
  primary &&
1294
1301
  primaryFromOwner !== undefined &&
@@ -1300,16 +1307,16 @@ export function selectRouteForRequest(
1300
1307
  params: primary.params,
1301
1308
  childrenPath: target,
1302
1309
  targetPath: target,
1303
- };
1310
+ }
1304
1311
  }
1305
- const host = hostMatch(routes, nav.children);
1312
+ const host = hostMatch(routes, nav.children)
1306
1313
  if (
1307
1314
  host &&
1308
1315
  host.route !== matched.route &&
1309
1316
  matched.route.syntheticSlotDir &&
1310
1317
  host.route.slotDirs?.includes(matched.route.syntheticSlotDir)
1311
1318
  ) {
1312
- return { ...host, targetPath: target };
1319
+ return { ...host, targetPath: target }
1313
1320
  }
1314
1321
  }
1315
1322
  return {
@@ -1317,7 +1324,7 @@ export function selectRouteForRequest(
1317
1324
  params: matched.params,
1318
1325
  childrenPath: target,
1319
1326
  targetPath: target,
1320
- };
1327
+ }
1321
1328
  }
1322
1329
 
1323
1330
  /**
@@ -1326,135 +1333,133 @@ export function selectRouteForRequest(
1326
1333
  * parallel-route state (URI-encoded JSON).
1327
1334
  */
1328
1335
  export function parseNavState(request: Request): NavState | undefined {
1329
- if (!request.headers.get('x-pnext-soft-nav')) return undefined;
1330
- const raw = request.headers.get('x-pnext-nav-state');
1331
- if (!raw) return {};
1336
+ if (!request.headers.get('x-pnext-soft-nav')) return undefined
1337
+ const raw = request.headers.get('x-pnext-nav-state')
1338
+ if (!raw) return {}
1332
1339
  try {
1333
- const parsed = JSON.parse(decodeURIComponent(raw)) as unknown;
1334
- if (parsed && typeof parsed === 'object') return parsed;
1340
+ const parsed = JSON.parse(decodeURIComponent(raw)) as unknown
1341
+ if (parsed && typeof parsed === 'object') return parsed
1335
1342
  } catch {
1336
1343
  // Malformed state degrades to a plain soft navigation.
1337
1344
  }
1338
- return {};
1345
+ return {}
1339
1346
  }
1340
1347
 
1341
1348
  function hostMatch(
1342
1349
  routes: RouteManifestEntry[],
1343
1350
  fromPath: string | undefined,
1344
1351
  ): RouteRenderSelection | null {
1345
- if (!fromPath) return null;
1346
- const from = normalizePathname(fromPath);
1347
- const host = matchRoute(routes, from);
1348
- if (host?.route.kind !== 'page') return null;
1349
- return { route: host.route, params: host.params, childrenPath: from, targetPath: from };
1352
+ if (!fromPath) return null
1353
+ const from = normalizePathname(fromPath)
1354
+ const host = matchRoute(routes, from)
1355
+ if (host?.route.kind !== 'page') return null
1356
+ return { route: host.route, params: host.params, childrenPath: from, targetPath: from }
1350
1357
  }
1351
1358
 
1352
- const routeRegexCache = new WeakMap<RouteManifestEntry, RegExp>();
1359
+ const routeRegexCache = new WeakMap<RouteManifestEntry, RegExp>()
1353
1360
 
1354
1361
  function routeRegex(route: RouteManifestEntry) {
1355
- let regex = routeRegexCache.get(route);
1362
+ let regex = routeRegexCache.get(route)
1356
1363
  if (!regex) {
1357
- regex = new RegExp(`^${route.pattern}$`);
1358
- routeRegexCache.set(route, regex);
1364
+ regex = new RegExp(`^${route.pattern}$`)
1365
+ routeRegexCache.set(route, regex)
1359
1366
  }
1360
- return regex;
1367
+ return regex
1361
1368
  }
1362
1369
 
1363
1370
  interface RouteSegmentToken {
1364
- route: string;
1371
+ route: string
1365
1372
  /** Regex piece; absent for the optional catch-all (assembled specially). */
1366
- pattern?: string;
1367
- param?: string;
1368
- catchAll?: string;
1369
- catchAllOptional?: boolean;
1373
+ pattern?: string
1374
+ param?: string
1375
+ catchAll?: string
1376
+ catchAllOptional?: boolean
1370
1377
  }
1371
1378
 
1372
1379
  function segmentTokenOf(bare: string): RouteSegmentToken {
1373
- const optionalCatchAllMatch = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(bare);
1380
+ const optionalCatchAllMatch = /^\[\[\.\.\.([^\]]+)\]\]$/.exec(bare)
1374
1381
  if (optionalCatchAllMatch?.[1]) {
1375
- const name = optionalCatchAllMatch[1];
1376
- return { route: `:${name}*`, catchAll: name, catchAllOptional: true };
1382
+ const name = optionalCatchAllMatch[1]
1383
+ return { route: `:${name}*`, catchAll: name, catchAllOptional: true }
1377
1384
  }
1378
- const catchAllMatch = /^\[\.\.\.([^\]]+)\]$/.exec(bare);
1385
+ const catchAllMatch = /^\[\.\.\.([^\]]+)\]$/.exec(bare)
1379
1386
  if (catchAllMatch?.[1]) {
1380
- return { route: `:${catchAllMatch[1]}*`, pattern: '(.*)', catchAll: catchAllMatch[1] };
1387
+ return { route: `:${catchAllMatch[1]}*`, pattern: '(.*)', catchAll: catchAllMatch[1] }
1381
1388
  }
1382
- const paramMatch = /^\[([^\]]+)\]$/.exec(bare);
1389
+ const paramMatch = /^\[([^\]]+)\]$/.exec(bare)
1383
1390
  if (paramMatch?.[1]) {
1384
- return { route: `:${paramMatch[1]}`, pattern: '([^/]+)', param: paramMatch[1] };
1391
+ return { route: `:${paramMatch[1]}`, pattern: '([^/]+)', param: paramMatch[1] }
1385
1392
  }
1386
- const literal = decodeLiteralSegment(bare);
1387
- return { route: literal, pattern: escapeRegex(literal) };
1393
+ const literal = decodeLiteralSegment(bare)
1394
+ return { route: literal, pattern: escapeRegex(literal) }
1388
1395
  }
1389
1396
 
1390
1397
  // A path is private when any of its directory segments begins with `_`, except
1391
1398
  // route groups `(name)`, slots `@slot`, and `%5F`-encoded segments (which
1392
1399
  // decode to a routable leading underscore). The filename segment is excluded.
1393
1400
  function isPrivateAppPath(relative: string): boolean {
1394
- const segments = relative.split('/');
1401
+ const segments = relative.split('/')
1395
1402
  return segments
1396
1403
  .slice(0, -1)
1397
- .some(
1398
- segment => segment.startsWith('_') && !isGroupSegment(segment) && !isSlotSegment(segment),
1399
- );
1404
+ .some(segment => segment.startsWith('_') && !isGroupSegment(segment) && !isSlotSegment(segment))
1400
1405
  }
1401
1406
 
1402
1407
  // A literal (non-dynamic) path segment: `%5F` decodes to `_` so a directory
1403
1408
  // named `%5Ffoo` on disk routes as `/_foo`. Other percent escapes are left
1404
1409
  // as-is (the router decodes params at match time).
1405
1410
  function decodeLiteralSegment(segment: string): string {
1406
- return segment.replace(/%5[Ff]/g, '_');
1411
+ return segment.replace(/%5[Ff]/g, '_')
1407
1412
  }
1408
1413
 
1409
1414
  function routeParts(routeDir: string): RouteParts {
1410
- const segments = routeDir.split('/').filter(Boolean);
1411
- let tokens: RouteSegmentToken[] = [];
1412
- let interception: RouteParts['interception'];
1415
+ const segments = routeDir.split('/').filter(Boolean)
1416
+ let tokens: RouteSegmentToken[] = []
1417
+ let interception: RouteParts['interception']
1413
1418
 
1414
1419
  for (const segment of segments) {
1415
1420
  // Route group `(name)`: organizational only. `@slot`: no path segment.
1416
- if (isGroupSegment(segment) || isSlotSegment(segment)) continue;
1421
+ if (isGroupSegment(segment) || isSlotSegment(segment)) continue
1417
1422
 
1418
- const marker = interceptionMarkerOf(segment);
1419
- let bare = segment;
1423
+ const marker = interceptionMarkerOf(segment)
1424
+ let bare = segment
1420
1425
  if (marker) {
1421
1426
  // The interception target rewinds `levels` URL segments from the
1422
1427
  // marker's position; the segments before the marker form the base the
1423
1428
  // interceptor renders within.
1424
- bare = segment.slice(marker.length);
1425
- const base = tokens.slice();
1426
- const levels = interceptionMarkerLevels(marker);
1429
+ bare = segment.slice(marker.length)
1430
+ const base = tokens.slice()
1431
+ const levels = interceptionMarkerLevels(marker)
1427
1432
  tokens =
1428
1433
  levels === Number.POSITIVE_INFINITY
1429
1434
  ? []
1430
- : tokens.slice(0, Math.max(0, tokens.length - levels));
1435
+ : tokens.slice(0, Math.max(0, tokens.length - levels))
1431
1436
  interception = {
1432
1437
  marker,
1433
1438
  base: `/${base.map(token => token.route).join('/')}`.replace(/\/$/, '') || '/',
1434
1439
  basePattern: base.map(token => token.pattern ?? '(.*)').join('/'),
1435
- };
1440
+ }
1436
1441
  }
1437
1442
 
1438
- tokens.push(segmentTokenOf(bare));
1443
+ tokens.push(segmentTokenOf(bare))
1439
1444
  }
1440
1445
 
1441
- const params = tokens.filter(token => token.param).map(token => token.param!);
1442
- const catchAllToken = tokens.find(token => token.catchAll);
1443
- const routeSegments = tokens.map(token => token.route);
1446
+ const params = tokens.filter(token => token.param).map(token => token.param!)
1447
+ const catchAllToken = tokens.find(token => token.catchAll)
1448
+ const routeSegments = tokens.map(token => token.route)
1444
1449
  const patternSegments = tokens
1445
1450
  .filter(token => !token.catchAllOptional)
1446
- .map(token => token.pattern!);
1451
+ .map(token => token.pattern!)
1447
1452
 
1448
- const route = `/${routeSegments.join('/')}`.replace(/\/$/, '') || '/';
1449
- let pattern: string;
1453
+ const route = `/${routeSegments.join('/')}`.replace(/\/$/, '') || '/'
1454
+ let pattern: string
1450
1455
  if (catchAllToken?.catchAllOptional) {
1451
1456
  // `[[...slug]]` matches the base path and any nested path: the whole
1452
1457
  // trailing group (including its leading slash) is optional.
1453
- const prefix = patternSegments.length > 0 ? `/${patternSegments.join('/')}` : '';
1454
- pattern = `${prefix}(?:/(.*))?`;
1455
- if (pattern === '(?:/(.*))?') pattern = '/?(?:(.*))?';
1458
+ const prefix = patternSegments.length > 0 ? `/${patternSegments.join('/')}` : ''
1459
+ pattern = `${prefix}(?:/(.*))?`
1460
+ if (pattern === '(?:/(.*))?') pattern = '/?(?:(.*))?'
1456
1461
  } else {
1457
- pattern = route === '/' ? '/' : `/${patternSegments.join('/')}`;
1462
+ pattern = route === '/' ? '/' : `/${patternSegments.join('/')}`
1458
1463
  }
1459
1464
  return {
1460
1465
  route,
@@ -1463,59 +1468,59 @@ function routeParts(routeDir: string): RouteParts {
1463
1468
  catchAll: catchAllToken?.catchAll,
1464
1469
  catchAllOptional: Boolean(catchAllToken?.catchAllOptional),
1465
1470
  interception,
1466
- };
1471
+ }
1467
1472
  }
1468
1473
 
1469
1474
  interface DynamicMetadataRouteParts extends RouteParts {
1470
- metadataRoute: MetadataRouteEntry;
1475
+ metadataRoute: MetadataRouteEntry
1471
1476
  }
1472
1477
 
1473
1478
  function dynamicMetadataRouteParts(
1474
1479
  relative: string,
1475
1480
  source: string,
1476
1481
  ): DynamicMetadataRouteParts | undefined {
1477
- const name = path.basename(relative);
1478
- const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative);
1479
- const root = dir === '';
1480
- const extension = path.extname(name);
1481
- const base = name.slice(0, -extension.length);
1482
- const kind = dynamicMetadataKind(base, root);
1483
- if (!kind) return undefined;
1484
-
1485
- const prefix = routeParts(dir);
1486
- const generatedParam = metadataGeneratedParam(kind, source);
1487
- const final = dynamicMetadataFinalSegment(kind, base, dir, Boolean(generatedParam));
1482
+ const name = path.basename(relative)
1483
+ const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative)
1484
+ const root = dir === ''
1485
+ const extension = path.extname(name)
1486
+ const base = name.slice(0, -extension.length)
1487
+ const kind = dynamicMetadataKind(base, root)
1488
+ if (!kind) return undefined
1489
+
1490
+ const prefix = routeParts(dir)
1491
+ const generatedParam = metadataGeneratedParam(kind, source)
1492
+ const final = dynamicMetadataFinalSegment(kind, base, dir, Boolean(generatedParam))
1488
1493
  return {
1489
1494
  ...appendRouteParts(prefix, final.route, final.pattern, generatedParam ? [generatedParam] : []),
1490
1495
  metadataRoute: {
1491
1496
  kind,
1492
1497
  ...(generatedParam ? { generatedParam } : {}),
1493
1498
  },
1494
- };
1499
+ }
1495
1500
  }
1496
1501
 
1497
1502
  function dynamicMetadataKind(base: string, root: boolean): MetadataRouteKind | undefined {
1498
- if (base === 'robots') return root ? 'robots' : undefined;
1499
- if (base === 'sitemap') return 'sitemap';
1500
- if (base === 'manifest') return root ? 'manifest' : undefined;
1501
- if (/^icon\d*$/.test(base)) return 'icon';
1502
- if (/^apple-icon\d*$/.test(base)) return 'apple-icon';
1503
- if (/^opengraph-image\d*$/.test(base)) return 'opengraph-image';
1504
- if (/^twitter-image\d*$/.test(base)) return 'twitter-image';
1505
- return undefined;
1503
+ if (base === 'robots') return root ? 'robots' : undefined
1504
+ if (base === 'sitemap') return 'sitemap'
1505
+ if (base === 'manifest') return root ? 'manifest' : undefined
1506
+ if (/^icon\d*$/.test(base)) return 'icon'
1507
+ if (/^apple-icon\d*$/.test(base)) return 'apple-icon'
1508
+ if (/^opengraph-image\d*$/.test(base)) return 'opengraph-image'
1509
+ if (/^twitter-image\d*$/.test(base)) return 'twitter-image'
1510
+ return undefined
1506
1511
  }
1507
1512
 
1508
1513
  function metadataGeneratedParam(kind: MetadataRouteKind, source: string) {
1509
1514
  if (kind === 'sitemap' && /\bexport\s+(?:async\s+)?function\s+generateSitemaps\b/.test(source)) {
1510
- return '__metadata_id__';
1515
+ return '__metadata_id__'
1511
1516
  }
1512
1517
  if (
1513
1518
  metadataImageKind(kind) &&
1514
1519
  /\bexport\s+(?:async\s+)?function\s+generateImageMetadata\b/.test(source)
1515
1520
  ) {
1516
- return '__metadata_id__';
1521
+ return '__metadata_id__'
1517
1522
  }
1518
- return undefined;
1523
+ return undefined
1519
1524
  }
1520
1525
 
1521
1526
  function dynamicMetadataFinalSegment(
@@ -1524,31 +1529,31 @@ function dynamicMetadataFinalSegment(
1524
1529
  relativeDir: string,
1525
1530
  generated: boolean,
1526
1531
  ) {
1527
- if (kind === 'robots') return { route: 'robots.txt', pattern: 'robots\\.txt' };
1532
+ if (kind === 'robots') return { route: 'robots.txt', pattern: 'robots\\.txt' }
1528
1533
  if (kind === 'manifest')
1529
- return { route: 'manifest.webmanifest', pattern: 'manifest\\.webmanifest' };
1534
+ return { route: 'manifest.webmanifest', pattern: 'manifest\\.webmanifest' }
1530
1535
  if (kind === 'sitemap') {
1531
1536
  return generated
1532
1537
  ? { route: 'sitemap/:id.xml', pattern: 'sitemap/([^/]+)\\.xml' }
1533
- : { route: 'sitemap.xml', pattern: 'sitemap\\.xml' };
1538
+ : { route: 'sitemap.xml', pattern: 'sitemap\\.xml' }
1534
1539
  }
1535
1540
 
1536
- const suffix = metadataRouteSuffix(relativeDir);
1537
- const imageBase = suffix ? `${base}-${suffix}` : base;
1541
+ const suffix = metadataRouteSuffix(relativeDir)
1542
+ const imageBase = suffix ? `${base}-${suffix}` : base
1538
1543
  return generated
1539
1544
  ? { route: `${imageBase}/:id`, pattern: `${escapeRegex(imageBase)}/([^/]+)` }
1540
- : { route: imageBase, pattern: escapeRegex(imageBase) };
1545
+ : { route: imageBase, pattern: escapeRegex(imageBase) }
1541
1546
  }
1542
1547
 
1543
1548
  function appendRouteParts(parts: RouteParts, route: string, pattern: string, params: string[]) {
1544
- const prefixRoute = parts.route === '/' ? '' : parts.route;
1545
- const prefixPattern = parts.pattern === '/' ? '' : parts.pattern;
1549
+ const prefixRoute = parts.route === '/' ? '' : parts.route
1550
+ const prefixPattern = parts.pattern === '/' ? '' : parts.pattern
1546
1551
  return {
1547
1552
  ...parts,
1548
1553
  route: `${prefixRoute}/${route}`.replace(/\/$/, '') || '/',
1549
1554
  pattern: `${prefixPattern}/${pattern}`,
1550
1555
  params: [...parts.params, ...params],
1551
- };
1556
+ }
1552
1557
  }
1553
1558
 
1554
1559
  function metadataImageKind(kind: MetadataRouteKind) {
@@ -1557,28 +1562,28 @@ function metadataImageKind(kind: MetadataRouteKind) {
1557
1562
  kind === 'apple-icon' ||
1558
1563
  kind === 'opengraph-image' ||
1559
1564
  kind === 'twitter-image'
1560
- );
1565
+ )
1561
1566
  }
1562
1567
 
1563
1568
  function metadataRouteUsesRequest(source: string) {
1564
1569
  // routeHandlerUsesRequest already consults the compat usageDetection seam
1565
1570
  // (next/headers, next/server connection(), ...).
1566
- return routeHandlerUsesRequest(source);
1571
+ return routeHandlerUsesRequest(source)
1567
1572
  }
1568
1573
 
1569
1574
  function metadataRouteSuffix(relativeDir: string) {
1570
- if (!relativeDir) return '';
1571
- const segments = relativeDir.split('/').filter(Boolean);
1572
- if (!segments.some(segment => isGroupSegment(segment) || isSlotSegment(segment))) return '';
1573
- return djb2Hash(`/${relativeDir}`).toString(36).slice(0, 6);
1575
+ if (!relativeDir) return ''
1576
+ const segments = relativeDir.split('/').filter(Boolean)
1577
+ if (!segments.some(segment => isGroupSegment(segment) || isSlotSegment(segment))) return ''
1578
+ return djb2Hash(`/${relativeDir}`).toString(36).slice(0, 6)
1574
1579
  }
1575
1580
 
1576
1581
  function djb2Hash(value: string) {
1577
- let hash = 5381;
1582
+ let hash = 5381
1578
1583
  for (let i = 0; i < value.length; i++) {
1579
- hash = ((hash << 5) + hash + value.charCodeAt(i)) & 0xffffffff;
1584
+ hash = ((hash << 5) + hash + value.charCodeAt(i)) & 0xffffffff
1580
1585
  }
1581
- return hash >>> 0;
1586
+ return hash >>> 0
1582
1587
  }
1583
1588
 
1584
1589
  function inferRouteMode(
@@ -1586,72 +1591,72 @@ function inferRouteMode(
1586
1591
  usesRequest: boolean,
1587
1592
  config?: RouteSegmentConfig,
1588
1593
  ): RouteMode {
1589
- if (config?.dynamic === 'force-dynamic') return 'dynamic';
1590
- if (config?.revalidate === 0) return 'dynamic';
1591
- if (config?.fetchCache === 'force-no-store') return 'dynamic';
1592
- if (config?.dynamic === 'force-static') return 'static';
1593
- if (config?.dynamic === 'error') return 'static';
1594
- if (usesRequest) return 'dynamic';
1595
- if (parts.params.length > 0 || parts.catchAll) return 'dynamic';
1596
- return 'static';
1594
+ if (config?.dynamic === 'force-dynamic') return 'dynamic'
1595
+ if (config?.revalidate === 0) return 'dynamic'
1596
+ if (config?.fetchCache === 'force-no-store') return 'dynamic'
1597
+ if (config?.dynamic === 'force-static') return 'static'
1598
+ if (config?.dynamic === 'error') return 'static'
1599
+ if (usesRequest) return 'dynamic'
1600
+ if (parts.params.length > 0 || parts.catchAll) return 'dynamic'
1601
+ return 'static'
1597
1602
  }
1598
1603
 
1599
1604
  interface ParsedSegmentConfig {
1600
- dynamic?: RouteSegmentConfig['dynamic'];
1601
- revalidate?: number | false;
1602
- fetchCache?: RouteSegmentConfig['fetchCache'];
1603
- dynamicParams?: boolean;
1604
- runtime?: string;
1605
- prefetch?: RouteSegmentConfig['prefetch'];
1606
- unstableInstant?: boolean;
1605
+ dynamic?: RouteSegmentConfig['dynamic']
1606
+ revalidate?: number | false
1607
+ fetchCache?: RouteSegmentConfig['fetchCache']
1608
+ dynamicParams?: boolean
1609
+ runtime?: string
1610
+ prefetch?: RouteSegmentConfig['prefetch']
1611
+ unstableInstant?: boolean
1607
1612
  /** Literal but invalid `export const revalidate` value (build error in Next). */
1608
- invalidRevalidate?: string;
1613
+ invalidRevalidate?: string
1609
1614
  }
1610
1615
 
1611
1616
  function parseSegmentConfig(source: string): ParsedSegmentConfig {
1612
- const searchable = stripComments(source);
1613
- const parsed: ParsedSegmentConfig = {};
1617
+ const searchable = stripComments(source)
1618
+ const parsed: ParsedSegmentConfig = {}
1614
1619
  const dynamicMatch =
1615
1620
  /^\s*export\s+const\s+dynamic\s*=\s*['"](auto|force-dynamic|error|force-static)['"]/m.exec(
1616
1621
  searchable,
1617
- );
1618
- if (dynamicMatch) parsed.dynamic = dynamicMatch[1] as RouteSegmentConfig['dynamic'];
1622
+ )
1623
+ if (dynamicMatch) parsed.dynamic = dynamicMatch[1] as RouteSegmentConfig['dynamic']
1619
1624
  const revalidateMatch = /^\s*export\s+const\s+revalidate\s*=\s*(\d+|false)\s*;?\s*$/m.exec(
1620
1625
  searchable,
1621
- );
1626
+ )
1622
1627
  if (revalidateMatch) {
1623
- parsed.revalidate = revalidateMatch[1] === 'false' ? false : Number(revalidateMatch[1]);
1628
+ parsed.revalidate = revalidateMatch[1] === 'false' ? false : Number(revalidateMatch[1])
1624
1629
  } else {
1625
1630
  // Invalid literal values (strings, negatives) are a build error in Next.
1626
1631
  const invalid = /^\s*export\s+const\s+revalidate\s*=\s*(['"]([^'"]*)['"]|-\d+)/m.exec(
1627
1632
  searchable,
1628
- );
1629
- if (invalid) parsed.invalidRevalidate = invalid[2] ?? invalid[1];
1633
+ )
1634
+ if (invalid) parsed.invalidRevalidate = invalid[2] ?? invalid[1]
1630
1635
  }
1631
1636
  const fetchCacheMatch =
1632
1637
  /^\s*export\s+const\s+fetchCache\s*=\s*['"](auto|default-cache|only-cache|force-cache|force-no-store|default-no-store|only-no-store)['"]/m.exec(
1633
1638
  searchable,
1634
- );
1635
- if (fetchCacheMatch) parsed.fetchCache = fetchCacheMatch[1] as RouteSegmentConfig['fetchCache'];
1639
+ )
1640
+ if (fetchCacheMatch) parsed.fetchCache = fetchCacheMatch[1] as RouteSegmentConfig['fetchCache']
1636
1641
  const dynamicParamsMatch = /^\s*export\s+const\s+dynamicParams\s*=\s*(true|false)/m.exec(
1637
1642
  searchable,
1638
- );
1639
- if (dynamicParamsMatch) parsed.dynamicParams = dynamicParamsMatch[1] === 'true';
1640
- const runtimeMatch = /^\s*export\s+const\s+runtime\s*=\s*['"](edge|nodejs)['"]/m.exec(searchable);
1641
- if (runtimeMatch) parsed.runtime = runtimeMatch[1];
1643
+ )
1644
+ if (dynamicParamsMatch) parsed.dynamicParams = dynamicParamsMatch[1] === 'true'
1645
+ const runtimeMatch = /^\s*export\s+const\s+runtime\s*=\s*['"](edge|nodejs)['"]/m.exec(searchable)
1646
+ if (runtimeMatch) parsed.runtime = runtimeMatch[1]
1642
1647
  const prefetchMatch =
1643
1648
  /^\s*export\s+const\s+prefetch\s*=\s*['"](allow-runtime|partial|unstable_eager)['"]/m.exec(
1644
1649
  searchable,
1645
- );
1646
- if (prefetchMatch) parsed.prefetch = prefetchMatch[1] as RouteSegmentConfig['prefetch'];
1650
+ )
1651
+ if (prefetchMatch) parsed.prefetch = prefetchMatch[1] as RouteSegmentConfig['prefetch']
1647
1652
  // `unstable_instant = true | false | {…samples}` — an object literal opts in
1648
1653
  // like `true` (its samples refine the runtime-prefetch render, they don't
1649
1654
  // gate it).
1650
1655
  const instantMatch = /^\s*export\s+const\s+unstable_instant\s*=\s*(true|false|\{)/m.exec(
1651
1656
  searchable,
1652
- );
1653
- if (instantMatch) parsed.unstableInstant = instantMatch[1] !== 'false';
1654
- return parsed;
1657
+ )
1658
+ if (instantMatch) parsed.unstableInstant = instantMatch[1] !== 'false'
1659
+ return parsed
1655
1660
  }
1656
1661
 
1657
1662
  /**
@@ -1670,54 +1675,54 @@ function segmentConfigFromEntries(
1670
1675
  entries: { file: string; source: string }[],
1671
1676
  parts: RouteParts,
1672
1677
  ): RouteSegmentConfig | undefined {
1673
- const config: RouteSegmentConfig = {};
1674
- let lowestRevalidate: number | undefined;
1675
- let sawRevalidateFalse = false;
1676
- const dynamicParamsByName = new Map<string, boolean>();
1677
- const allParams = [...parts.params, ...(parts.catchAll ? [parts.catchAll] : [])];
1678
+ const config: RouteSegmentConfig = {}
1679
+ let lowestRevalidate: number | undefined
1680
+ let sawRevalidateFalse = false
1681
+ const dynamicParamsByName = new Map<string, boolean>()
1682
+ const allParams = [...parts.params, ...(parts.catchAll ? [parts.catchAll] : [])]
1678
1683
 
1679
1684
  for (const entry of entries) {
1680
- const parsed = parseSegmentConfig(entry.source);
1685
+ const parsed = parseSegmentConfig(entry.source)
1681
1686
  if (parsed.invalidRevalidate !== undefined) {
1682
1687
  throw new Error(
1683
1688
  `Invalid revalidate value "${parsed.invalidRevalidate}" on "${parts.route || '/'}", must be a non-negative number or false`,
1684
- );
1689
+ )
1685
1690
  }
1686
1691
  if (parsed.dynamic) {
1687
- if (parsed.dynamic === 'force-dynamic') config.dynamic = 'force-dynamic';
1688
- else if (!config.dynamic) config.dynamic = parsed.dynamic;
1692
+ if (parsed.dynamic === 'force-dynamic') config.dynamic = 'force-dynamic'
1693
+ else if (!config.dynamic) config.dynamic = parsed.dynamic
1689
1694
  }
1690
1695
  if (parsed.revalidate !== undefined) {
1691
- if (parsed.revalidate === false) sawRevalidateFalse = true;
1696
+ if (parsed.revalidate === false) sawRevalidateFalse = true
1692
1697
  else {
1693
1698
  lowestRevalidate =
1694
1699
  lowestRevalidate === undefined
1695
1700
  ? parsed.revalidate
1696
- : Math.min(lowestRevalidate, parsed.revalidate);
1701
+ : Math.min(lowestRevalidate, parsed.revalidate)
1697
1702
  }
1698
1703
  }
1699
- if (parsed.fetchCache && !config.fetchCache) config.fetchCache = parsed.fetchCache;
1700
- if (parsed.runtime && !config.runtime) config.runtime = parsed.runtime;
1701
- if (parsed.prefetch && !config.prefetch) config.prefetch = parsed.prefetch;
1704
+ if (parsed.fetchCache && !config.fetchCache) config.fetchCache = parsed.fetchCache
1705
+ if (parsed.runtime && !config.runtime) config.runtime = parsed.runtime
1706
+ if (parsed.prefetch && !config.prefetch) config.prefetch = parsed.prefetch
1702
1707
  // Leaf-most declaration wins (entries are page-first): a page's explicit
1703
1708
  // `unstable_instant = false` overrides a layout's opt-in.
1704
1709
  if (parsed.unstableInstant !== undefined && config.unstableInstant === undefined) {
1705
- config.unstableInstant = parsed.unstableInstant;
1710
+ config.unstableInstant = parsed.unstableInstant
1706
1711
  }
1707
1712
  if (parsed.dynamicParams !== undefined) {
1708
- const param = paramOfOwnSegment(appPath, entry.file, allParams);
1713
+ const param = paramOfOwnSegment(appPath, entry.file, allParams)
1709
1714
  if (param !== undefined && !dynamicParamsByName.has(param)) {
1710
- dynamicParamsByName.set(param, parsed.dynamicParams);
1715
+ dynamicParamsByName.set(param, parsed.dynamicParams)
1711
1716
  }
1712
1717
  }
1713
1718
  }
1714
1719
 
1715
- if (lowestRevalidate !== undefined) config.revalidate = lowestRevalidate;
1716
- else if (sawRevalidateFalse) config.revalidate = false;
1717
- const governed = allParams.filter(param => dynamicParamsByName.get(param) === false);
1718
- if (governed.length > 0) config.dynamicParamsFalse = governed;
1720
+ if (lowestRevalidate !== undefined) config.revalidate = lowestRevalidate
1721
+ else if (sawRevalidateFalse) config.revalidate = false
1722
+ const governed = allParams.filter(param => dynamicParamsByName.get(param) === false)
1723
+ if (governed.length > 0) config.dynamicParamsFalse = governed
1719
1724
 
1720
- return Object.keys(config).length > 0 ? config : undefined;
1725
+ return Object.keys(config).length > 0 ? config : undefined
1721
1726
  }
1722
1727
 
1723
1728
  /**
@@ -1726,32 +1731,32 @@ function segmentConfigFromEntries(
1726
1731
  * `locale` but not a `[slug]` below it).
1727
1732
  */
1728
1733
  function paramOfOwnSegment(appPath: string, file: string, params: string[]) {
1729
- const segments = toPosixPath(path.relative(appPath, path.dirname(file))).split('/');
1730
- const own = segments[segments.length - 1];
1734
+ const segments = toPosixPath(path.relative(appPath, path.dirname(file))).split('/')
1735
+ const own = segments[segments.length - 1]
1731
1736
  return params.find(
1732
1737
  param => own === `[${param}]` || own === `[...${param}]` || own === `[[...${param}]]`,
1733
- );
1738
+ )
1734
1739
  }
1735
1740
 
1736
1741
  // The parse is memoized per source, so the thousands of directive checks a
1737
1742
  // scan makes cost one parse per file.
1738
1743
  function hasUseClientDirective(file: string, source: string) {
1739
- return scanFacts(file, source).useClient;
1744
+ return scanFacts(file, source).useClient
1740
1745
  }
1741
1746
 
1742
1747
  function isClientModule(context: ScanContext, file: string) {
1743
- const key = path.resolve(file);
1744
- let client = context.useClient.get(key);
1748
+ const key = path.resolve(file)
1749
+ let client = context.useClient.get(key)
1745
1750
  if (client === undefined) {
1746
- client = hasUseClientDirective(file, readSource(context, file));
1747
- context.useClient.set(key, client);
1751
+ client = hasUseClientDirective(file, readSource(context, file))
1752
+ context.useClient.set(key, client)
1748
1753
  }
1749
- return client;
1754
+ return client
1750
1755
  }
1751
1756
 
1752
1757
  function collectClientReferences(context: ScanContext, entryFiles: string[]) {
1753
- const references = new Map<string, ClientReference>();
1754
- const visited = new Set<string>();
1758
+ const references = new Map<string, ClientReference>()
1759
+ const visited = new Set<string>()
1755
1760
 
1756
1761
  for (const file of entryFiles) {
1757
1762
  // A convention module may be a bare re-export of a client module
@@ -1761,23 +1766,23 @@ function collectClientReferences(context: ScanContext, entryFiles: string[]) {
1761
1766
  // an island (the client bundle resolves the re-export chain naturally).
1762
1767
  if (fileExists(context, file)) {
1763
1768
  if (!isClientModule(context, file) && defaultReexportsClientModule(context, file)) {
1764
- addClientReference(references, clientReference(file, 'default'));
1769
+ addClientReference(references, clientReference(file, 'default'))
1765
1770
  }
1766
1771
  }
1767
- collectFromFile(context, file, references, visited);
1772
+ collectFromFile(context, file, references, visited)
1768
1773
  }
1769
1774
 
1770
- const collected = [...references.values()];
1775
+ const collected = [...references.values()]
1771
1776
  // Webpack eagerly initializes side-effect-only client modules (a bare `import './client-only'`) ONLY once
1772
1777
  // at least one client reference actually renders. A route whose sole client references are
1773
1778
  // side-effect-only executes nothing on the client, so drop them - otherwise their side effects would
1774
1779
  // wrongly run.
1775
- const hasRenderedReference = collected.some(reference => !reference.sideEffect);
1780
+ const hasRenderedReference = collected.some(reference => !reference.sideEffect)
1776
1781
  const filtered = hasRenderedReference
1777
1782
  ? collected
1778
- : collected.filter(reference => !reference.sideEffect);
1783
+ : collected.filter(reference => !reference.sideEffect)
1779
1784
 
1780
- return filtered.sort((a, b) => a.id.localeCompare(b.id));
1785
+ return filtered.sort((a, b) => a.id.localeCompare(b.id))
1781
1786
  }
1782
1787
 
1783
1788
  /**
@@ -1792,34 +1797,34 @@ function defaultReexportsClientModule(
1792
1797
  file: string,
1793
1798
  visited = new Set<string>(),
1794
1799
  ): boolean {
1795
- const key = path.resolve(file);
1796
- if (visited.has(key)) return false;
1797
- visited.add(key);
1798
- const source = readSource(context, file);
1799
- const specifiers: string[] = [];
1800
+ const key = path.resolve(file)
1801
+ if (visited.has(key)) return false
1802
+ visited.add(key)
1803
+ const source = readSource(context, file)
1804
+ const specifiers: string[] = []
1800
1805
  for (const edge of scanFacts(file, source).imports) {
1801
1806
  // `export *` never carries a default, so only the named form counts.
1802
- if (edge.reexport && edge.exports.includes('default')) specifiers.push(edge.specifier);
1807
+ if (edge.reexport && edge.exports.includes('default')) specifiers.push(edge.specifier)
1803
1808
  }
1804
1809
  // `import X from 'spec'; export default X` and the namespace form
1805
1810
  // `import * as NS from 'spec'; export default NS.default` (the pages-compat
1806
1811
  // materializer emits the latter) re-export a default just the same.
1807
- const defaultExpr = /export\s+default\s+([A-Za-z_$][\w$]*)(\s*\.\s*default)?\s*;?/.exec(source);
1812
+ const defaultExpr = /export\s+default\s+([A-Za-z_$][\w$]*)(\s*\.\s*default)?\s*;?/.exec(source)
1808
1813
  if (defaultExpr) {
1809
- const binding = defaultExpr[1]!;
1810
- const namespaced = Boolean(defaultExpr[2]);
1814
+ const binding = defaultExpr[1]!
1815
+ const namespaced = Boolean(defaultExpr[2])
1811
1816
  const importBinding = namespaced
1812
1817
  ? new RegExp(`import\\s*\\*\\s*as\\s+${binding}\\s+from\\s+['"]([^'"]+)['"]`).exec(source)
1813
- : new RegExp(`import\\s+${binding}\\s+from\\s+['"]([^'"]+)['"]`).exec(source);
1814
- if (importBinding?.[1]) specifiers.push(importBinding[1]);
1818
+ : new RegExp(`import\\s+${binding}\\s+from\\s+['"]([^'"]+)['"]`).exec(source)
1819
+ if (importBinding?.[1]) specifiers.push(importBinding[1])
1815
1820
  }
1816
1821
  for (const specifier of specifiers) {
1817
- const resolved = resolveModuleEdge(context.root, file, specifier);
1818
- if (!resolved || !fileExists(context, resolved)) continue;
1819
- if (isClientModule(context, resolved)) return true;
1820
- if (defaultReexportsClientModule(context, resolved, visited)) return true;
1822
+ const resolved = resolveModuleEdge(context.root, file, specifier)
1823
+ if (!resolved || !fileExists(context, resolved)) continue
1824
+ if (isClientModule(context, resolved)) return true
1825
+ if (defaultReexportsClientModule(context, resolved, visited)) return true
1821
1826
  }
1822
- return false;
1827
+ return false
1823
1828
  }
1824
1829
 
1825
1830
  function collectFromFile(
@@ -1828,11 +1833,11 @@ function collectFromFile(
1828
1833
  references: Map<string, ClientReference>,
1829
1834
  visited: Set<string>,
1830
1835
  ) {
1831
- if (visited.has(file) || !fileExists(context, file)) return;
1832
- visited.add(file);
1836
+ if (visited.has(file) || !fileExists(context, file)) return
1837
+ visited.add(file)
1833
1838
 
1834
1839
  if (isClientModule(context, file)) {
1835
- addClientReference(references, clientReference(file, 'default'));
1840
+ addClientReference(references, clientReference(file, 'default'))
1836
1841
  }
1837
1842
 
1838
1843
  for (const imported of moduleEdges(context, file)) {
@@ -1840,18 +1845,18 @@ function collectFromFile(
1840
1845
  // `export * from './client'` re-exports the target's named exports (never
1841
1846
  // its default), so each of them is a client reference of this module.
1842
1847
  if (imported.star) {
1843
- const importedSource = readSource(context, imported.file);
1848
+ const importedSource = readSource(context, imported.file)
1844
1849
  for (const exportName of scanFacts(imported.file, importedSource).exportNames) {
1845
1850
  if (exportName !== 'default') {
1846
- addClientReference(references, clientReference(imported.file, exportName));
1851
+ addClientReference(references, clientReference(imported.file, exportName))
1847
1852
  }
1848
1853
  }
1849
- continue;
1854
+ continue
1850
1855
  }
1851
1856
  if (imported.exports.includes('*')) {
1852
1857
  throw new Error(
1853
1858
  `${file} namespace-imports Client Component module ${imported.file}. Import the client component by default or named export instead.`,
1854
- );
1859
+ )
1855
1860
  }
1856
1861
  // A bare side-effect import binds no exports, but the module still belongs in the client bundle:
1857
1862
  // webpack eagerly initializes every client reference module once any of them renders, so its top-level
@@ -1861,36 +1866,36 @@ function collectFromFile(
1861
1866
  addClientReference(references, {
1862
1867
  ...clientReference(imported.file, '*side-effect*'),
1863
1868
  sideEffect: true,
1864
- });
1865
- continue;
1869
+ })
1870
+ continue
1866
1871
  }
1867
1872
  for (const exportName of imported.exports) {
1868
1873
  addClientReference(references, {
1869
1874
  ...clientReference(imported.file, exportName),
1870
1875
  dynamic: imported.dynamic,
1871
- });
1876
+ })
1872
1877
  }
1873
- continue;
1878
+ continue
1874
1879
  }
1875
1880
 
1876
1881
  if (imported.dynamic?.load === 'visible') {
1877
1882
  console.warn(
1878
1883
  `${file} uses dynamic({ load: 'visible' }) for ${imported.file}, but the target is a Server Component. It will render on the server instead of loading on visibility.`,
1879
- );
1884
+ )
1880
1885
  }
1881
1886
 
1882
- collectFromFile(context, imported.file, references, visited);
1887
+ collectFromFile(context, imported.file, references, visited)
1883
1888
  }
1884
1889
  }
1885
1890
 
1886
1891
  function addClientReference(references: Map<string, ClientReference>, reference: ClientReference) {
1887
- const existing = references.get(reference.id);
1892
+ const existing = references.get(reference.id)
1888
1893
  if (!existing) {
1889
- references.set(reference.id, reference);
1890
- return;
1894
+ references.set(reference.id, reference)
1895
+ return
1891
1896
  }
1892
1897
 
1893
- if (!reference.dynamic) references.set(reference.id, reference);
1898
+ if (!reference.dynamic) references.set(reference.id, reference)
1894
1899
  }
1895
1900
 
1896
1901
  // CSS cascade order across a route's entry files: outer->inner layouts, then
@@ -1899,12 +1904,12 @@ function addClientReference(references: Map<string, ClientReference>, reference:
1899
1904
  // so a layout's stylesheet is emitted before (and thus loses specificity ties
1900
1905
  // to) the page's.
1901
1906
  function cssEntryOrder(options: {
1902
- routeFile?: string;
1903
- layoutFiles: string[];
1904
- templateFiles: string[];
1905
- specialFiles: string[];
1906
- slotFiles: string[];
1907
- leadFiles?: string[];
1907
+ routeFile?: string
1908
+ layoutFiles: string[]
1909
+ templateFiles: string[]
1910
+ specialFiles: string[]
1911
+ slotFiles: string[]
1912
+ leadFiles?: string[]
1908
1913
  }) {
1909
1914
  return [
1910
1915
  ...(options.leadFiles ?? []),
@@ -1913,7 +1918,7 @@ function cssEntryOrder(options: {
1913
1918
  ...(options.routeFile ? [options.routeFile] : []),
1914
1919
  ...options.specialFiles,
1915
1920
  ...options.slotFiles,
1916
- ];
1921
+ ]
1917
1922
  }
1918
1923
 
1919
1924
  function collectCssImports(
@@ -1921,46 +1926,47 @@ function collectCssImports(
1921
1926
  entryFiles: string[],
1922
1927
  options: { route?: boolean } = {},
1923
1928
  ) {
1924
- const imports = new Set<string>();
1925
- const visited = new Set<string>();
1929
+ const imports = new Set<string>()
1930
+ const visited = new Set<string>()
1926
1931
 
1927
1932
  for (const file of entryFiles) {
1928
- collectCssFromFile(context, file, imports, visited);
1933
+ collectCssFromFile(context, file, imports, visited)
1929
1934
  }
1930
1935
 
1931
1936
  // Preserve source import order (a Set keeps insertion order): the emitted
1932
1937
  // stylesheet's cascade depends on it — layouts outer->inner, then page, then
1933
1938
  // components in import order. Sorting alphabetically would scramble the
1934
1939
  // cascade and flip which rule wins.
1935
- if (!options.route || !getCssExtensions().deferRootNotFoundCss()) return [...imports];
1940
+ if (!options.route || !getCssExtensions().deferRootNotFoundCss()) return [...imports]
1936
1941
 
1937
1942
  const rootNotFound = entryFiles.find(
1938
- file => path.dirname(file) === context.appPath && /^not-found\.[^.]+$/.test(path.basename(file)),
1939
- );
1940
- if (!rootNotFound) return [...imports];
1943
+ file =>
1944
+ path.dirname(file) === context.appPath && /^not-found\.[^.]+$/.test(path.basename(file)),
1945
+ )
1946
+ if (!rootNotFound) return [...imports]
1941
1947
 
1942
- const rootNotFoundImports = new Set<string>();
1943
- collectCssFromFile(context, rootNotFound, rootNotFoundImports, new Set());
1944
- if (rootNotFoundImports.size === 0) return [...imports];
1948
+ const rootNotFoundImports = new Set<string>()
1949
+ collectCssFromFile(context, rootNotFound, rootNotFoundImports, new Set())
1950
+ if (rootNotFoundImports.size === 0) return [...imports]
1945
1951
 
1946
- const matchedImports = new Set<string>();
1952
+ const matchedImports = new Set<string>()
1947
1953
  for (const file of entryFiles) {
1948
- if (file === rootNotFound) continue;
1949
- collectCssFromFile(context, file, matchedImports, new Set());
1954
+ if (file === rootNotFound) continue
1955
+ collectCssFromFile(context, file, matchedImports, new Set())
1950
1956
  }
1951
1957
 
1952
- return [...imports].filter(file => !rootNotFoundImports.has(file) || matchedImports.has(file));
1958
+ return [...imports].filter(file => !rootNotFoundImports.has(file) || matchedImports.has(file))
1953
1959
  }
1954
1960
 
1955
1961
  function collectSourceFiles(context: ScanContext, entryFiles: string[]) {
1956
- const files = new Set<string>();
1957
- const visited = new Set<string>();
1962
+ const files = new Set<string>()
1963
+ const visited = new Set<string>()
1958
1964
 
1959
1965
  for (const file of entryFiles) {
1960
- collectSourceFile(context, file, files, visited);
1966
+ collectSourceFile(context, file, files, visited)
1961
1967
  }
1962
1968
 
1963
- return [...files].sort();
1969
+ return [...files].sort()
1964
1970
  }
1965
1971
 
1966
1972
  function collectSourceFile(
@@ -1969,13 +1975,13 @@ function collectSourceFile(
1969
1975
  files: Set<string>,
1970
1976
  visited: Set<string>,
1971
1977
  ) {
1972
- if (visited.has(file) || !fileExists(context, file)) return;
1973
- visited.add(file);
1974
- files.add(file);
1975
- if (isAssetFile(file)) return;
1978
+ if (visited.has(file) || !fileExists(context, file)) return
1979
+ visited.add(file)
1980
+ files.add(file)
1981
+ if (isAssetFile(file)) return
1976
1982
 
1977
1983
  for (const imported of moduleEdges(context, file)) {
1978
- collectSourceFile(context, imported.file, files, visited);
1984
+ collectSourceFile(context, imported.file, files, visited)
1979
1985
  }
1980
1986
  }
1981
1987
 
@@ -1985,41 +1991,41 @@ function collectCssFromFile(
1985
1991
  imports: Set<string>,
1986
1992
  visited: Set<string>,
1987
1993
  ) {
1988
- if (visited.has(file) || !fileExists(context, file)) return;
1989
- visited.add(file);
1994
+ if (visited.has(file) || !fileExists(context, file)) return
1995
+ visited.add(file)
1990
1996
 
1991
1997
  if (isCssFile(file)) {
1992
- if (!context.globalCssImports().has(path.resolve(file))) imports.add(file);
1993
- return;
1998
+ if (!context.globalCssImports().has(path.resolve(file))) imports.add(file)
1999
+ return
1994
2000
  }
1995
2001
 
1996
- const clientFile = isClientModule(context, file);
2002
+ const clientFile = isClientModule(context, file)
1997
2003
  for (const imported of moduleEdges(context, file)) {
1998
2004
  if (isCssFile(imported.file)) {
1999
- if (!context.globalCssImports().has(path.resolve(imported.file))) imports.add(imported.file);
2000
- continue;
2005
+ if (!context.globalCssImports().has(path.resolve(imported.file))) imports.add(imported.file)
2006
+ continue
2001
2007
  }
2002
2008
 
2003
2009
  // CSS behind a non-SSR island boundary ships with the island
2004
2010
  // (assets/<reference-id>.css) instead of the route chunk, so it loads
2005
2011
  // when the component does. Boundaries only exist in server files:
2006
2012
  // dynamic() inside a client subtree mounts without the island runtime.
2007
- if (!clientFile && (isLazyClientEdge(context, imported))) continue;
2013
+ if (!clientFile && isLazyClientEdge(context, imported)) continue
2008
2014
 
2009
- collectCssFromFile(context, imported.file, imports, visited);
2015
+ collectCssFromFile(context, imported.file, imports, visited)
2010
2016
  }
2011
2017
  }
2012
2018
 
2013
2019
  function isLazyClientEdge(context: ScanContext, edge: ModuleEdge) {
2014
- if (!edge.dynamic || ssrClientReference({ dynamic: edge.dynamic })) return false;
2015
- return isClientModule(context, edge.file);
2020
+ if (!edge.dynamic || ssrClientReference({ dynamic: edge.dynamic })) return false
2021
+ return isClientModule(context, edge.file)
2016
2022
  }
2017
2023
 
2018
2024
  function attachLazyReferenceCss(context: ScanContext, references: ClientReference[]) {
2019
2025
  for (const reference of references) {
2020
- if (ssrClientReference(reference)) continue;
2021
- const css = collectCssImports(context, [reference.file]);
2022
- if (css.length > 0) reference.cssImports = css;
2026
+ if (ssrClientReference(reference)) continue
2027
+ const css = collectCssImports(context, [reference.file])
2028
+ if (css.length > 0) reference.cssImports = css
2023
2029
  }
2024
2030
  }
2025
2031
 
@@ -2032,41 +2038,37 @@ function attachLazyReferenceCss(context: ScanContext, references: ClientReferenc
2032
2038
  * scans would miss it and, e.g., prerender a revalidateTag route handler.
2033
2039
  * Relative specifiers only: bare imports never carry segment config.
2034
2040
  */
2035
- function readScanSource(
2036
- context: ScanContext,
2037
- file: string,
2038
- visited = new Set<string>(),
2039
- ): string {
2040
- const key = path.resolve(file);
2041
- if (visited.has(key)) return '';
2042
- visited.add(key);
2043
- const source = readSource(context, file);
2044
- const parts = [source];
2041
+ function readScanSource(context: ScanContext, file: string, visited = new Set<string>()): string {
2042
+ const key = path.resolve(file)
2043
+ if (visited.has(key)) return ''
2044
+ visited.add(key)
2045
+ const source = readSource(context, file)
2046
+ const parts = [source]
2045
2047
  for (const edge of scanFacts(file, source).imports) {
2046
- if (!edge.reexport || !edge.specifier.startsWith('.')) continue;
2047
- const resolved = resolveModuleEdge(context.root, file, edge.specifier);
2048
- if (!resolved || !existsSync(resolved)) continue;
2049
- parts.push(readScanSource(context, resolved, visited));
2048
+ if (!edge.reexport || !edge.specifier.startsWith('.')) continue
2049
+ const resolved = resolveModuleEdge(context.root, file, edge.specifier)
2050
+ if (!resolved || !existsSync(resolved)) continue
2051
+ parts.push(readScanSource(context, resolved, visited))
2050
2052
  }
2051
- return parts.join('\n');
2053
+ return parts.join('\n')
2052
2054
  }
2053
2055
 
2054
2056
  function readSource(context: ScanContext, file: string) {
2055
- const key = path.resolve(file);
2056
- let source = context.sources.get(key);
2057
+ const key = path.resolve(file)
2058
+ let source = context.sources.get(key)
2057
2059
  if (source === undefined) {
2058
2060
  // Raw text comes from the build-scoped cache (a no-op outside a build), so
2059
2061
  // the client loader and action discovery don't re-read what this walk reads.
2060
- source = rewriteLiteralDynamicCalls(readSourceSync(key), file);
2061
- context.sources.set(key, source);
2062
+ source = rewriteLiteralDynamicCalls(readSourceSync(key), file)
2063
+ context.sources.set(key, source)
2062
2064
  }
2063
- return source;
2065
+ return source
2064
2066
  }
2065
2067
 
2066
2068
  // True when any of the route's source files imports a Link component
2067
2069
  // (next/link in compat, or the core @wular/pnext/link). Matches the module
2068
2070
  // specifier so it survives aliasing/renamed imports of the default export.
2069
- const linkSpecifiers = new Set(['next/link', '@wular/pnext/link']);
2071
+ const linkSpecifiers = new Set(['next/link', '@wular/pnext/link'])
2070
2072
 
2071
2073
  // Every module specifier through which a route's graph can reach a soft
2072
2074
  // navigation: Link, the client router hooks, and compat's navigation surface.
@@ -2079,7 +2081,7 @@ const navigationSpecifiers = new Set([
2079
2081
  'next/router',
2080
2082
  '@wular/pnext/navigation',
2081
2083
  '@wular/pnext/navigation/client',
2082
- ]);
2084
+ ])
2083
2085
 
2084
2086
  // The root `@wular/pnext` barrel also re-exports navigation: a NAMED import of
2085
2087
  // one of these counts, anything else (types, cache, config helpers) does not.
@@ -2097,72 +2099,72 @@ const rootNavigationExports = new Set([
2097
2099
  'useRoute',
2098
2100
  'useRouter',
2099
2101
  'useSearchParams',
2100
- ]);
2102
+ ])
2101
2103
 
2102
2104
  function importReachesNavigation(edge: { specifier: string; exports: string[] }) {
2103
- if (navigationSpecifiers.has(edge.specifier)) return true;
2104
- if (edge.specifier !== '@wular/pnext') return false;
2105
- return edge.exports.length === 0 || edge.exports.some(name => rootNavigationExports.has(name));
2105
+ if (navigationSpecifiers.has(edge.specifier)) return true
2106
+ if (edge.specifier !== '@wular/pnext') return false
2107
+ return edge.exports.length === 0 || edge.exports.some(name => rootNavigationExports.has(name))
2106
2108
  }
2107
2109
 
2108
2110
  function routeUsesLinkComponent(context: ScanContext, files: string[]) {
2109
2111
  for (const file of files) {
2110
- if (!fileExists(context, file) || isAssetFile(file)) continue;
2111
- const key = path.resolve(file);
2112
- let uses = context.usesLink.get(key);
2112
+ if (!fileExists(context, file) || isAssetFile(file)) continue
2113
+ const key = path.resolve(file)
2114
+ let uses = context.usesLink.get(key)
2113
2115
  if (uses === undefined) {
2114
- const facts = scanFacts(file, readSource(context, file));
2115
- uses = facts.imports.some(edge => linkSpecifiers.has(edge.specifier));
2116
- context.usesLink.set(key, uses);
2116
+ const facts = scanFacts(file, readSource(context, file))
2117
+ uses = facts.imports.some(edge => linkSpecifiers.has(edge.specifier))
2118
+ context.usesLink.set(key, uses)
2117
2119
  }
2118
- if (uses) return true;
2120
+ if (uses) return true
2119
2121
  }
2120
- return false;
2122
+ return false
2121
2123
  }
2122
2124
 
2123
2125
  /** True when any file in the route's closure can reach a soft navigation. */
2124
2126
  function routeUsesNavigation(context: ScanContext, files: string[]) {
2125
2127
  for (const file of files) {
2126
- if (!fileExists(context, file) || isAssetFile(file)) continue;
2127
- const key = path.resolve(file);
2128
- let uses = context.usesNavigation.get(key);
2128
+ if (!fileExists(context, file) || isAssetFile(file)) continue
2129
+ const key = path.resolve(file)
2130
+ let uses = context.usesNavigation.get(key)
2129
2131
  if (uses === undefined) {
2130
- const facts = scanFacts(file, readSource(context, file));
2131
- uses = facts.imports.some(importReachesNavigation);
2132
- context.usesNavigation.set(key, uses);
2132
+ const facts = scanFacts(file, readSource(context, file))
2133
+ uses = facts.imports.some(importReachesNavigation)
2134
+ context.usesNavigation.set(key, uses)
2133
2135
  }
2134
- if (uses) return true;
2136
+ if (uses) return true
2135
2137
  }
2136
- return false;
2138
+ return false
2137
2139
  }
2138
2140
 
2139
2141
  function routeClientEntryReasons(context: ScanContext, files: string[]): ClientEntryReason[] {
2140
- const reasons: ClientEntryReason[] = [];
2142
+ const reasons: ClientEntryReason[] = []
2141
2143
  for (const file of files) {
2142
- if (!fileExists(context, file) || isAssetFile(file)) continue;
2143
- const key = path.resolve(file);
2144
- let fileReasons = context.entryReasons.get(key);
2144
+ if (!fileExists(context, file) || isAssetFile(file)) continue
2145
+ const key = path.resolve(file)
2146
+ let fileReasons = context.entryReasons.get(key)
2145
2147
  if (fileReasons === undefined) {
2146
- fileReasons = sourceClientEntryReasons(readSource(context, file));
2147
- context.entryReasons.set(key, fileReasons);
2148
+ fileReasons = sourceClientEntryReasons(readSource(context, file))
2149
+ context.entryReasons.set(key, fileReasons)
2148
2150
  }
2149
- reasons.push(...fileReasons);
2151
+ reasons.push(...fileReasons)
2150
2152
  }
2151
- return reasons;
2153
+ return reasons
2152
2154
  }
2153
2155
 
2154
2156
  function usesRequestImportDataForFiles(context: ScanContext, files: string[]) {
2155
2157
  for (const file of files) {
2156
- const key = path.resolve(file);
2157
- let usesRequestImport = context.requestImportUsage.get(key);
2158
+ const key = path.resolve(file)
2159
+ let usesRequestImport = context.requestImportUsage.get(key)
2158
2160
  if (usesRequestImport === undefined) {
2159
- usesRequestImport = sourceUsesRegisteredRequestApi(readSource(context, file));
2160
- context.requestImportUsage.set(key, usesRequestImport);
2161
+ usesRequestImport = sourceUsesRegisteredRequestApi(readSource(context, file))
2162
+ context.requestImportUsage.set(key, usesRequestImport)
2161
2163
  }
2162
- if (usesRequestImport) return true;
2164
+ if (usesRequestImport) return true
2163
2165
  }
2164
2166
 
2165
- return false;
2167
+ return false
2166
2168
  }
2167
2169
 
2168
2170
  function routeDependencyClassification(
@@ -2172,18 +2174,18 @@ function routeDependencyClassification(
2172
2174
  ) {
2173
2175
  const sources = files
2174
2176
  .filter(file => !isAssetFile(file))
2175
- .map(file => ({ file, source: readSource(context, file) }));
2176
- return classifyRouteDependencies({ kind, files: sources });
2177
+ .map(file => ({ file, source: readSource(context, file) }))
2178
+ return classifyRouteDependencies({ kind, files: sources })
2177
2179
  }
2178
2180
 
2179
2181
  function moduleEdges(context: ScanContext, file: string) {
2180
- const key = path.resolve(file);
2181
- let edges = context.edges.get(key);
2182
+ const key = path.resolve(file)
2183
+ let edges = context.edges.get(key)
2182
2184
  if (!edges) {
2183
- edges = moduleEdgesFromSource(context.root, file, readSource(context, file));
2184
- context.edges.set(key, edges);
2185
+ edges = moduleEdgesFromSource(context.root, file, readSource(context, file))
2186
+ context.edges.set(key, edges)
2185
2187
  }
2186
- return edges;
2188
+ return edges
2187
2189
  }
2188
2190
 
2189
2191
  // Compat pins framework specifiers (`next/form`, ...) to its own absolute files, and the bundler's
@@ -2199,55 +2201,55 @@ function resolveModuleEdge(root: string, file: string, specifier: string) {
2199
2201
  getBundlerExtensions().resolveRouteDependency(root, file, specifier) ??
2200
2202
  resolveScopedClientBoundaryPackage(root, file, specifier) ??
2201
2203
  getCssExtensions().resolveCssDependency(root, file, specifier)
2202
- );
2204
+ )
2203
2205
  }
2204
2206
 
2205
- const scopedClientBoundaryPackages = new Set(['@next/third-parties']);
2207
+ const scopedClientBoundaryPackages = new Set(['@next/third-parties'])
2206
2208
 
2207
2209
  function resolveScopedClientBoundaryPackage(root: string, file: string, specifier: string) {
2208
- if (!scopedClientBoundaryPackages.has(packageNameOfSpecifier(specifier) ?? '')) return undefined;
2209
- return resolvePackageSpecifier(root, file, specifier, ['import', 'default']);
2210
+ if (!scopedClientBoundaryPackages.has(packageNameOfSpecifier(specifier) ?? '')) return undefined
2211
+ return resolvePackageSpecifier(root, file, specifier, ['import', 'default'])
2210
2212
  }
2211
2213
 
2212
2214
  function moduleEdgesFromSource(root: string, file: string, source: string) {
2213
- const dynamicNames = pnextDynamicImportNames(source, file);
2214
- assertSupportedDynamicCalls(file, source, dynamicNames);
2215
+ const dynamicNames = pnextDynamicImportNames(source, file)
2216
+ assertSupportedDynamicCalls(file, source, dynamicNames)
2215
2217
  // Edges carry their byte offset so the final list follows true source order:
2216
2218
  // CSS injection order is a source-order property — a side-effect
2217
2219
  // `import './a.css'` written before a component import must be emitted
2218
2220
  // before that component's CSS.
2219
- const ordered: (ModuleEdge & { index: number })[] = [];
2220
- const facts = scanFacts(file, source);
2221
+ const ordered: (ModuleEdge & { index: number })[] = []
2222
+ const facts = scanFacts(file, source)
2221
2223
 
2222
2224
  for (const edge of facts.imports) {
2223
- const resolved = resolveModuleEdge(root, file, edge.specifier);
2224
- if (!resolved) continue;
2225
+ const resolved = resolveModuleEdge(root, file, edge.specifier)
2226
+ if (!resolved) continue
2225
2227
  ordered.push({
2226
2228
  file: resolved,
2227
2229
  exports: [...edge.exports],
2228
2230
  index: edge.index,
2229
2231
  ...(edge.star ? { star: true } : {}),
2230
- });
2232
+ })
2231
2233
  }
2232
2234
 
2233
2235
  if (scopedClientBoundaryPackages.has(packageNameForFile(file) ?? '')) {
2234
2236
  for (const call of facts.requires) {
2235
- const resolved = resolveModuleEdge(root, file, call.specifier);
2236
- if (!resolved) continue;
2237
+ const resolved = resolveModuleEdge(root, file, call.specifier)
2238
+ if (!resolved) continue
2237
2239
  const exports = commonJsModuleHasDefaultExport(readFileSync(resolved, 'utf8'), resolved)
2238
2240
  ? ['default']
2239
- : [];
2240
- ordered.push({ file: resolved, exports, index: call.index });
2241
+ : []
2242
+ ordered.push({ file: resolved, exports, index: call.index })
2241
2243
  }
2242
2244
  }
2243
2245
 
2244
- ordered.sort((a, b) => a.index - b.index);
2245
- const imports: ModuleEdge[] = ordered.map(({ index: _index, ...edge }) => edge);
2246
+ ordered.sort((a, b) => a.index - b.index)
2247
+ const imports: ModuleEdge[] = ordered.map(({ index: _index, ...edge }) => edge)
2246
2248
 
2247
2249
  for (const imported of dynamicImportEdgesFromSource(root, file, source, dynamicNames))
2248
- imports.push(imported);
2250
+ imports.push(imported)
2249
2251
 
2250
- return imports;
2252
+ return imports
2251
2253
  }
2252
2254
 
2253
2255
  function assertSupportedDynamicCalls(file: string, source: string, dynamicNames: Set<string>) {
@@ -2255,13 +2257,13 @@ function assertSupportedDynamicCalls(file: string, source: string, dynamicNames:
2255
2257
  if (!/\bimport\s*\(/.test(call.source)) {
2256
2258
  throw new Error(
2257
2259
  `${file} uses dynamic() without a literal import. PNext supports dynamic('./module') or dynamic(() => import('./module')).`,
2258
- );
2260
+ )
2259
2261
  }
2260
2262
 
2261
2263
  if (!/\bimport\s*\(\s*['"][^'"]+['"]\s*\)/.test(call.source)) {
2262
2264
  throw new Error(
2263
2265
  `${file} uses a non-literal dynamic import. PNext supports dynamic('./module') or dynamic(() => import('./module')).`,
2264
- );
2266
+ )
2265
2267
  }
2266
2268
  }
2267
2269
  }
@@ -2272,136 +2274,116 @@ function dynamicImportEdgesFromSource(
2272
2274
  source: string,
2273
2275
  dynamicNames: Set<string>,
2274
2276
  ) {
2275
- const imports: { file: string; exports: string[]; dynamic: ClientDynamicReference }[] = [];
2276
- if (dynamicNames.size === 0) return imports;
2277
-
2278
- const optionObjects = dynamicOptionObjects(source);
2279
- const dynamicPattern =
2280
- /import\s*\(\s*['"]([^'"]+)['"]\s*\)(?:\s*\.then\s*\(\s*([A-Za-z_$][\w$]*)\s*=>\s*\2\.([A-Za-z_$][\w$]*)\s*\))?/g;
2281
- let match: RegExpExecArray | null;
2282
-
2283
- while ((match = dynamicPattern.exec(source))) {
2284
- const [, specifier, _moduleName, exportName] = match;
2285
- if (!specifier) continue;
2286
- if (!isDynamicCallImport(source, match.index, dynamicNames)) continue;
2287
-
2288
- const resolved = resolveModuleEdge(root, file, specifier);
2289
- if (!resolved) continue;
2277
+ const imports: { file: string; exports: string[]; dynamic: ClientDynamicReference }[] = []
2278
+ if (dynamicNames.size === 0) return imports
2279
+
2280
+ const optionObjects = dynamicOptionObjects(source)
2281
+ for (const fact of dynamicCallFacts(source, file)) {
2282
+ if (!dynamicNames.has(fact.name)) continue
2283
+ const resolved = resolveModuleEdge(root, file, fact.specifier)
2284
+ if (!resolved) continue
2290
2285
  imports.push({
2291
2286
  file: resolved,
2292
- exports: [exportName ?? 'default'],
2293
- dynamic: dynamicOptionsForImport(source, dynamicPattern.lastIndex, optionObjects),
2294
- });
2287
+ exports: [fact.exportName],
2288
+ dynamic: dynamicOptionsForFact(source, fact, optionObjects),
2289
+ })
2295
2290
  }
2296
2291
 
2297
- return imports;
2292
+ return imports
2298
2293
  }
2299
2294
 
2300
2295
  function dynamicOptionObjects(source: string) {
2301
- const options = new Map<string, ClientDynamicReference>();
2302
- const pattern = /const\s+([A-Za-z_$][\w$]*)\s*=\s*({[\s\S]*?})\s*(?:as\s+const\s*)?;/g;
2303
- let match: RegExpExecArray | null;
2296
+ const options = new Map<string, ClientDynamicReference>()
2297
+ const pattern = /const\s+([A-Za-z_$][\w$]*)\s*=\s*({[\s\S]*?})\s*(?:as\s+const\s*)?;/g
2298
+ let match: RegExpExecArray | null
2304
2299
 
2305
2300
  while ((match = pattern.exec(source))) {
2306
- const [, name, objectSource] = match;
2307
- if (name && objectSource) options.set(name, dynamicOptionsFromSource(objectSource));
2301
+ const [, name, objectSource] = match
2302
+ if (name && objectSource) options.set(name, dynamicOptionsFromSource(objectSource))
2308
2303
  }
2309
2304
 
2310
- return options;
2305
+ return options
2311
2306
  }
2312
2307
 
2313
- function isDynamicCallImport(source: string, importIndex: number, dynamicNames: Set<string>) {
2314
- const prefix = source.slice(Math.max(0, importIndex - 120), importIndex);
2315
- const dynamicIndex = nextDynamicCallIndexFromEnd(prefix, dynamicNames);
2316
- if (dynamicIndex === -1) return false;
2317
- return !/[;\n]\s*$/.test(prefix.slice(dynamicIndex));
2318
- }
2319
-
2320
- function nextDynamicCallIndexFromEnd(source: string, dynamicNames: Set<string>) {
2321
- let next = -1;
2322
- for (const name of dynamicNames) {
2323
- const index = source.lastIndexOf(name);
2324
- if (index !== -1 && index > next) next = index;
2325
- }
2326
- return next;
2327
- }
2328
-
2329
- function dynamicOptionsForImport(
2308
+ function dynamicOptionsForFact(
2330
2309
  source: string,
2331
- endIndex: number,
2310
+ fact: DynamicCallFact,
2332
2311
  optionObjects: Map<string, ClientDynamicReference>,
2333
2312
  ) {
2334
- const tail = source.slice(endIndex, endIndex + 300);
2335
- const inlineMatch = /^\s*,\s*({[\s\S]*?})\s*,?\s*\)/.exec(tail);
2336
- if (inlineMatch?.[1]) return dynamicOptionsFromSource(inlineMatch[1]);
2337
-
2338
- const identifierMatch = /^\s*,\s*([A-Za-z_$][\w$]*)\s*,?\s*\)/.exec(tail);
2339
- if (identifierMatch?.[1]) return optionObjects.get(identifierMatch[1]) ?? {};
2340
-
2341
- return {};
2313
+ if (fact.optionsStart === undefined || fact.optionsEnd === undefined) return {}
2314
+ const options = source.slice(fact.optionsStart, fact.optionsEnd)
2315
+ if (/^[A-Za-z_$][\w$]*$/.test(options)) return optionObjects.get(options) ?? {}
2316
+ return dynamicOptionsFromSource(options)
2342
2317
  }
2343
2318
 
2344
2319
  function dynamicOptionsFromSource(source?: string): ClientDynamicReference {
2345
- if (!source) return {};
2320
+ if (!source) return {}
2346
2321
  return {
2347
2322
  ...dynamicLoadFromSource(source),
2348
2323
  ...dynamicRootMarginFromSource(source),
2349
2324
  ...dynamicSsrFromSource(source),
2350
2325
  ...dynamicThresholdFromSource(source),
2351
- };
2326
+ }
2352
2327
  }
2353
2328
 
2354
2329
  function dynamicLoadFromSource(source: string): ClientDynamicReference {
2355
- const match = /\bload\s*:\s*['"](render|visible)['"]/.exec(source);
2356
- return match?.[1] === 'render' || match?.[1] === 'visible' ? { load: match[1] } : {};
2330
+ const match = /\bload\s*:\s*['"](render|visible)['"]/.exec(source)
2331
+ return match?.[1] === 'render' || match?.[1] === 'visible' ? { load: match[1] } : {}
2357
2332
  }
2358
2333
 
2359
2334
  function dynamicRootMarginFromSource(source: string): ClientDynamicReference {
2360
- const match = /\brootMargin\s*:\s*['"]([^'"]+)['"]/.exec(source);
2361
- return match?.[1] ? { rootMargin: match[1] } : {};
2335
+ const match = /\brootMargin\s*:\s*['"]([^'"]+)['"]/.exec(source)
2336
+ return match?.[1] ? { rootMargin: match[1] } : {}
2362
2337
  }
2363
2338
 
2364
2339
  function dynamicSsrFromSource(source: string): ClientDynamicReference {
2365
- const match = /\bssr\s*:\s*(true|false)\b/.exec(source);
2366
- return match?.[1] ? { ssr: match[1] === 'true' } : {};
2340
+ const match = /\bssr\s*:\s*(true|false)\b/.exec(source)
2341
+ return match?.[1] ? { ssr: match[1] === 'true' } : {}
2367
2342
  }
2368
2343
 
2369
2344
  function dynamicThresholdFromSource(source: string): ClientDynamicReference {
2370
- const numberMatch = /\bthreshold\s*:\s*(-?\d+(?:\.\d+)?)/.exec(source);
2371
- if (numberMatch?.[1]) return { threshold: Number(numberMatch[1]) };
2345
+ const numberMatch = /\bthreshold\s*:\s*(-?\d+(?:\.\d+)?)/.exec(source)
2346
+ if (numberMatch?.[1]) return { threshold: Number(numberMatch[1]) }
2372
2347
 
2373
- const arrayMatch = /\bthreshold\s*:\s*\[([^\]]*)\]/.exec(source);
2374
- if (!arrayMatch?.[1]) return {};
2348
+ const arrayMatch = /\bthreshold\s*:\s*\[([^\]]*)\]/.exec(source)
2349
+ if (!arrayMatch?.[1]) return {}
2375
2350
  const threshold = arrayMatch[1]
2376
2351
  .split(',')
2377
2352
  .map(value => Number(value.trim()))
2378
- .filter(Number.isFinite);
2379
- return threshold.length > 0 ? { threshold } : {};
2353
+ .filter(Number.isFinite)
2354
+ return threshold.length > 0 ? { threshold } : {}
2380
2355
  }
2381
2356
 
2382
2357
  function isCssFile(file: string) {
2383
- return file.endsWith('.css') || getCssExtensions().extraCssExtensions().some(ext => file.endsWith(ext));
2358
+ return (
2359
+ file.endsWith('.css') ||
2360
+ getCssExtensions()
2361
+ .extraCssExtensions()
2362
+ .some(ext => file.endsWith(ext))
2363
+ )
2384
2364
  }
2385
2365
 
2386
2366
  function isAssetFile(file: string) {
2387
2367
  return (
2388
2368
  /\.(css|svg|png|jpe?g|gif|webp|ico|woff2?)$/.test(file) ||
2389
- getCssExtensions().extraCssExtensions().some(ext => file.endsWith(ext))
2390
- );
2369
+ getCssExtensions()
2370
+ .extraCssExtensions()
2371
+ .some(ext => file.endsWith(ext))
2372
+ )
2391
2373
  }
2392
2374
 
2393
2375
  function exportsExperimentalPpr(sources: string[]) {
2394
2376
  return sources.some(source =>
2395
2377
  /\bexport\s+const\s+experimental_ppr\s*=\s*true\b/.test(stripComments(source)),
2396
- );
2378
+ )
2397
2379
  }
2398
2380
 
2399
2381
  function usesRequestData(source: string) {
2400
- const searchable = stripJsxText(stripCommentsAndStrings(source));
2382
+ const searchable = stripJsxText(stripCommentsAndStrings(source))
2401
2383
  // Core knows only its own generic request-data markers; the next/* import
2402
2384
  // detectors (next/headers, next/navigation hooks, next/server connection())
2403
2385
  // are registered by compat via the usageDetection seam.
2404
- return /\b(?:request|searchParams)\b/.test(searchable) || sourceUsesRegisteredRequestApi(source);
2386
+ return /\b(?:request|searchParams)\b/.test(searchable) || sourceUsesRegisteredRequestApi(source)
2405
2387
  }
2406
2388
 
2407
2389
  /**
@@ -2411,7 +2393,10 @@ function usesRequestData(source: string) {
2411
2393
  * left untouched so a real usage between JSX tags is still seen.
2412
2394
  */
2413
2395
  function stripJsxText(source: string) {
2414
- return source.replace(/>([^<>(){}=;\n]*)</g, (_match, text: string) => `>${' '.repeat(text.length)}<`);
2396
+ return source.replace(
2397
+ />([^<>(){}=;\n]*)</g,
2398
+ (_match, text: string) => `>${' '.repeat(text.length)}<`,
2399
+ )
2415
2400
  }
2416
2401
 
2417
2402
  /**
@@ -2426,27 +2411,27 @@ function stripJsxText(source: string) {
2426
2411
  * referenced (the route can prerender).
2427
2412
  */
2428
2413
  function dynamicApiLabel(sources: string[], kind: 'page' | 'handler'): string | undefined {
2429
- const joined = sources.map(stripCommentsAndStrings).join('\n');
2430
- if (/\bconnection\s*\(/.test(joined)) return '`connection()`';
2431
- if (/\bcookies\s*\(/.test(joined)) return '`cookies()`';
2432
- if (/\bheaders\s*\(/.test(joined)) return '`headers()`';
2433
- if (/\bdraftMode\s*\(/.test(joined)) return '`draftMode()`';
2414
+ const joined = sources.map(stripCommentsAndStrings).join('\n')
2415
+ if (/\bconnection\s*\(/.test(joined)) return '`connection()`'
2416
+ if (/\bcookies\s*\(/.test(joined)) return '`cookies()`'
2417
+ if (/\bheaders\s*\(/.test(joined)) return '`headers()`'
2418
+ if (/\bdraftMode\s*\(/.test(joined)) return '`draftMode()`'
2434
2419
  if (kind === 'handler') {
2435
- if (/\.formData\b/.test(joined)) return '`request.formData`';
2436
- if (/\bnextUrl\b/.test(joined)) return '`nextUrl.toString`';
2437
- if (/\brequest\.url\b|\breq\.url\b/.test(joined)) return '`request.url`';
2420
+ if (/\.formData\b/.test(joined)) return '`request.formData`'
2421
+ if (/\bnextUrl\b/.test(joined)) return '`nextUrl.toString`'
2422
+ if (/\brequest\.url\b|\breq\.url\b/.test(joined)) return '`request.url`'
2438
2423
  } else if (/\bsearchParams\b/.test(joined)) {
2439
- return '`await searchParams`, `searchParams.then`, or similar';
2424
+ return '`await searchParams`, `searchParams.then`, or similar'
2440
2425
  }
2441
- return undefined;
2426
+ return undefined
2442
2427
  }
2443
2428
 
2444
2429
  function maxDurationFromSources(sources: string[]) {
2445
2430
  const durations = sources
2446
2431
  .map(source => /\bexport\s+const\s+maxDuration\s*=\s*(\d+)\b/.exec(source)?.[1])
2447
2432
  .filter((value): value is string => Boolean(value))
2448
- .map(value => Number(value));
2449
- return durations.length > 0 ? Math.max(...durations) : undefined;
2433
+ .map(value => Number(value))
2434
+ return durations.length > 0 ? Math.max(...durations) : undefined
2450
2435
  }
2451
2436
 
2452
2437
  // On-demand revalidation calls make a handler dynamic in Next: caching the
@@ -2454,78 +2439,78 @@ function maxDurationFromSources(sources: string[]) {
2454
2439
  function usesRevalidationApis(source: string) {
2455
2440
  return /\b(?:revalidatePath|revalidateTag|updateTag|expirePath|expireTag|unstable_expirePath|unstable_expireTag)\s*\(/.test(
2456
2441
  stripCommentsAndStrings(source),
2457
- );
2442
+ )
2458
2443
  }
2459
2444
 
2460
2445
  /** Sentinel for a destructured first parameter (no identifier to track). */
2461
- const DESTRUCTURED_PARAM = '<destructured>';
2446
+ const DESTRUCTURED_PARAM = '<destructured>'
2462
2447
 
2463
2448
  function routeHandlerUsesRequest(source: string) {
2464
- const withoutComments = stripComments(source);
2465
- const searchable = stripCommentsAndStrings(source);
2449
+ const withoutComments = stripComments(source)
2450
+ const searchable = stripCommentsAndStrings(source)
2466
2451
  // Compat's usageDetection (next/headers import, etc.) marks a handler dynamic.
2467
- if (sourceUsesRegisteredRequestApi(source)) return true;
2452
+ if (sourceUsesRegisteredRequestApi(source)) return true
2468
2453
  for (const method of routeHandlerMethods) {
2469
2454
  for (const params of exportedFunctionParams(withoutComments, method)) {
2470
- const name = firstParameterName(params);
2455
+ const name = firstParameterName(params)
2471
2456
  // A destructured request parameter (`GET({ nextUrl })`) reads the request
2472
2457
  // in the signature itself — there is no binding to count references for.
2473
- if (name === DESTRUCTURED_PARAM) return true;
2474
- if (name && referencesName(searchable, name) > 1) return true;
2458
+ if (name === DESTRUCTURED_PARAM) return true
2459
+ if (name && referencesName(searchable, name) > 1) return true
2475
2460
  }
2476
2461
  }
2477
- return false;
2462
+ return false
2478
2463
  }
2479
2464
 
2480
2465
  function exportedFunctionParams(source: string, method: string) {
2481
- const params: string[] = [];
2466
+ const params: string[] = []
2482
2467
  const functionPattern = new RegExp(
2483
2468
  `\\bexport\\s+(?:async\\s+)?function\\s+${method}\\s*\\(([^)]*)\\)`,
2484
2469
  'g',
2485
- );
2486
- let match: RegExpExecArray | null;
2470
+ )
2471
+ let match: RegExpExecArray | null
2487
2472
  while ((match = functionPattern.exec(source))) {
2488
- params.push(match[1] ?? '');
2473
+ params.push(match[1] ?? '')
2489
2474
  }
2490
2475
 
2491
2476
  const arrowPattern = new RegExp(
2492
2477
  `\\bexport\\s+const\\s+${method}\\s*(?::[^=]+)?=\\s*(?:async\\s*)?(?:\\(([^)]*)\\)|([A-Za-z_$][\\w$]*))\\s*=>`,
2493
2478
  'g',
2494
- );
2479
+ )
2495
2480
  while ((match = arrowPattern.exec(source))) {
2496
- params.push(match[1] ?? match[2] ?? '');
2481
+ params.push(match[1] ?? match[2] ?? '')
2497
2482
  }
2498
2483
 
2499
- return params;
2484
+ return params
2500
2485
  }
2501
2486
 
2502
2487
  function firstParameterName(params: string) {
2503
- const first = params.split(',')[0]?.trim();
2504
- if (!first) return undefined;
2505
- if (first.startsWith('{') || first.startsWith('[')) return DESTRUCTURED_PARAM;
2488
+ const first = params.split(',')[0]?.trim()
2489
+ if (!first) return undefined
2490
+ if (first.startsWith('{') || first.startsWith('[')) return DESTRUCTURED_PARAM
2506
2491
  const name =
2507
- /^\.{3}\s*([A-Za-z_$][\w$]*)/.exec(first)?.[1] ?? /^([A-Za-z_$][\w$]*)/.exec(first)?.[1];
2508
- return name;
2492
+ /^\.{3}\s*([A-Za-z_$][\w$]*)/.exec(first)?.[1] ?? /^([A-Za-z_$][\w$]*)/.exec(first)?.[1]
2493
+ return name
2509
2494
  }
2510
2495
 
2511
2496
  function referencesName(source: string, name: string) {
2512
- const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'g');
2513
- return [...source.matchAll(pattern)].length;
2497
+ const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`, 'g')
2498
+ return [...source.matchAll(pattern)].length
2514
2499
  }
2515
2500
 
2516
2501
  function stripCommentsAndStrings(source: string) {
2517
- return stripComments(source).replace(/(['"`])(?:\\[\s\S]|(?!\1)[^\\])*\1/g, '');
2502
+ return stripComments(source).replace(/(['"`])(?:\\[\s\S]|(?!\1)[^\\])*\1/g, '')
2518
2503
  }
2519
2504
 
2520
2505
  function stripComments(source: string) {
2521
- return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
2506
+ return source.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1')
2522
2507
  }
2523
2508
 
2524
2509
  function rootFromAppPath(appPath: string) {
2525
- const normalized = toPosixPath(appPath);
2510
+ const normalized = toPosixPath(appPath)
2526
2511
  return normalized.endsWith('/src/app')
2527
2512
  ? path.dirname(path.dirname(appPath))
2528
- : path.dirname(appPath);
2513
+ : path.dirname(appPath)
2529
2514
  }
2530
2515
 
2531
2516
  function clientReference(file: string, exportName: string): ClientReference {
@@ -2533,25 +2518,25 @@ function clientReference(file: string, exportName: string): ClientReference {
2533
2518
  id: `c-${clientReferenceId(file, exportName)}`,
2534
2519
  file,
2535
2520
  exportName,
2536
- };
2521
+ }
2537
2522
  }
2538
2523
 
2539
2524
  function routeId(route: string) {
2540
- if (route === '/') return 'index';
2525
+ if (route === '/') return 'index'
2541
2526
  return route
2542
2527
  .split('/')
2543
2528
  .filter(Boolean)
2544
2529
  .map(segment => {
2545
2530
  if (segment.startsWith(':') && segment.endsWith('*'))
2546
- return `catchall-${sanitizeIdPart(segment.slice(1, -1))}`;
2547
- if (segment.startsWith(':')) return `param-${sanitizeIdPart(segment.slice(1))}`;
2548
- return sanitizeIdPart(segment);
2531
+ return `catchall-${sanitizeIdPart(segment.slice(1, -1))}`
2532
+ if (segment.startsWith(':')) return `param-${sanitizeIdPart(segment.slice(1))}`
2533
+ return sanitizeIdPart(segment)
2549
2534
  })
2550
- .join('-');
2535
+ .join('-')
2551
2536
  }
2552
2537
 
2553
2538
  function sanitizeIdPart(value: string) {
2554
- return value.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'route';
2539
+ return value.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'route'
2555
2540
  }
2556
2541
 
2557
2542
  function routeRank(route: RouteManifestEntry) {
@@ -2563,10 +2548,10 @@ function routeRank(route: RouteManifestEntry) {
2563
2548
  .split('/')
2564
2549
  .filter(Boolean)
2565
2550
  .map(segment => (segment.startsWith(':') ? (segment.endsWith('*') ? '2' : '1') : '0'))
2566
- .join('');
2567
- return `${spec}:${route.synthetic ? 1 : 0}:${route.route}`;
2551
+ .join('')
2552
+ return `${spec}:${route.synthetic ? 1 : 0}:${route.route}`
2568
2553
  }
2569
2554
 
2570
2555
  function escapeRegex(value: string) {
2571
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2556
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
2572
2557
  }