@wular/pnext 0.0.1

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 (371) hide show
  1. package/README.md +153 -0
  2. package/bin/pnext +67 -0
  3. package/config/lint/base.js +48 -0
  4. package/config/ts/base.json +26 -0
  5. package/config/ts/react.json +13 -0
  6. package/package.json +70 -0
  7. package/reference/compat.md +63 -0
  8. package/reference/config.md +120 -0
  9. package/reference/css.md +58 -0
  10. package/reference/dev.md +69 -0
  11. package/reference/env.md +40 -0
  12. package/reference/metadata.md +86 -0
  13. package/reference/navigation.md +149 -0
  14. package/reference/overview.md +35 -0
  15. package/reference/performance.md +97 -0
  16. package/reference/rendering.md +127 -0
  17. package/reference/routing.md +167 -0
  18. package/reference/typegen.md +64 -0
  19. package/src/api/cache.ts +80 -0
  20. package/src/api/client-cache.ts +9 -0
  21. package/src/api/client-navigation.ts +279 -0
  22. package/src/api/dynamic.tsx +102 -0
  23. package/src/api/link.tsx +119 -0
  24. package/src/api/navigation.ts +198 -0
  25. package/src/api/router/events.ts +53 -0
  26. package/src/api/router/history.ts +70 -0
  27. package/src/api/router/hub.ts +194 -0
  28. package/src/api/router/policies.ts +107 -0
  29. package/src/api/router/runtime.ts +5238 -0
  30. package/src/api/router/types.ts +299 -0
  31. package/src/api/router.ts +167 -0
  32. package/src/api/server.ts +323 -0
  33. package/src/api/suspense.ts +16 -0
  34. package/src/cache/context.ts +61 -0
  35. package/src/cli/adapters/vercel-warm.ts +375 -0
  36. package/src/cli/adapters/vercel.ts +1310 -0
  37. package/src/cli/analyze-print.ts +181 -0
  38. package/src/cli/analyze.ts +328 -0
  39. package/src/cli/boot-trace.ts +29 -0
  40. package/src/cli/build.ts +3114 -0
  41. package/src/cli/dev.ts +276 -0
  42. package/src/cli/index.ts +196 -0
  43. package/src/cli/named-bin.ts +119 -0
  44. package/src/cli/serve-ui.ts +160 -0
  45. package/src/cli/start.ts +1425 -0
  46. package/src/client/build.ts +2136 -0
  47. package/src/client/chunk-fold.ts +526 -0
  48. package/src/client/entry.ts +1525 -0
  49. package/src/client/paths.ts +22 -0
  50. package/src/client/prebuilt.ts +621 -0
  51. package/src/client/profile.ts +75 -0
  52. package/src/client/react-compiler.ts +94 -0
  53. package/src/client/reference-stub.ts +145 -0
  54. package/src/client/reference.ts +47 -0
  55. package/src/compat/actions/action-client.ts +531 -0
  56. package/src/compat/actions/action-dispatch.ts +676 -0
  57. package/src/compat/actions/action-router.ts +40 -0
  58. package/src/compat/actions/action-shared.ts +95 -0
  59. package/src/compat/actions/client-plugin.ts +135 -0
  60. package/src/compat/actions/client-stub.ts +65 -0
  61. package/src/compat/actions/config.ts +164 -0
  62. package/src/compat/actions/detect.ts +208 -0
  63. package/src/compat/actions/discovery.ts +244 -0
  64. package/src/compat/actions/early-submit.ts +40 -0
  65. package/src/compat/actions/endpoint.ts +602 -0
  66. package/src/compat/actions/flight.ts +52 -0
  67. package/src/compat/actions/form-state.ts +73 -0
  68. package/src/compat/actions/hoist.ts +485 -0
  69. package/src/compat/actions/ids.ts +39 -0
  70. package/src/compat/actions/index.ts +41 -0
  71. package/src/compat/actions/instances.ts +144 -0
  72. package/src/compat/actions/origin.ts +109 -0
  73. package/src/compat/actions/protocol.ts +125 -0
  74. package/src/compat/actions/registry.ts +74 -0
  75. package/src/compat/actions/rewrite.ts +282 -0
  76. package/src/compat/actions/serve.ts +414 -0
  77. package/src/compat/actions/server-tag.ts +21 -0
  78. package/src/compat/actions/unrecognized-error.ts +30 -0
  79. package/src/compat/adapter/build-complete.ts +257 -0
  80. package/src/compat/bundler/bun-externals.ts +53 -0
  81. package/src/compat/bundler/cjs-exports.ts +542 -0
  82. package/src/compat/bundler/config.ts +363 -0
  83. package/src/compat/bundler/externals.ts +34 -0
  84. package/src/compat/bundler/import-meta-url.ts +60 -0
  85. package/src/compat/bundler/modularize-imports.ts +119 -0
  86. package/src/compat/bundler/new-url-asset.ts +87 -0
  87. package/src/compat/bundler/optimize-package-imports.ts +273 -0
  88. package/src/compat/bundler/polyfill.ts +88 -0
  89. package/src/compat/bundler/react-compiler.ts +61 -0
  90. package/src/compat/bundler/react-profiler.tsx +25 -0
  91. package/src/compat/bundler/relay-transform.ts +116 -0
  92. package/src/compat/bundler/require-context.ts +281 -0
  93. package/src/compat/bundler/resolve-extensions.ts +75 -0
  94. package/src/compat/bundler/source-cache.ts +61 -0
  95. package/src/compat/bundler/static-imports.ts +25 -0
  96. package/src/compat/bundler/symlink-imports.ts +119 -0
  97. package/src/compat/bundler/tsconfig-paths.ts +50 -0
  98. package/src/compat/bundler/wasm.ts +153 -0
  99. package/src/compat/bundler/webpack-loaders.ts +685 -0
  100. package/src/compat/bundler/worker.ts +278 -0
  101. package/src/compat/cache/build-flags.ts +81 -0
  102. package/src/compat/cache/build-prerender-errors.ts +163 -0
  103. package/src/compat/cache/custom-handler.ts +159 -0
  104. package/src/compat/cache/fetch-patch.ts +745 -0
  105. package/src/compat/cache/handler.ts +100 -0
  106. package/src/compat/cache/modern-handler.ts +275 -0
  107. package/src/compat/cache/resume-data-cache.ts +143 -0
  108. package/src/compat/cache/revalidate.ts +759 -0
  109. package/src/compat/cache/runtime-error.ts +124 -0
  110. package/src/compat/cache/use-cache-transform.ts +961 -0
  111. package/src/compat/cache/use-cache.ts +1695 -0
  112. package/src/compat/cache-control.ts +269 -0
  113. package/src/compat/client/base-path.ts +64 -0
  114. package/src/compat/client/css-order.ts +36 -0
  115. package/src/compat/client/errors/control-flow.ts +92 -0
  116. package/src/compat/client/errors/error-boundary.ts +222 -0
  117. package/src/compat/client/errors/global-error.ts +238 -0
  118. package/src/compat/client/errors/install.ts +217 -0
  119. package/src/compat/client/errors/lazy.ts +53 -0
  120. package/src/compat/client/errors/primitive-throw.ts +126 -0
  121. package/src/compat/client/errors/soft-refresh.ts +14 -0
  122. package/src/compat/client/link-status.ts +86 -0
  123. package/src/compat/client/nav-compat-runtime.ts +57 -0
  124. package/src/compat/client/nav-compat.ts +42 -0
  125. package/src/compat/client/navigation-scroll.ts +154 -0
  126. package/src/compat/client/optimistic-routing.ts +206 -0
  127. package/src/compat/client/prefetch-cache.ts +111 -0
  128. package/src/compat/client/route-announcer.ts +72 -0
  129. package/src/compat/client/segment-cache-policy.ts +159 -0
  130. package/src/compat/client/segment-cache.ts +1077 -0
  131. package/src/compat/client/segment-prefetch.ts +375 -0
  132. package/src/compat/client/trailing-slash.ts +24 -0
  133. package/src/compat/css/chunking.ts +254 -0
  134. package/src/compat/css/inline-css.ts +73 -0
  135. package/src/compat/css/lightningcss.ts +90 -0
  136. package/src/compat/css/modules.ts +373 -0
  137. package/src/compat/css/nonce.ts +30 -0
  138. package/src/compat/css/sass-plugin.ts +65 -0
  139. package/src/compat/css/sass.ts +392 -0
  140. package/src/compat/css/styled-jsx-runtime.ts +80 -0
  141. package/src/compat/css/styled-jsx.ts +49 -0
  142. package/src/compat/edge-runtime.ts +71 -0
  143. package/src/compat/export/client.ts +112 -0
  144. package/src/compat/export/index.ts +272 -0
  145. package/src/compat/export/standalone.ts +207 -0
  146. package/src/compat/image-optimizer/cache.ts +119 -0
  147. package/src/compat/image-optimizer/detect.ts +143 -0
  148. package/src/compat/image-optimizer/index.ts +601 -0
  149. package/src/compat/image-optimizer/source.ts +243 -0
  150. package/src/compat/index.ts +458 -0
  151. package/src/compat/lifecycle/after-scope.ts +86 -0
  152. package/src/compat/lifecycle/after.ts +173 -0
  153. package/src/compat/lifecycle/error-funnel.ts +306 -0
  154. package/src/compat/lifecycle/error-serialize.ts +87 -0
  155. package/src/compat/lifecycle/error-ui.ts +167 -0
  156. package/src/compat/lifecycle/instrumentation-client.ts +138 -0
  157. package/src/compat/lifecycle/instrumentation.ts +277 -0
  158. package/src/compat/lifecycle/node-console.ts +19 -0
  159. package/src/compat/lifecycle/testmode.ts +263 -0
  160. package/src/compat/mdx/compile.ts +219 -0
  161. package/src/compat/mdx/next-mdx-stub.ts +46 -0
  162. package/src/compat/mdx/plugin.ts +37 -0
  163. package/src/compat/metadata-route-artifacts.ts +458 -0
  164. package/src/compat/metadata.ts +295 -0
  165. package/src/compat/middleware/manifest.ts +210 -0
  166. package/src/compat/misc/action-return.ts +173 -0
  167. package/src/compat/next/cache.ts +211 -0
  168. package/src/compat/next/canonical-url.ts +35 -0
  169. package/src/compat/next/client-cache.ts +57 -0
  170. package/src/compat/next/client-navigation.ts +313 -0
  171. package/src/compat/next/client-only.ts +3 -0
  172. package/src/compat/next/client-script.tsx +215 -0
  173. package/src/compat/next/client-server.ts +39 -0
  174. package/src/compat/next/config-loader.ts +569 -0
  175. package/src/compat/next/config.ts +29 -0
  176. package/src/compat/next/constants.cjs +6 -0
  177. package/src/compat/next/constants.ts +6 -0
  178. package/src/compat/next/custom-server.ts +236 -0
  179. package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
  180. package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
  181. package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
  182. package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
  183. package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
  184. package/src/compat/next/dynamic.tsx +46 -0
  185. package/src/compat/next/error.tsx +148 -0
  186. package/src/compat/next/font/cache.ts +171 -0
  187. package/src/compat/next/font/google.ts +2 -0
  188. package/src/compat/next/font/index.ts +8 -0
  189. package/src/compat/next/font/local.ts +5 -0
  190. package/src/compat/next/font/runtime-client.ts +71 -0
  191. package/src/compat/next/font/runtime.ts +974 -0
  192. package/src/compat/next/font/shared.ts +281 -0
  193. package/src/compat/next/form.tsx +156 -0
  194. package/src/compat/next/head.tsx +10 -0
  195. package/src/compat/next/headers.ts +247 -0
  196. package/src/compat/next/image/config.ts +196 -0
  197. package/src/compat/next/image/optimizer.ts +96 -0
  198. package/src/compat/next/image/patterns.ts +103 -0
  199. package/src/compat/next/image/shared.ts +141 -0
  200. package/src/compat/next/image/static-metadata.ts +283 -0
  201. package/src/compat/next/image/validate.ts +269 -0
  202. package/src/compat/next/image-client.tsx +215 -0
  203. package/src/compat/next/image-props.ts +575 -0
  204. package/src/compat/next/image-usage.ts +102 -0
  205. package/src/compat/next/image.tsx +56 -0
  206. package/src/compat/next/index.ts +1 -0
  207. package/src/compat/next/legacy-image.tsx +97 -0
  208. package/src/compat/next/link-usage.ts +29 -0
  209. package/src/compat/next/link-validation-transform.ts +200 -0
  210. package/src/compat/next/link.tsx +466 -0
  211. package/src/compat/next/navigation.cjs +21 -0
  212. package/src/compat/next/navigation.ts +188 -0
  213. package/src/compat/next/offline.ts +51 -0
  214. package/src/compat/next/og.ts +324 -0
  215. package/src/compat/next/optimistic-route-state.ts +188 -0
  216. package/src/compat/next/preferred-region.ts +39 -0
  217. package/src/compat/next/redirects.ts +131 -0
  218. package/src/compat/next/resource-hints.ts +136 -0
  219. package/src/compat/next/rewrites.ts +350 -0
  220. package/src/compat/next/root-params.ts +142 -0
  221. package/src/compat/next/router.cjs +49 -0
  222. package/src/compat/next/router.ts +143 -0
  223. package/src/compat/next/script.tsx +355 -0
  224. package/src/compat/next/server-only.ts +3 -0
  225. package/src/compat/next/server.ts +28 -0
  226. package/src/compat/next/svgr.ts +58 -0
  227. package/src/compat/next/telemetry.ts +77 -0
  228. package/src/compat/next/user-agent.ts +100 -0
  229. package/src/compat/next/web-vitals.ts +56 -0
  230. package/src/compat/otel/api.ts +95 -0
  231. package/src/compat/otel/client-trace-metadata.ts +71 -0
  232. package/src/compat/otel/fetch-span.ts +77 -0
  233. package/src/compat/otel/tracer.ts +944 -0
  234. package/src/compat/pages/client-plugin.ts +108 -0
  235. package/src/compat/pages/index.ts +527 -0
  236. package/src/compat/pages/router-state.ts +94 -0
  237. package/src/compat/ppr/io.ts +38 -0
  238. package/src/compat/ppr/missing-root-params.ts +105 -0
  239. package/src/compat/ppr/root-params-scan.ts +164 -0
  240. package/src/compat/ppr/root-params-transform.ts +75 -0
  241. package/src/compat/ppr/root-params.ts +129 -0
  242. package/src/compat/ppr/segment-config-incompat.ts +34 -0
  243. package/src/compat/protocol.ts +202 -0
  244. package/src/compat/react/client.ts +59 -0
  245. package/src/compat/react/compiler-runtime.ts +60 -0
  246. package/src/compat/react/dom-client.ts +115 -0
  247. package/src/compat/react/dom-react-server.ts +20 -0
  248. package/src/compat/react/dom-server.ts +40 -0
  249. package/src/compat/react/dom.ts +154 -0
  250. package/src/compat/react/preact.ts +522 -0
  251. package/src/compat/react/react-server.ts +84 -0
  252. package/src/compat/react/router-shim.ts +26 -0
  253. package/src/compat/react/server-component-use.ts +48 -0
  254. package/src/compat/react/server-inserted-html.ts +87 -0
  255. package/src/compat/react/server.ts +156 -0
  256. package/src/compat/react/view-transition.ts +60 -0
  257. package/src/compat/register/actions.ts +875 -0
  258. package/src/compat/register/boot.ts +141 -0
  259. package/src/compat/register/build-tier.ts +11 -0
  260. package/src/compat/register/build.ts +182 -0
  261. package/src/compat/register/bundler.ts +587 -0
  262. package/src/compat/register/cache.ts +85 -0
  263. package/src/compat/register/client-errors.ts +18 -0
  264. package/src/compat/register/config.ts +17 -0
  265. package/src/compat/register/css-extras.ts +120 -0
  266. package/src/compat/register/edge-runtime.ts +6 -0
  267. package/src/compat/register/errors.ts +46 -0
  268. package/src/compat/register/export.ts +23 -0
  269. package/src/compat/register/font.ts +36 -0
  270. package/src/compat/register/hooks.ts +34 -0
  271. package/src/compat/register/image.ts +133 -0
  272. package/src/compat/register/index.ts +111 -0
  273. package/src/compat/register/instrumentation-client.ts +35 -0
  274. package/src/compat/register/lifecycle.ts +86 -0
  275. package/src/compat/register/mdx.ts +48 -0
  276. package/src/compat/register/middleware.ts +36 -0
  277. package/src/compat/register/misc.ts +44 -0
  278. package/src/compat/register/otel.ts +288 -0
  279. package/src/compat/register/pages-api.ts +473 -0
  280. package/src/compat/register/ppr.ts +56 -0
  281. package/src/compat/register/protocol.ts +57 -0
  282. package/src/compat/register/proxy.ts +127 -0
  283. package/src/compat/register/render.ts +268 -0
  284. package/src/compat/register/routing.ts +410 -0
  285. package/src/compat/register/segment.ts +1903 -0
  286. package/src/compat/register/static-image.ts +21 -0
  287. package/src/compat/register/typed-routes.ts +35 -0
  288. package/src/compat/register/usecache.ts +131 -0
  289. package/src/compat/register/validation.ts +56 -0
  290. package/src/compat/segment/loading-boundary.ts +113 -0
  291. package/src/compat/segment/page-slot.ts +200 -0
  292. package/src/compat/segment/tree.ts +481 -0
  293. package/src/compat/segment/vary-key.ts +102 -0
  294. package/src/compat/segment/vary-params.ts +551 -0
  295. package/src/compat/static-params.ts +33 -0
  296. package/src/compat/tsconfig-defaults.ts +301 -0
  297. package/src/compat/typecheck/index.ts +1481 -0
  298. package/src/compat/typecheck/worker.ts +26 -0
  299. package/src/compat/typed-routes/index.ts +92 -0
  300. package/src/compat/typed-routes/manifest.ts +356 -0
  301. package/src/compat/typed-routes/typegen.ts +566 -0
  302. package/src/compat/validation/errors.ts +159 -0
  303. package/src/compat/validation/index.ts +1770 -0
  304. package/src/compat/validation/prerender-diagnostics.ts +1508 -0
  305. package/src/compat-bootstrap.ts +67 -0
  306. package/src/config.ts +218 -0
  307. package/src/css/build.ts +697 -0
  308. package/src/css/index.ts +2 -0
  309. package/src/css/postcss.ts +236 -0
  310. package/src/css/worker.ts +34 -0
  311. package/src/dev/client-actions.ts +35 -0
  312. package/src/dev/client-chunk-store.ts +92 -0
  313. package/src/dev/client-key-cache.ts +178 -0
  314. package/src/dev/global-css-cache.ts +212 -0
  315. package/src/dev/imports.ts +2430 -0
  316. package/src/dev/module-cache.ts +721 -0
  317. package/src/dev/module-generations.ts +38 -0
  318. package/src/dev/module-transform.ts +188 -0
  319. package/src/dev/node-module-bundle-cache.ts +63 -0
  320. package/src/dev/restart-cache.ts +10 -0
  321. package/src/dev/route-bundle-key-cache.ts +154 -0
  322. package/src/dev/route-facts-cache.ts +223 -0
  323. package/src/dev/server.ts +1710 -0
  324. package/src/dynamic/source.ts +307 -0
  325. package/src/dynamic/tree-shake.ts +262 -0
  326. package/src/env.ts +92 -0
  327. package/src/extensions.ts +1898 -0
  328. package/src/index.ts +34 -0
  329. package/src/internal.ts +43 -0
  330. package/src/islands/boundary-error.ts +8 -0
  331. package/src/islands/static-children.ts +37 -0
  332. package/src/islands/static-slots.ts +106 -0
  333. package/src/ppr-postpone.ts +24 -0
  334. package/src/ppr.ts +784 -0
  335. package/src/proxy.ts +752 -0
  336. package/src/render/hooks.ts +384 -0
  337. package/src/render/index.ts +1 -0
  338. package/src/render/island-context.ts +47 -0
  339. package/src/render/metadata.ts +857 -0
  340. package/src/render/renderer.ts +7391 -0
  341. package/src/render/resource-hints.ts +44 -0
  342. package/src/render/slots.tsx +679 -0
  343. package/src/request/context.ts +396 -0
  344. package/src/resolve/engine.ts +219 -0
  345. package/src/resolve/imports.ts +1104 -0
  346. package/src/resolve/scan-facts.ts +474 -0
  347. package/src/resolve/source-text.ts +86 -0
  348. package/src/routing/forwarded.ts +41 -0
  349. package/src/routing/handler.ts +271 -0
  350. package/src/routing/href.ts +203 -0
  351. package/src/routing/metadata.ts +1018 -0
  352. package/src/routing/request-runtime.ts +43 -0
  353. package/src/routing/routes.ts +2560 -0
  354. package/src/routing/slots.ts +432 -0
  355. package/src/runtime/server.ts +3453 -0
  356. package/src/runtime/vendor.ts +1160 -0
  357. package/src/style-modules.d.ts +9 -0
  358. package/src/typegen.ts +151 -0
  359. package/src/types.ts +725 -0
  360. package/src/utils/ansi.ts +9 -0
  361. package/src/utils/content-type.ts +31 -0
  362. package/src/utils/decode.ts +7 -0
  363. package/src/utils/dev-profile.ts +31 -0
  364. package/src/utils/error-log.ts +29 -0
  365. package/src/utils/fs-cache.ts +31 -0
  366. package/src/utils/fs.ts +119 -0
  367. package/src/utils/html.ts +46 -0
  368. package/src/utils/serialize.ts +378 -0
  369. package/src/utils/source.ts +35 -0
  370. package/src/utils/verbose.ts +39 -0
  371. package/tsconfig.json +10 -0
@@ -0,0 +1,26 @@
1
+ // The typecheck, run on its own thread, so `typescript`'s multi-megabyte import lands off the build
2
+ // thread and runs alongside client bundling and prerendering.
3
+ //
4
+ // The thread gets the two config fields the checker reads plus a serializable projection of
5
+ // next.config, so it never re-loads the app's config.
6
+
7
+ import { parentPort, workerData } from 'node:worker_threads';
8
+ import type { ResolvedConfig } from '../../config';
9
+ import { setNextConfig } from '../next/config-loader';
10
+ import { PnextTypecheckError, validateTypes } from './index';
11
+ import type { TypecheckWorkerInput, TypecheckWorkerResult } from './index';
12
+
13
+ const input = workerData as TypecheckWorkerInput;
14
+ setNextConfig(input.nextConfig);
15
+
16
+ let result: TypecheckWorkerResult;
17
+ try {
18
+ await validateTypes({ root: input.root, outPath: input.outPath } as ResolvedConfig);
19
+ result = { ok: true };
20
+ } catch (error) {
21
+ result =
22
+ error instanceof PnextTypecheckError
23
+ ? { ok: false, messages: error.message.split('\n\n') }
24
+ : { ok: false, failure: error instanceof Error ? error.stack || error.message : String(error) };
25
+ }
26
+ parentPort?.postMessage(result);
@@ -0,0 +1,92 @@
1
+ // Writes `.next/types/{routes.d.ts,link.d.ts,validator.ts}` from a Next-shaped
2
+ // route-types manifest so the typed-routes / typed-routes-validator suites (which
3
+ // read `<distDir>/types/*` and run `tsc --noEmit`) pass. Compat-only.
4
+ import { existsSync } from 'node:fs';
5
+ import { readFile, writeFile } from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import type { ResolvedConfig } from '../../config';
8
+ import type { RouteManifestEntry } from '../../types';
9
+ import { ensureDir, toPosixPath } from '../../utils/fs';
10
+ import { getNextConfig } from '../next/config-loader';
11
+ import { scanRootParamTypes, type RootParamValueType } from '../ppr/root-params-scan';
12
+ import { buildRouteTypesManifest } from './manifest';
13
+ import { generateLinkTypesFile, generateRouteTypesFile, generateValidatorFile } from './typegen';
14
+
15
+ async function writeIfChanged(file: string, source: string): Promise<void> {
16
+ if (existsSync(file) && (await readFile(file, 'utf8')) === source) return;
17
+ await ensureDir(path.dirname(file));
18
+ await writeFile(file, source);
19
+ }
20
+
21
+ export async function generateTypedRoutes(
22
+ config: ResolvedConfig,
23
+ routes: RouteManifestEntry[],
24
+ ): Promise<void> {
25
+ const nextConfig = getNextConfig();
26
+ const typedRoutes = nextConfig.typedRoutes === true;
27
+
28
+ const manifest = await buildRouteTypesManifest({ config, routes, nextConfig });
29
+
30
+ // Next writes generated types into `<distDir>/types`; the harness maps
31
+ // distDir to `.next`, and the fixtures read `<distDir>/types/*` + include
32
+ // them via tsconfig's `**/*.ts` glob.
33
+ const typesDir = path.join(config.root, '.next', 'types');
34
+
35
+ await writeIfChanged(path.join(typesDir, 'routes.d.ts'), generateRouteTypesFile(manifest));
36
+
37
+ // Import specifiers in validator.ts are relative to the validator file's dir.
38
+ const toImportSpecifier = (rootRelativePath: string): string => {
39
+ const abs = path.join(config.root, rootRelativePath);
40
+ const rel = toPosixPath(path.relative(typesDir, abs));
41
+ return rel.startsWith('.') ? rel : `./${rel}`;
42
+ };
43
+ await writeIfChanged(
44
+ path.join(typesDir, 'validator.ts'),
45
+ generateValidatorFile(manifest, toImportSpecifier),
46
+ );
47
+
48
+ if (typedRoutes) {
49
+ await writeIfChanged(path.join(typesDir, 'link.d.ts'), generateLinkTypesFile(manifest));
50
+ }
51
+
52
+ // Next generates `<distDir>/types/root-params.d.ts` — a `declare module
53
+ // 'next/root-params'` with one async getter per root param — and imports it
54
+ // from next-env.d.ts (typecheck suite runs the app's own `tsc --noEmit`).
55
+ await writeIfChanged(
56
+ path.join(typesDir, 'root-params.d.ts'),
57
+ rootParamsTypesFile(scanRootParamTypes(path.join(config.root, 'app'))),
58
+ );
59
+
60
+ // Next writes `next-env.d.ts` with reference imports so tsc - which does NOT traverse the dotfolder
61
+ // `.next/` via the tsconfig glob - pulls the generated types, and thus the global
62
+ // PageProps/LayoutProps/RouteContext and the next/link href augmentation, into the program. Without it a
63
+ // plain `tsc --noEmit` in the app never sees the generated files.
64
+ await writeNextEnv(config.root, typedRoutes);
65
+ }
66
+
67
+ // Match Next's generated file byte-for-byte (ordering + comment banner): the
68
+ // getters sort by name, and value types print string, string[], undefined.
69
+ function rootParamsTypesFile(rootParams: Map<string, Set<RootParamValueType>>): string {
70
+ if (rootParams.size === 0) {
71
+ return `// Type definitions for Next.js root params (next/root-params)\n// No root params detected.\nexport {}\n`;
72
+ }
73
+ const order: RootParamValueType[] = ['string', 'string[]', 'undefined'];
74
+ const getters = [...rootParams.entries()]
75
+ .sort(([a], [b]) => a.localeCompare(b))
76
+ .map(([name, types]) => {
77
+ const union = order.filter(type => types.has(type)).join(' | ');
78
+ return ` export function ${name}(): Promise<${union}>`;
79
+ });
80
+ return `// Type definitions for Next.js root params (next/root-params)\n\ndeclare module 'next/root-params' {\n${getters.join('\n')}\n}\n`;
81
+ }
82
+
83
+ async function writeNextEnv(root: string, typedRoutes: boolean): Promise<void> {
84
+ const lines = [
85
+ '/// <reference types="next" />',
86
+ 'import "./.next/types/routes.d.ts";',
87
+ 'import "./.next/types/root-params.d.ts";',
88
+ ];
89
+ if (typedRoutes) lines.push('import "./.next/types/link.d.ts";');
90
+ lines.push('', '// NOTE: This file should not be edited', '');
91
+ await writeIfChanged(path.join(root, 'next-env.d.ts'), lines.join('\n'));
92
+ }
@@ -0,0 +1,356 @@
1
+ // Builds a Next-shaped route-types manifest from pnext's route entries, an
2
+ // independent scan of app-dir layouts/slots and the pages/ directory, plus the
3
+ // redirects/rewrites from next.config. Feeds the Next-identical codegen in
4
+ // ./typegen.ts. Compat-only.
5
+ import { existsSync } from 'node:fs';
6
+ import { readdir } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import type { ResolvedConfig } from '../../config';
9
+ import type { RouteManifestEntry } from '../../types';
10
+ import { toPosixPath } from '../../utils/fs';
11
+
12
+ /** path-to-regexp group info, mirroring Next's `getRouteRegex().groups`. */
13
+ export interface Group {
14
+ pos: number;
15
+ repeat: boolean;
16
+ optional: boolean;
17
+ }
18
+
19
+ export interface ManifestRouteInfo {
20
+ path: string;
21
+ groups: Record<string, Group>;
22
+ }
23
+
24
+ export interface RouteTypesManifest {
25
+ appRoutes: Record<string, ManifestRouteInfo>;
26
+ pageRoutes: Record<string, ManifestRouteInfo>;
27
+ layoutRoutes: Record<string, ManifestRouteInfo & { slots: string[] }>;
28
+ appRouteHandlerRoutes: Record<string, ManifestRouteInfo>;
29
+ redirectRoutes: Record<string, ManifestRouteInfo>;
30
+ rewriteRoutes: Record<string, ManifestRouteInfo>;
31
+ appPagePaths: Set<string>;
32
+ pagesRouterPagePaths: Set<string>;
33
+ layoutPaths: Set<string>;
34
+ appRouteHandlers: Set<string>;
35
+ pageApiRoutes: Set<string>;
36
+ filePathToRoute: Map<string, string>;
37
+ }
38
+
39
+ const PAGE_EXTENSIONS = ['tsx', 'ts', 'jsx', 'js', 'mdx', 'md'];
40
+ const LAYOUT_NAMES = new Set(PAGE_EXTENSIONS.map(ext => `layout.${ext}`));
41
+
42
+ function isGroupSegment(name: string): boolean {
43
+ return name.startsWith('(') && name.endsWith(')');
44
+ }
45
+
46
+ function isSlotSegment(name: string): boolean {
47
+ return name.startsWith('@');
48
+ }
49
+
50
+ function decodeLiteral(segment: string): string {
51
+ return segment.replace(/%5[Ff]/g, '_');
52
+ }
53
+
54
+ /** Extract groups (param name -> shape) from a bracket-syntax route. */
55
+ function groupsFromBracketRoute(route: string): Record<string, Group> {
56
+ const groups: Record<string, Group> = {};
57
+ let pos = 1;
58
+ for (const segment of route.split('/').filter(Boolean)) {
59
+ const optionalCatchAll = /^\[\[\.\.\.(.+)\]\]$/.exec(segment);
60
+ if (optionalCatchAll?.[1]) {
61
+ groups[optionalCatchAll[1]] = { pos: pos++, repeat: true, optional: true };
62
+ continue;
63
+ }
64
+ const catchAll = /^\[\.\.\.(.+)\]$/.exec(segment);
65
+ if (catchAll?.[1]) {
66
+ groups[catchAll[1]] = { pos: pos++, repeat: true, optional: false };
67
+ continue;
68
+ }
69
+ const param = /^\[(.+)\]$/.exec(segment);
70
+ if (param?.[1]) {
71
+ groups[param[1]] = { pos: pos++, repeat: false, optional: false };
72
+ }
73
+ }
74
+ return groups;
75
+ }
76
+
77
+ /**
78
+ * Turn a pnext `route` (`:param` / `:param*` form, groups already stripped and
79
+ * `%5F` decoded) plus its catch-all flags into Next bracket syntax.
80
+ */
81
+ function bracketRouteFromEntry(entry: RouteManifestEntry): string {
82
+ const segments = entry.route.split('/').filter(Boolean).map(segment => {
83
+ const catchAll = /^:(.+)\*$/.exec(segment);
84
+ if (catchAll?.[1]) {
85
+ const name = catchAll[1];
86
+ return name === entry.catchAll && entry.catchAllOptional
87
+ ? `[[...${name}]]`
88
+ : `[...${name}]`;
89
+ }
90
+ const param = /^:(.+)$/.exec(segment);
91
+ if (param?.[1]) return `[${param[1]}]`;
92
+ return segment;
93
+ });
94
+ return `/${segments.join('/')}`.replace(/\/$/, '') || '/';
95
+ }
96
+
97
+ /** Bracket route derived from a directory path relative to app/ (for layouts). */
98
+ function bracketRouteFromDir(relativeDir: string): string {
99
+ const segments: string[] = [];
100
+ for (const segment of relativeDir.split('/').filter(Boolean)) {
101
+ if (isGroupSegment(segment) || isSlotSegment(segment)) continue;
102
+ if (/^\[\[?\.\.\..+\]\]?$/.test(segment) || /^\[.+\]$/.test(segment)) {
103
+ segments.push(segment);
104
+ } else {
105
+ segments.push(decodeLiteral(segment));
106
+ }
107
+ }
108
+ return `/${segments.join('/')}`.replace(/\/$/, '') || '/';
109
+ }
110
+
111
+ function pagesRouteFromRelative(relative: string): string {
112
+ const withoutExt = relative.replace(/\.[^.]+$/, '');
113
+ const trimmed = withoutExt.replace(/\/index$/, '').replace(/^index$/, '');
114
+ const route = `/${trimmed}`.replace(/\/$/, '') || '/';
115
+ return route;
116
+ }
117
+
118
+ async function walkFiles(dir: string): Promise<string[]> {
119
+ if (!existsSync(dir)) return [];
120
+ const out: string[] = [];
121
+ const entries = await readdir(dir, { withFileTypes: true });
122
+ for (const entry of entries) {
123
+ const full = path.join(dir, entry.name);
124
+ if (entry.isDirectory()) out.push(...(await walkFiles(full)));
125
+ else out.push(full);
126
+ }
127
+ return out;
128
+ }
129
+
130
+ /**
131
+ * A private app segment (`_foo`, but not `%5F`-encoded or a group/slot) makes
132
+ * the whole route non-routable.
133
+ */
134
+ function isPrivate(relativeDir: string): boolean {
135
+ return relativeDir
136
+ .split('/')
137
+ .some(seg => seg.startsWith('_') && !isGroupSegment(seg) && !isSlotSegment(seg));
138
+ }
139
+
140
+ // path-to-regexp custom-route source -> bracket routes. The fixture only uses
141
+ // `:name`, `:name+`, `:name*`; other modifiers/patterns are skipped like Next.
142
+ function convertCustomRouteSource(source: string): string[] {
143
+ const normalized = source.startsWith('/') ? source : `/${source}`;
144
+ const converted = normalized
145
+ .split('/')
146
+ .map(segment => {
147
+ const optional = /^:(.+)\*$/.exec(segment);
148
+ if (optional?.[1]) return `[[...${optional[1]}]]`;
149
+ const catchAll = /^:(.+)\+$/.exec(segment);
150
+ if (catchAll?.[1]) return `[...${catchAll[1]}]`;
151
+ const param = /^:([A-Za-z0-9_]+)$/.exec(segment);
152
+ if (param?.[1]) return `[${param[1]}]`;
153
+ return segment;
154
+ })
155
+ .join('/');
156
+ return [converted.replace(/\/$/, '') || '/'];
157
+ }
158
+
159
+ interface CustomRoute {
160
+ source: string;
161
+ }
162
+ type RedirectsFn = () => Promise<CustomRoute[]> | CustomRoute[];
163
+ type RewritesFn = () =>
164
+ | Promise<CustomRoute[] | { beforeFiles?: CustomRoute[]; afterFiles?: CustomRoute[]; fallback?: CustomRoute[] }>
165
+ | CustomRoute[]
166
+ | { beforeFiles?: CustomRoute[]; afterFiles?: CustomRoute[]; fallback?: CustomRoute[] };
167
+
168
+ export interface BuildManifestInput {
169
+ config: ResolvedConfig;
170
+ routes: RouteManifestEntry[];
171
+ nextConfig: { redirects?: unknown; rewrites?: unknown };
172
+ }
173
+
174
+ export async function buildRouteTypesManifest({
175
+ config,
176
+ routes,
177
+ nextConfig,
178
+ }: BuildManifestInput): Promise<RouteTypesManifest> {
179
+ const relApp = (file: string) => toPosixPath(path.relative(config.root, file));
180
+ const isHybrid = config.appPath.includes('pnext-pages-compat');
181
+ const sourceAppPath = (entry: RouteManifestEntry): string | undefined => {
182
+ if (!isHybrid) return entry.file;
183
+ const basename = entry.kind === 'page' ? /^page\.(?:tsx?|jsx?|mdx?)$/ : /^route\.(?:tsx?|jsx?)$/;
184
+ const source = entry.sourceFiles.find(file => {
185
+ const normalized = toPosixPath(file);
186
+ return normalized.includes('/source-app/') && basename.test(path.basename(normalized));
187
+ });
188
+ if (!source) return undefined;
189
+ const relative = toPosixPath(source).split('/source-app/')[1];
190
+ return relative ? path.join(config.root, 'app', relative) : undefined;
191
+ };
192
+
193
+ const manifest: RouteTypesManifest = {
194
+ appRoutes: {},
195
+ pageRoutes: {},
196
+ layoutRoutes: {},
197
+ appRouteHandlerRoutes: {},
198
+ redirectRoutes: {},
199
+ rewriteRoutes: {},
200
+ appPagePaths: new Set(),
201
+ pagesRouterPagePaths: new Set(),
202
+ layoutPaths: new Set(),
203
+ appRouteHandlers: new Set(),
204
+ pageApiRoutes: new Set(),
205
+ filePathToRoute: new Map(),
206
+ };
207
+
208
+ // App pages + route handlers from pnext's scanned entries. Skip synthetic /
209
+ // interception entries (typegen resolves interceptors to their canonical page
210
+ // which is already scanned). When in hybrid mode (app + pages), config.appPath
211
+ // points at a generated compat directory containing wrappers for both app and
212
+ // pages. For typegen we only want original app routes in AppRoutes, not pages
213
+ // wrappers which belong in PageRoutes.
214
+ const hybridFilteredRoutes = isHybrid
215
+ ? routes.filter(entry => {
216
+ const file = entry.file;
217
+ if (!file.includes('pnext-pages-compat')) return true;
218
+ const sourceFiles = (entry as { sourceFiles?: string[] }).sourceFiles ?? [];
219
+ const isPagesWrapper = sourceFiles.some(sf => sf.includes('source-pages'));
220
+ if (isPagesWrapper) return false;
221
+ return true;
222
+ })
223
+ : routes;
224
+
225
+ for (const entry of hybridFilteredRoutes) {
226
+ if (entry.synthetic || entry.interception || entry.metadataRoute) continue;
227
+ const source = sourceAppPath(entry);
228
+ if (!source) continue;
229
+ const bracket = bracketRouteFromEntry(entry);
230
+ const rel = relApp(source);
231
+ const info: ManifestRouteInfo = { path: rel, groups: groupsFromBracketRoute(bracket) };
232
+ if (entry.kind === 'page') {
233
+ if (!manifest.appRoutes[bracket]) manifest.appRoutes[bracket] = info;
234
+ // appPagePaths/filePathToRoute (the validator inputs) are populated by an independent filesystem scan
235
+ // below, NOT from runtime entries: pnext's scanner collapses parallel-route slot pages onto a single
236
+ // representative route entry, which mis-maps a slot page's route and drops its siblings, breaking the
237
+ // generated validator's tsc.
238
+ } else {
239
+ if (!manifest.appRouteHandlerRoutes[bracket]) manifest.appRouteHandlerRoutes[bracket] = info;
240
+ manifest.appRouteHandlers.add(rel);
241
+ manifest.filePathToRoute.set(rel, bracket);
242
+ }
243
+ }
244
+
245
+ // Layouts + slots: independent app-dir scan (pnext entries don't carry them).
246
+ const appFiles = await walkFiles(isHybrid ? path.join(config.root, 'app') : config.appPath);
247
+ const appPath = isHybrid ? path.join(config.root, 'app') : config.appPath;
248
+ for (const file of appFiles) {
249
+ const relative = toPosixPath(path.relative(appPath, file));
250
+ const base = path.basename(relative);
251
+ if (!LAYOUT_NAMES.has(base)) continue;
252
+ const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative);
253
+ if (isPrivate(dir)) continue;
254
+ const bracket = bracketRouteFromDir(dir);
255
+ const rel = relApp(file);
256
+ if (!manifest.layoutRoutes[bracket]) {
257
+ manifest.layoutRoutes[bracket] = {
258
+ path: rel,
259
+ groups: groupsFromBracketRoute(bracket),
260
+ slots: [],
261
+ };
262
+ }
263
+ manifest.layoutPaths.add(rel);
264
+ manifest.filePathToRoute.set(rel, bracket);
265
+ }
266
+
267
+ // Slots: `@name` dirs whose nearest ancestor layout owns them. A path can
268
+ // pass through multiple slot dirs (e.g. `@modal/(.)test-nested/@sidebar`);
269
+ // the slot that owns a given file is the deepest (last) one on the path,
270
+ // not the first, since the layout receiving the prop lives directly under it.
271
+ for (const file of appFiles) {
272
+ const relative = toPosixPath(path.relative(appPath, file));
273
+ const segments = relative.split('/');
274
+ let slotIndex = -1;
275
+ for (let i = segments.length - 1; i >= 0; i--) {
276
+ if (isSlotSegment(segments[i]!)) {
277
+ slotIndex = i;
278
+ break;
279
+ }
280
+ }
281
+ if (slotIndex === -1) continue;
282
+ const slotName = segments[slotIndex]!.slice(1);
283
+ const ownerDir = segments.slice(0, slotIndex).join('/');
284
+ if (isPrivate(ownerDir)) continue;
285
+ const ownerRoute = bracketRouteFromDir(ownerDir);
286
+ const layout = manifest.layoutRoutes[ownerRoute];
287
+ if (layout && !layout.slots.includes(slotName)) layout.slots.push(slotName);
288
+ }
289
+
290
+ // App pages for the validator: an independent filesystem scan of every `page.{ext}` (including
291
+ // parallel-route slot pages), each mapped to its dir-derived route with group/slot segments stripped.
292
+ // Next validates each page file's `default` export against `AppPageConfig<its-route>`; a slot page like
293
+ // `dashboard/@analytics/page.tsx` renders at `/dashboard`, so it must validate against `/dashboard`.
294
+ // Guarded to routes that are actually routable, so private and interception pages are excluded - the
295
+ // AppPageConfig<Route> constraint requires `Route extends AppRoutes`.
296
+ const PAGE_BASENAMES = new Set(PAGE_EXTENSIONS.map(ext => `page.${ext}`));
297
+ for (const file of appFiles) {
298
+ const relative = toPosixPath(path.relative(appPath, file));
299
+ if (!PAGE_BASENAMES.has(path.basename(relative))) continue;
300
+ const dir = path.dirname(relative) === '.' ? '' : path.dirname(relative);
301
+ if (isPrivate(dir)) continue;
302
+ const route = bracketRouteFromDir(dir);
303
+ if (!manifest.appRoutes[route]) continue;
304
+ const rel = relApp(file);
305
+ manifest.appPagePaths.add(rel);
306
+ manifest.filePathToRoute.set(rel, route);
307
+ }
308
+
309
+ // Pages Router: pages/*.{ext} (non-api) -> pageRoutes; pages/api/** -> pageApiRoutes.
310
+ const pagesDir = path.join(config.root, 'pages');
311
+ for (const file of await walkFiles(pagesDir)) {
312
+ const relative = toPosixPath(path.relative(pagesDir, file));
313
+ if (!PAGE_EXTENSIONS.some(ext => relative.endsWith(`.${ext}`))) continue;
314
+ const base = path.basename(relative);
315
+ if (base.startsWith('_app.') || base.startsWith('_document.') || base.startsWith('_error.')) {
316
+ continue;
317
+ }
318
+ const rel = relApp(file);
319
+ const isApi = relative === 'api' || relative.startsWith('api/');
320
+ const route = pagesRouteFromRelative(relative);
321
+ if (isApi) {
322
+ manifest.pageApiRoutes.add(rel);
323
+ manifest.filePathToRoute.set(rel, route);
324
+ } else {
325
+ manifest.pageRoutes[route] = { path: rel, groups: groupsFromBracketRoute(route) };
326
+ manifest.pagesRouterPagePaths.add(rel);
327
+ manifest.filePathToRoute.set(rel, route);
328
+ }
329
+ }
330
+
331
+ // redirects / rewrites from next.config.
332
+ const redirects = nextConfig.redirects;
333
+ if (typeof redirects === 'function') {
334
+ const list = await (redirects as RedirectsFn)();
335
+ for (const item of list ?? []) {
336
+ for (const route of convertCustomRouteSource(item.source)) {
337
+ manifest.redirectRoutes[route] = { path: route, groups: groupsFromBracketRoute(route) };
338
+ }
339
+ }
340
+ }
341
+
342
+ const rewrites = nextConfig.rewrites;
343
+ if (typeof rewrites === 'function') {
344
+ const result = await (rewrites as RewritesFn)();
345
+ const sources = Array.isArray(result)
346
+ ? result
347
+ : [...(result?.beforeFiles ?? []), ...(result?.afterFiles ?? []), ...(result?.fallback ?? [])];
348
+ for (const item of sources) {
349
+ for (const route of convertCustomRouteSource(item.source)) {
350
+ manifest.rewriteRoutes[route] = { path: route, groups: groupsFromBracketRoute(route) };
351
+ }
352
+ }
353
+ }
354
+
355
+ return manifest;
356
+ }