vinext 0.0.53 → 0.0.55

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 (276) hide show
  1. package/README.md +1 -0
  2. package/dist/build/inline-css.d.ts +7 -0
  3. package/dist/build/inline-css.js +50 -0
  4. package/dist/build/inline-css.js.map +1 -0
  5. package/dist/build/prerender.js +2 -1
  6. package/dist/build/prerender.js.map +1 -1
  7. package/dist/check.js +19 -3
  8. package/dist/check.js.map +1 -1
  9. package/dist/client/navigation-runtime.d.ts +3 -1
  10. package/dist/client/navigation-runtime.js +1 -1
  11. package/dist/client/navigation-runtime.js.map +1 -1
  12. package/dist/client/window-next.d.ts +7 -0
  13. package/dist/client/window-next.js.map +1 -1
  14. package/dist/config/next-config.d.ts +97 -2
  15. package/dist/config/next-config.js +155 -6
  16. package/dist/config/next-config.js.map +1 -1
  17. package/dist/config/tsconfig-paths.d.ts +12 -3
  18. package/dist/config/tsconfig-paths.js +55 -24
  19. package/dist/config/tsconfig-paths.js.map +1 -1
  20. package/dist/deploy.js +13 -0
  21. package/dist/deploy.js.map +1 -1
  22. package/dist/entries/app-browser-entry.d.ts +11 -1
  23. package/dist/entries/app-browser-entry.js +16 -6
  24. package/dist/entries/app-browser-entry.js.map +1 -1
  25. package/dist/entries/app-rsc-entry.d.ts +9 -1
  26. package/dist/entries/app-rsc-entry.js +30 -5
  27. package/dist/entries/app-rsc-entry.js.map +1 -1
  28. package/dist/entries/app-rsc-manifest.d.ts +21 -1
  29. package/dist/entries/app-rsc-manifest.js +28 -9
  30. package/dist/entries/app-rsc-manifest.js.map +1 -1
  31. package/dist/entries/pages-client-entry.d.ts +4 -1
  32. package/dist/entries/pages-client-entry.js +18 -2
  33. package/dist/entries/pages-client-entry.js.map +1 -1
  34. package/dist/entries/pages-server-entry.js +123 -8
  35. package/dist/entries/pages-server-entry.js.map +1 -1
  36. package/dist/entries/runtime-entry-module.d.ts +1 -10
  37. package/dist/entries/runtime-entry-module.js +2 -12
  38. package/dist/entries/runtime-entry-module.js.map +1 -1
  39. package/dist/index.js +144 -44
  40. package/dist/index.js.map +1 -1
  41. package/dist/plugins/import-meta-url.d.ts +16 -0
  42. package/dist/plugins/import-meta-url.js +193 -0
  43. package/dist/plugins/import-meta-url.js.map +1 -0
  44. package/dist/plugins/remove-console.d.ts +16 -0
  45. package/dist/plugins/remove-console.js +176 -0
  46. package/dist/plugins/remove-console.js.map +1 -0
  47. package/dist/routing/app-route-graph.d.ts +24 -1
  48. package/dist/routing/app-route-graph.js +52 -4
  49. package/dist/routing/app-route-graph.js.map +1 -1
  50. package/dist/routing/app-router.d.ts +2 -2
  51. package/dist/routing/app-router.js +2 -2
  52. package/dist/routing/app-router.js.map +1 -1
  53. package/dist/routing/file-matcher.d.ts +21 -1
  54. package/dist/routing/file-matcher.js +39 -1
  55. package/dist/routing/file-matcher.js.map +1 -1
  56. package/dist/routing/pages-router.d.ts +1 -1
  57. package/dist/routing/pages-router.js +10 -3
  58. package/dist/routing/pages-router.js.map +1 -1
  59. package/dist/server/api-handler.js +1 -1
  60. package/dist/server/app-browser-action-result.d.ts +9 -16
  61. package/dist/server/app-browser-action-result.js +25 -14
  62. package/dist/server/app-browser-action-result.js.map +1 -1
  63. package/dist/server/app-browser-entry.js +195 -60
  64. package/dist/server/app-browser-entry.js.map +1 -1
  65. package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
  66. package/dist/server/app-browser-mpa-navigation.js +36 -0
  67. package/dist/server/app-browser-mpa-navigation.js.map +1 -0
  68. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  69. package/dist/server/app-browser-navigation-controller.js +4 -0
  70. package/dist/server/app-browser-navigation-controller.js.map +1 -1
  71. package/dist/server/app-browser-popstate.d.ts +3 -1
  72. package/dist/server/app-browser-popstate.js +15 -1
  73. package/dist/server/app-browser-popstate.js.map +1 -1
  74. package/dist/server/app-browser-state.js +2 -1
  75. package/dist/server/app-browser-state.js.map +1 -1
  76. package/dist/server/app-elements-wire.d.ts +13 -4
  77. package/dist/server/app-elements-wire.js +10 -1
  78. package/dist/server/app-elements-wire.js.map +1 -1
  79. package/dist/server/app-elements.d.ts +2 -2
  80. package/dist/server/app-elements.js +2 -2
  81. package/dist/server/app-elements.js.map +1 -1
  82. package/dist/server/app-fallback-renderer.d.ts +15 -5
  83. package/dist/server/app-fallback-renderer.js +10 -4
  84. package/dist/server/app-fallback-renderer.js.map +1 -1
  85. package/dist/server/app-inline-css-client.d.ts +7 -0
  86. package/dist/server/app-inline-css-client.js +37 -0
  87. package/dist/server/app-inline-css-client.js.map +1 -0
  88. package/dist/server/app-layout-param-observation.d.ts +30 -0
  89. package/dist/server/app-layout-param-observation.js +130 -0
  90. package/dist/server/app-layout-param-observation.js.map +1 -0
  91. package/dist/server/app-page-boundary-render.js +2 -2
  92. package/dist/server/app-page-boundary-render.js.map +1 -1
  93. package/dist/server/app-page-boundary.d.ts +21 -1
  94. package/dist/server/app-page-boundary.js +28 -3
  95. package/dist/server/app-page-boundary.js.map +1 -1
  96. package/dist/server/app-page-cache.d.ts +7 -3
  97. package/dist/server/app-page-cache.js +7 -7
  98. package/dist/server/app-page-cache.js.map +1 -1
  99. package/dist/server/app-page-dispatch.d.ts +10 -1
  100. package/dist/server/app-page-dispatch.js +126 -79
  101. package/dist/server/app-page-dispatch.js.map +1 -1
  102. package/dist/server/app-page-element-builder.js +12 -28
  103. package/dist/server/app-page-element-builder.js.map +1 -1
  104. package/dist/server/app-page-params.d.ts +2 -1
  105. package/dist/server/app-page-params.js +14 -1
  106. package/dist/server/app-page-params.js.map +1 -1
  107. package/dist/server/app-page-probe.d.ts +12 -1
  108. package/dist/server/app-page-probe.js +116 -1
  109. package/dist/server/app-page-probe.js.map +1 -1
  110. package/dist/server/app-page-render-identity.d.ts +22 -0
  111. package/dist/server/app-page-render-identity.js +42 -0
  112. package/dist/server/app-page-render-identity.js.map +1 -0
  113. package/dist/server/app-page-render.d.ts +8 -1
  114. package/dist/server/app-page-render.js +4 -1
  115. package/dist/server/app-page-render.js.map +1 -1
  116. package/dist/server/app-page-request.d.ts +6 -3
  117. package/dist/server/app-page-request.js +5 -2
  118. package/dist/server/app-page-request.js.map +1 -1
  119. package/dist/server/app-page-response.js +2 -2
  120. package/dist/server/app-page-response.js.map +1 -1
  121. package/dist/server/app-page-route-wiring.d.ts +15 -0
  122. package/dist/server/app-page-route-wiring.js +7 -5
  123. package/dist/server/app-page-route-wiring.js.map +1 -1
  124. package/dist/server/app-page-stream.d.ts +11 -0
  125. package/dist/server/app-page-stream.js +1 -0
  126. package/dist/server/app-page-stream.js.map +1 -1
  127. package/dist/server/app-route-handler-response.js +37 -5
  128. package/dist/server/app-route-handler-response.js.map +1 -1
  129. package/dist/server/app-rsc-cache-busting.d.ts +3 -2
  130. package/dist/server/app-rsc-cache-busting.js +9 -7
  131. package/dist/server/app-rsc-cache-busting.js.map +1 -1
  132. package/dist/server/app-rsc-handler.d.ts +14 -3
  133. package/dist/server/app-rsc-handler.js +56 -6
  134. package/dist/server/app-rsc-handler.js.map +1 -1
  135. package/dist/server/app-rsc-request-normalization.d.ts +2 -1
  136. package/dist/server/app-rsc-request-normalization.js +3 -2
  137. package/dist/server/app-rsc-request-normalization.js.map +1 -1
  138. package/dist/server/app-segment-config.d.ts +1 -1
  139. package/dist/server/app-segment-config.js +4 -1
  140. package/dist/server/app-segment-config.js.map +1 -1
  141. package/dist/server/app-server-action-execution.d.ts +26 -3
  142. package/dist/server/app-server-action-execution.js +240 -29
  143. package/dist/server/app-server-action-execution.js.map +1 -1
  144. package/dist/server/app-ssr-entry.d.ts +6 -0
  145. package/dist/server/app-ssr-entry.js +22 -7
  146. package/dist/server/app-ssr-entry.js.map +1 -1
  147. package/dist/server/app-ssr-error-meta.js +3 -3
  148. package/dist/server/app-ssr-error-meta.js.map +1 -1
  149. package/dist/server/app-ssr-stream.d.ts +2 -1
  150. package/dist/server/app-ssr-stream.js +176 -31
  151. package/dist/server/app-ssr-stream.js.map +1 -1
  152. package/dist/server/artifact-compatibility.d.ts +2 -1
  153. package/dist/server/artifact-compatibility.js +10 -1
  154. package/dist/server/artifact-compatibility.js.map +1 -1
  155. package/dist/server/client-reuse-manifest.d.ts +9 -4
  156. package/dist/server/client-reuse-manifest.js +2 -1
  157. package/dist/server/client-reuse-manifest.js.map +1 -1
  158. package/dist/server/client-trace-metadata.d.ts +31 -0
  159. package/dist/server/client-trace-metadata.js +83 -0
  160. package/dist/server/client-trace-metadata.js.map +1 -0
  161. package/dist/server/cookie-utils.d.ts +13 -0
  162. package/dist/server/cookie-utils.js +20 -0
  163. package/dist/server/cookie-utils.js.map +1 -0
  164. package/dist/server/dev-server.d.ts +8 -1
  165. package/dist/server/dev-server.js +83 -12
  166. package/dist/server/dev-server.js.map +1 -1
  167. package/dist/server/document-initial-head.d.ts +7 -0
  168. package/dist/server/document-initial-head.js +35 -0
  169. package/dist/server/document-initial-head.js.map +1 -0
  170. package/dist/server/html.d.ts +2 -1
  171. package/dist/server/html.js +6 -1
  172. package/dist/server/html.js.map +1 -1
  173. package/dist/server/isr-cache.d.ts +7 -5
  174. package/dist/server/isr-cache.js +17 -6
  175. package/dist/server/isr-cache.js.map +1 -1
  176. package/dist/server/middleware-runtime.js +1 -2
  177. package/dist/server/middleware-runtime.js.map +1 -1
  178. package/dist/server/pages-document-initial-props.d.ts +89 -0
  179. package/dist/server/pages-document-initial-props.js +140 -0
  180. package/dist/server/pages-document-initial-props.js.map +1 -0
  181. package/dist/server/pages-node-compat.js +1 -1
  182. package/dist/server/pages-page-data.js +3 -0
  183. package/dist/server/pages-page-data.js.map +1 -1
  184. package/dist/server/pages-page-method.d.ts +48 -0
  185. package/dist/server/pages-page-method.js +19 -0
  186. package/dist/server/pages-page-method.js.map +1 -0
  187. package/dist/server/pages-page-response.d.ts +20 -0
  188. package/dist/server/pages-page-response.js +37 -7
  189. package/dist/server/pages-page-response.js.map +1 -1
  190. package/dist/server/pages-serializable-props.d.ts +25 -0
  191. package/dist/server/pages-serializable-props.js +69 -0
  192. package/dist/server/pages-serializable-props.js.map +1 -0
  193. package/dist/server/prod-server.js +16 -6
  194. package/dist/server/prod-server.js.map +1 -1
  195. package/dist/server/server-action-not-found.js +3 -2
  196. package/dist/server/server-action-not-found.js.map +1 -1
  197. package/dist/server/skip-cache-proof.d.ts +23 -2
  198. package/dist/server/skip-cache-proof.js +81 -12
  199. package/dist/server/skip-cache-proof.js.map +1 -1
  200. package/dist/server/static-file-cache.js +2 -1
  201. package/dist/server/static-file-cache.js.map +1 -1
  202. package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
  203. package/dist/server/static-layout-client-reuse-proof.js +35 -0
  204. package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
  205. package/dist/shims/app-router-scroll-state.d.ts +4 -2
  206. package/dist/shims/app-router-scroll-state.js +16 -3
  207. package/dist/shims/app-router-scroll-state.js.map +1 -1
  208. package/dist/shims/app-router-scroll.d.ts +16 -2
  209. package/dist/shims/app-router-scroll.js +18 -3
  210. package/dist/shims/app-router-scroll.js.map +1 -1
  211. package/dist/shims/cache.d.ts +27 -1
  212. package/dist/shims/cache.js +108 -6
  213. package/dist/shims/cache.js.map +1 -1
  214. package/dist/shims/document.d.ts +6 -0
  215. package/dist/shims/document.js +7 -8
  216. package/dist/shims/document.js.map +1 -1
  217. package/dist/shims/error-boundary.d.ts +4 -4
  218. package/dist/shims/error-boundary.js +27 -28
  219. package/dist/shims/error-boundary.js.map +1 -1
  220. package/dist/shims/error.js +3 -0
  221. package/dist/shims/error.js.map +1 -1
  222. package/dist/shims/fetch-cache.d.ts +3 -1
  223. package/dist/shims/fetch-cache.js +16 -5
  224. package/dist/shims/fetch-cache.js.map +1 -1
  225. package/dist/shims/hash-scroll.d.ts +4 -1
  226. package/dist/shims/hash-scroll.js +13 -1
  227. package/dist/shims/hash-scroll.js.map +1 -1
  228. package/dist/shims/head-state.d.ts +1 -0
  229. package/dist/shims/head-state.js +18 -3
  230. package/dist/shims/head-state.js.map +1 -1
  231. package/dist/shims/head.d.ts +35 -1
  232. package/dist/shims/head.js +113 -14
  233. package/dist/shims/head.js.map +1 -1
  234. package/dist/shims/headers.d.ts +7 -0
  235. package/dist/shims/headers.js +9 -1
  236. package/dist/shims/headers.js.map +1 -1
  237. package/dist/shims/internal/app-route-detection.d.ts +37 -0
  238. package/dist/shims/internal/app-route-detection.js +69 -0
  239. package/dist/shims/internal/app-route-detection.js.map +1 -0
  240. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
  241. package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
  242. package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
  243. package/dist/shims/link.d.ts +18 -2
  244. package/dist/shims/link.js +98 -8
  245. package/dist/shims/link.js.map +1 -1
  246. package/dist/shims/metadata.d.ts +7 -6
  247. package/dist/shims/metadata.js +9 -5
  248. package/dist/shims/metadata.js.map +1 -1
  249. package/dist/shims/navigation.d.ts +40 -3
  250. package/dist/shims/navigation.js +124 -25
  251. package/dist/shims/navigation.js.map +1 -1
  252. package/dist/shims/router.d.ts +5 -0
  253. package/dist/shims/router.js +51 -21
  254. package/dist/shims/router.js.map +1 -1
  255. package/dist/shims/script.d.ts +11 -1
  256. package/dist/shims/script.js +75 -6
  257. package/dist/shims/script.js.map +1 -1
  258. package/dist/shims/thenable-params.d.ts +5 -2
  259. package/dist/shims/thenable-params.js +25 -1
  260. package/dist/shims/thenable-params.js.map +1 -1
  261. package/dist/shims/unified-request-context.js +3 -0
  262. package/dist/shims/unified-request-context.js.map +1 -1
  263. package/dist/utils/client-build-manifest.d.ts +15 -0
  264. package/dist/utils/client-build-manifest.js +54 -0
  265. package/dist/utils/client-build-manifest.js.map +1 -0
  266. package/dist/utils/hash.js +1 -1
  267. package/dist/utils/hash.js.map +1 -1
  268. package/dist/utils/lazy-chunks.d.ts +1 -1
  269. package/dist/utils/lazy-chunks.js.map +1 -1
  270. package/dist/utils/path.d.ts +13 -0
  271. package/dist/utils/path.js +16 -0
  272. package/dist/utils/path.js.map +1 -0
  273. package/dist/utils/vite-version.d.ts +11 -0
  274. package/dist/utils/vite-version.js +36 -0
  275. package/dist/utils/vite-version.js.map +1 -0
  276. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"app-router.js","names":[],"sources":["../../src/routing/app-router.ts"],"sourcesContent":["/**\n * App Router file-system routing.\n *\n * Scans the app/ directory following Next.js App Router conventions:\n * - app/page.tsx -> /\n * - app/about/page.tsx -> /about\n * - app/blog/[slug]/page.tsx -> /blog/:slug\n * - app/[...catchAll]/page.tsx -> /:catchAll+\n * - app/route.ts -> / (API route)\n * - app/(group)/page.tsx -> / (route groups are transparent)\n * - Layouts: app/layout.tsx wraps all children\n * - Loading: app/loading.tsx -> Suspense fallback\n * - Error: app/error.tsx -> ErrorBoundary\n * - Not Found: app/not-found.tsx\n */\nimport { createValidFileMatcher, type ValidFileMatcher } from \"./file-matcher.js\";\nimport { createRouteTrieCache, matchRouteWithTrie } from \"./route-matching.js\";\nimport {\n buildAppRouteGraph,\n type AppRoute,\n type AppRouteGraphRoute,\n type RouteManifest,\n} from \"./app-route-graph.js\";\nexport type { AppRoute } from \"./app-route-graph.js\";\nexport { computeRootParamNames, convertSegmentsToRouteParts } from \"./app-route-graph.js\";\n\ntype AppRouteGraph = {\n routes: AppRouteGraphRoute[];\n routeManifest: RouteManifest;\n};\n\n// Cache for app routes\nlet cachedGraph: AppRouteGraph | null = null;\nlet cachedAppDir: string | null = null;\nlet cachedPageExtensionsKey: string | null = null;\n\nexport function invalidateAppRouteCache(): void {\n cachedGraph = null;\n cachedAppDir = null;\n cachedPageExtensionsKey = null;\n}\n\n/**\n * Scan the app/ directory and return the route graph.\n * TODO(#726): Layer 4 should consume this read model directly once the\n * navigation planner owns route graph facts.\n *\n * @internal\n */\nexport async function appRouteGraph(\n appDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<AppRouteGraph> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const pageExtensionsKey = JSON.stringify(matcher.extensions);\n if (cachedGraph && cachedAppDir === appDir && cachedPageExtensionsKey === pageExtensionsKey) {\n return cachedGraph;\n }\n\n const graph = await buildAppRouteGraph(appDir, matcher);\n cachedGraph = graph;\n cachedAppDir = appDir;\n cachedPageExtensionsKey = pageExtensionsKey;\n return graph;\n}\n\n/**\n * Scan the app/ directory and return a list of routes.\n */\nexport async function appRouter(\n appDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<AppRouteGraphRoute[]> {\n const graph = await appRouteGraph(appDir, pageExtensions, matcher);\n return graph.routes;\n}\n\n// Trie cache — keyed by route array identity (same array = same trie)\nconst appTrieCache = createRouteTrieCache<AppRoute>();\n\n/**\n * Match a URL against App Router routes.\n */\nexport function matchAppRoute(\n url: string,\n routes: AppRoute[],\n): { route: AppRoute; params: Record<string, string | string[]> } | null {\n return matchRouteWithTrie(url, routes, appTrieCache);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,IAAI,cAAoC;AACxC,IAAI,eAA8B;AAClC,IAAI,0BAAyC;AAE7C,SAAgB,0BAAgC;CAC9C,cAAc;CACd,eAAe;CACf,0BAA0B;;;;;;;;;AAU5B,eAAsB,cACpB,QACA,gBACA,SACwB;CACxB,YAAY,uBAAuB,eAAe;CAClD,MAAM,oBAAoB,KAAK,UAAU,QAAQ,WAAW;CAC5D,IAAI,eAAe,iBAAiB,UAAU,4BAA4B,mBACxE,OAAO;CAGT,MAAM,QAAQ,MAAM,mBAAmB,QAAQ,QAAQ;CACvD,cAAc;CACd,eAAe;CACf,0BAA0B;CAC1B,OAAO;;;;;AAMT,eAAsB,UACpB,QACA,gBACA,SAC+B;CAE/B,QAAO,MADa,cAAc,QAAQ,gBAAgB,QAAQ,EACrD;;AAIf,MAAM,eAAe,sBAAgC;;;;AAKrD,SAAgB,cACd,KACA,QACuE;CACvE,OAAO,mBAAmB,KAAK,QAAQ,aAAa"}
1
+ {"version":3,"file":"app-router.js","names":[],"sources":["../../src/routing/app-router.ts"],"sourcesContent":["/**\n * App Router file-system routing.\n *\n * Scans the app/ directory following Next.js App Router conventions:\n * - app/page.tsx -> /\n * - app/about/page.tsx -> /about\n * - app/blog/[slug]/page.tsx -> /blog/:slug\n * - app/[...catchAll]/page.tsx -> /:catchAll+\n * - app/route.ts -> / (API route)\n * - app/(group)/page.tsx -> / (route groups are transparent)\n * - Layouts: app/layout.tsx wraps all children\n * - Loading: app/loading.tsx -> Suspense fallback\n * - Error: app/error.tsx -> ErrorBoundary\n * - Not Found: app/not-found.tsx\n */\nimport { createValidFileMatcher, type ValidFileMatcher } from \"./file-matcher.js\";\nimport { createRouteTrieCache, matchRouteWithTrie } from \"./route-matching.js\";\nimport {\n buildAppRouteGraph,\n type AppRoute,\n type AppRouteGraphRoute,\n type RouteManifest,\n} from \"./app-route-graph.js\";\nexport type { AppRoute } from \"./app-route-graph.js\";\nexport {\n computeAppRouteStaticSiblings,\n computeRootParamNames,\n convertSegmentsToRouteParts,\n} from \"./app-route-graph.js\";\n\ntype AppRouteGraph = {\n routes: AppRouteGraphRoute[];\n routeManifest: RouteManifest;\n};\n\n// Cache for app routes\nlet cachedGraph: AppRouteGraph | null = null;\nlet cachedAppDir: string | null = null;\nlet cachedPageExtensionsKey: string | null = null;\n\nexport function invalidateAppRouteCache(): void {\n cachedGraph = null;\n cachedAppDir = null;\n cachedPageExtensionsKey = null;\n}\n\n/**\n * Scan the app/ directory and return the route graph.\n * TODO(#726): Layer 4 should consume this read model directly once the\n * navigation planner owns route graph facts.\n *\n * @internal\n */\nexport async function appRouteGraph(\n appDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<AppRouteGraph> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const pageExtensionsKey = JSON.stringify(matcher.extensions);\n if (cachedGraph && cachedAppDir === appDir && cachedPageExtensionsKey === pageExtensionsKey) {\n return cachedGraph;\n }\n\n const graph = await buildAppRouteGraph(appDir, matcher);\n cachedGraph = graph;\n cachedAppDir = appDir;\n cachedPageExtensionsKey = pageExtensionsKey;\n return graph;\n}\n\n/**\n * Scan the app/ directory and return a list of routes.\n */\nexport async function appRouter(\n appDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<AppRouteGraphRoute[]> {\n const graph = await appRouteGraph(appDir, pageExtensions, matcher);\n return graph.routes;\n}\n\n// Trie cache — keyed by route array identity (same array = same trie)\nconst appTrieCache = createRouteTrieCache<AppRoute>();\n\n/**\n * Match a URL against App Router routes.\n */\nexport function matchAppRoute(\n url: string,\n routes: AppRoute[],\n): { route: AppRoute; params: Record<string, string | string[]> } | null {\n return matchRouteWithTrie(url, routes, appTrieCache);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoCA,IAAI,cAAoC;AACxC,IAAI,eAA8B;AAClC,IAAI,0BAAyC;AAE7C,SAAgB,0BAAgC;CAC9C,cAAc;CACd,eAAe;CACf,0BAA0B;;;;;;;;;AAU5B,eAAsB,cACpB,QACA,gBACA,SACwB;CACxB,YAAY,uBAAuB,eAAe;CAClD,MAAM,oBAAoB,KAAK,UAAU,QAAQ,WAAW;CAC5D,IAAI,eAAe,iBAAiB,UAAU,4BAA4B,mBACxE,OAAO;CAGT,MAAM,QAAQ,MAAM,mBAAmB,QAAQ,QAAQ;CACvD,cAAc;CACd,eAAe;CACf,0BAA0B;CAC1B,OAAO;;;;;AAMT,eAAsB,UACpB,QACA,gBACA,SAC+B;CAE/B,QAAO,MADa,cAAc,QAAQ,gBAAgB,QAAQ,EACrD;;AAIf,MAAM,eAAe,sBAAgC;;;;AAKrD,SAAgB,cACd,KACA,QACuE;CACvE,OAAO,mBAAmB,KAAK,QAAQ,aAAa"}
@@ -18,10 +18,30 @@ type ValidFileMatcher = {
18
18
  declare function createValidFileMatcher(pageExtensions?: readonly string[] | null): ValidFileMatcher;
19
19
  /** Check if a file exists with any configured page extension. */
20
20
  declare function findFileWithExtensions(basePath: string, matcher: ValidFileMatcher): boolean;
21
+ /**
22
+ * Vite's default `resolve.extensions` covers `.tsx/.ts/.jsx/.js/.json` (and
23
+ * `.mjs/.mts`). When the user configures `pageExtensions` with values Vite
24
+ * does not know about — e.g. `["platform.tsx", "tsx", "mdx"]` from the
25
+ * Next.js `resolve-extensions` fixture — extensionless imports of those
26
+ * files fail to resolve, and the build crashes with "Custom deploy script
27
+ * failed: undefined (1)".
28
+ *
29
+ * Build the merged extension list that Vite should use:
30
+ *
31
+ * 1. User-configured pageExtensions go first (each prefixed with `.`) so
32
+ * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
33
+ * 2. Vite's defaults follow, with duplicates removed.
34
+ *
35
+ * The user's pageExtensions retain their relative order, which is what
36
+ * Next.js / Turbopack do via the `resolveExtensions` config option.
37
+ *
38
+ * See: cloudflare/vinext#1502
39
+ */
40
+ declare function buildViteResolveExtensions(pageExtensions?: readonly string[] | null, viteDefaults?: readonly string[]): string[];
21
41
  /**
22
42
  * Use function-form exclude for Node < 22.14 compatibility.
23
43
  */
24
44
  declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
25
45
  //#endregion
26
- export { ValidFileMatcher, createValidFileMatcher, findFileWithExtensions, normalizePageExtensions, scanWithExtensions };
46
+ export { ValidFileMatcher, buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, normalizePageExtensions, scanWithExtensions };
27
47
  //# sourceMappingURL=file-matcher.d.ts.map
@@ -65,6 +65,44 @@ function findFileWithExtensions(basePath, matcher) {
65
65
  return matcher.dottedExtensions.some((ext) => existsSync(basePath + ext));
66
66
  }
67
67
  /**
68
+ * Vite's default `resolve.extensions` covers `.tsx/.ts/.jsx/.js/.json` (and
69
+ * `.mjs/.mts`). When the user configures `pageExtensions` with values Vite
70
+ * does not know about — e.g. `["platform.tsx", "tsx", "mdx"]` from the
71
+ * Next.js `resolve-extensions` fixture — extensionless imports of those
72
+ * files fail to resolve, and the build crashes with "Custom deploy script
73
+ * failed: undefined (1)".
74
+ *
75
+ * Build the merged extension list that Vite should use:
76
+ *
77
+ * 1. User-configured pageExtensions go first (each prefixed with `.`) so
78
+ * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
79
+ * 2. Vite's defaults follow, with duplicates removed.
80
+ *
81
+ * The user's pageExtensions retain their relative order, which is what
82
+ * Next.js / Turbopack do via the `resolveExtensions` config option.
83
+ *
84
+ * See: cloudflare/vinext#1502
85
+ */
86
+ function buildViteResolveExtensions(pageExtensions, viteDefaults = [
87
+ ".mjs",
88
+ ".js",
89
+ ".mts",
90
+ ".ts",
91
+ ".jsx",
92
+ ".tsx",
93
+ ".json"
94
+ ]) {
95
+ const dotted = normalizePageExtensions(pageExtensions).map((ext) => `.${ext}`);
96
+ const seen = /* @__PURE__ */ new Set();
97
+ const result = [];
98
+ for (const ext of [...dotted, ...viteDefaults]) {
99
+ if (seen.has(ext)) continue;
100
+ seen.add(ext);
101
+ result.push(ext);
102
+ }
103
+ return result;
104
+ }
105
+ /**
68
106
  * Use function-form exclude for Node < 22.14 compatibility.
69
107
  */
70
108
  async function* scanWithExtensions(stem, cwd, extensions, exclude) {
@@ -75,6 +113,6 @@ async function* scanWithExtensions(stem, cwd, extensions, exclude) {
75
113
  })) yield file;
76
114
  }
77
115
  //#endregion
78
- export { createValidFileMatcher, findFileWithExtensions, normalizePageExtensions, scanWithExtensions };
116
+ export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, normalizePageExtensions, scanWithExtensions };
79
117
 
80
118
  //# sourceMappingURL=file-matcher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-matcher.js","names":[],"sources":["../../src/routing/file-matcher.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { glob } from \"node:fs/promises\";\n\nconst DEFAULT_PAGE_EXTENSIONS = [\"tsx\", \"ts\", \"jsx\", \"js\"] as const;\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function normalizePageExtensions(pageExtensions?: readonly string[] | null): string[] {\n if (!Array.isArray(pageExtensions) || pageExtensions.length === 0) {\n return [...DEFAULT_PAGE_EXTENSIONS];\n }\n\n const filtered = pageExtensions\n .filter((ext): ext is string => typeof ext === \"string\")\n .map((ext) => ext.trim().replace(/^\\.+/, \"\"))\n .filter((ext) => ext.length > 0);\n return filtered.length > 0 ? [...filtered] : [...DEFAULT_PAGE_EXTENSIONS];\n}\n\nfunction buildExtensionGlob(stem: string, extensions: readonly string[]): string {\n if (extensions.length === 1) {\n return `${stem}.${extensions[0]}`;\n }\n return `${stem}.{${extensions.join(\",\")}}`;\n}\n\nexport type ValidFileMatcher = {\n extensions: string[];\n dottedExtensions: string[];\n extensionRegex: RegExp;\n isPageFile(filePath: string): boolean;\n isAppRouterPage(filePath: string): boolean;\n isAppRouterRoute(filePath: string): boolean;\n isAppLayoutFile(filePath: string): boolean;\n isAppDefaultFile(filePath: string): boolean;\n stripExtension(filePath: string): string;\n};\n\n/**\n * Ported in spirit from Next.js createValidFileMatcher:\n * packages/next/src/server/lib/find-page-file.ts\n */\nexport function createValidFileMatcher(\n pageExtensions?: readonly string[] | null,\n): ValidFileMatcher {\n const extensions = normalizePageExtensions(pageExtensions);\n const dottedExtensions = extensions.map((ext) => `.${ext}`);\n const extPattern = `(?:${extensions.map((ext) => escapeRegex(ext)).join(\"|\")})`;\n\n const extensionRegex = new RegExp(`\\\\.${extPattern}$`);\n const createLeafPattern = (fileNames: readonly string[]): RegExp => {\n const names = fileNames.length === 1 ? fileNames[0] : `(${fileNames.join(\"|\")})`;\n return new RegExp(`(^${names}|[\\\\\\\\/]${names})\\\\.${extPattern}$`);\n };\n\n const appRouterPageRegex = createLeafPattern([\"page\", \"route\"]);\n const appRouterRouteRegex = createLeafPattern([\"route\"]);\n const appLayoutRegex = createLeafPattern([\"layout\"]);\n const appDefaultRegex = createLeafPattern([\"default\"]);\n\n return {\n extensions,\n dottedExtensions,\n extensionRegex,\n isPageFile(filePath: string) {\n return extensionRegex.test(filePath);\n },\n isAppRouterPage(filePath: string) {\n return appRouterPageRegex.test(filePath);\n },\n isAppRouterRoute(filePath: string) {\n return appRouterRouteRegex.test(filePath);\n },\n isAppLayoutFile(filePath: string) {\n return appLayoutRegex.test(filePath);\n },\n isAppDefaultFile(filePath: string) {\n return appDefaultRegex.test(filePath);\n },\n stripExtension(filePath: string) {\n return filePath.replace(extensionRegex, \"\");\n },\n };\n}\n\n/** Check if a file exists with any configured page extension. */\nexport function findFileWithExtensions(basePath: string, matcher: ValidFileMatcher): boolean {\n return matcher.dottedExtensions.some((ext) => existsSync(basePath + ext));\n}\n\n/**\n * Use function-form exclude for Node < 22.14 compatibility.\n */\nexport async function* scanWithExtensions(\n stem: string,\n cwd: string,\n extensions: readonly string[],\n exclude?: (name: string) => boolean,\n): AsyncGenerator<string> {\n const pattern = buildExtensionGlob(stem, extensions);\n for await (const file of glob(pattern, {\n cwd,\n ...(exclude ? { exclude } : {}),\n })) {\n yield file;\n }\n}\n"],"mappings":";;;AAGA,MAAM,0BAA0B;CAAC;CAAO;CAAM;CAAO;CAAK;AAE1D,SAAS,YAAY,OAAuB;CAC1C,OAAO,MAAM,QAAQ,uBAAuB,OAAO;;AAGrD,SAAgB,wBAAwB,gBAAqD;CAC3F,IAAI,CAAC,MAAM,QAAQ,eAAe,IAAI,eAAe,WAAW,GAC9D,OAAO,CAAC,GAAG,wBAAwB;CAGrC,MAAM,WAAW,eACd,QAAQ,QAAuB,OAAO,QAAQ,SAAS,CACvD,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAC5C,QAAQ,QAAQ,IAAI,SAAS,EAAE;CAClC,OAAO,SAAS,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,wBAAwB;;AAG3E,SAAS,mBAAmB,MAAc,YAAuC;CAC/E,IAAI,WAAW,WAAW,GACxB,OAAO,GAAG,KAAK,GAAG,WAAW;CAE/B,OAAO,GAAG,KAAK,IAAI,WAAW,KAAK,IAAI,CAAC;;;;;;AAmB1C,SAAgB,uBACd,gBACkB;CAClB,MAAM,aAAa,wBAAwB,eAAe;CAC1D,MAAM,mBAAmB,WAAW,KAAK,QAAQ,IAAI,MAAM;CAC3D,MAAM,aAAa,MAAM,WAAW,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC;CAE7E,MAAM,iBAAiB,IAAI,OAAO,MAAM,WAAW,GAAG;CACtD,MAAM,qBAAqB,cAAyC;EAClE,MAAM,QAAQ,UAAU,WAAW,IAAI,UAAU,KAAK,IAAI,UAAU,KAAK,IAAI,CAAC;EAC9E,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,MAAM,MAAM,WAAW,GAAG;;CAGnE,MAAM,qBAAqB,kBAAkB,CAAC,QAAQ,QAAQ,CAAC;CAC/D,MAAM,sBAAsB,kBAAkB,CAAC,QAAQ,CAAC;CACxD,MAAM,iBAAiB,kBAAkB,CAAC,SAAS,CAAC;CACpD,MAAM,kBAAkB,kBAAkB,CAAC,UAAU,CAAC;CAEtD,OAAO;EACL;EACA;EACA;EACA,WAAW,UAAkB;GAC3B,OAAO,eAAe,KAAK,SAAS;;EAEtC,gBAAgB,UAAkB;GAChC,OAAO,mBAAmB,KAAK,SAAS;;EAE1C,iBAAiB,UAAkB;GACjC,OAAO,oBAAoB,KAAK,SAAS;;EAE3C,gBAAgB,UAAkB;GAChC,OAAO,eAAe,KAAK,SAAS;;EAEtC,iBAAiB,UAAkB;GACjC,OAAO,gBAAgB,KAAK,SAAS;;EAEvC,eAAe,UAAkB;GAC/B,OAAO,SAAS,QAAQ,gBAAgB,GAAG;;EAE9C;;;AAIH,SAAgB,uBAAuB,UAAkB,SAAoC;CAC3F,OAAO,QAAQ,iBAAiB,MAAM,QAAQ,WAAW,WAAW,IAAI,CAAC;;;;;AAM3E,gBAAuB,mBACrB,MACA,KACA,YACA,SACwB;CACxB,MAAM,UAAU,mBAAmB,MAAM,WAAW;CACpD,WAAW,MAAM,QAAQ,KAAK,SAAS;EACrC;EACA,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC/B,CAAC,EACA,MAAM"}
1
+ {"version":3,"file":"file-matcher.js","names":[],"sources":["../../src/routing/file-matcher.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { glob } from \"node:fs/promises\";\n\nconst DEFAULT_PAGE_EXTENSIONS = [\"tsx\", \"ts\", \"jsx\", \"js\"] as const;\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function normalizePageExtensions(pageExtensions?: readonly string[] | null): string[] {\n if (!Array.isArray(pageExtensions) || pageExtensions.length === 0) {\n return [...DEFAULT_PAGE_EXTENSIONS];\n }\n\n const filtered = pageExtensions\n .filter((ext): ext is string => typeof ext === \"string\")\n .map((ext) => ext.trim().replace(/^\\.+/, \"\"))\n .filter((ext) => ext.length > 0);\n return filtered.length > 0 ? [...filtered] : [...DEFAULT_PAGE_EXTENSIONS];\n}\n\nfunction buildExtensionGlob(stem: string, extensions: readonly string[]): string {\n if (extensions.length === 1) {\n return `${stem}.${extensions[0]}`;\n }\n return `${stem}.{${extensions.join(\",\")}}`;\n}\n\nexport type ValidFileMatcher = {\n extensions: string[];\n dottedExtensions: string[];\n extensionRegex: RegExp;\n isPageFile(filePath: string): boolean;\n isAppRouterPage(filePath: string): boolean;\n isAppRouterRoute(filePath: string): boolean;\n isAppLayoutFile(filePath: string): boolean;\n isAppDefaultFile(filePath: string): boolean;\n stripExtension(filePath: string): string;\n};\n\n/**\n * Ported in spirit from Next.js createValidFileMatcher:\n * packages/next/src/server/lib/find-page-file.ts\n */\nexport function createValidFileMatcher(\n pageExtensions?: readonly string[] | null,\n): ValidFileMatcher {\n const extensions = normalizePageExtensions(pageExtensions);\n const dottedExtensions = extensions.map((ext) => `.${ext}`);\n const extPattern = `(?:${extensions.map((ext) => escapeRegex(ext)).join(\"|\")})`;\n\n const extensionRegex = new RegExp(`\\\\.${extPattern}$`);\n const createLeafPattern = (fileNames: readonly string[]): RegExp => {\n const names = fileNames.length === 1 ? fileNames[0] : `(${fileNames.join(\"|\")})`;\n return new RegExp(`(^${names}|[\\\\\\\\/]${names})\\\\.${extPattern}$`);\n };\n\n const appRouterPageRegex = createLeafPattern([\"page\", \"route\"]);\n const appRouterRouteRegex = createLeafPattern([\"route\"]);\n const appLayoutRegex = createLeafPattern([\"layout\"]);\n const appDefaultRegex = createLeafPattern([\"default\"]);\n\n return {\n extensions,\n dottedExtensions,\n extensionRegex,\n isPageFile(filePath: string) {\n return extensionRegex.test(filePath);\n },\n isAppRouterPage(filePath: string) {\n return appRouterPageRegex.test(filePath);\n },\n isAppRouterRoute(filePath: string) {\n return appRouterRouteRegex.test(filePath);\n },\n isAppLayoutFile(filePath: string) {\n return appLayoutRegex.test(filePath);\n },\n isAppDefaultFile(filePath: string) {\n return appDefaultRegex.test(filePath);\n },\n stripExtension(filePath: string) {\n return filePath.replace(extensionRegex, \"\");\n },\n };\n}\n\n/** Check if a file exists with any configured page extension. */\nexport function findFileWithExtensions(basePath: string, matcher: ValidFileMatcher): boolean {\n return matcher.dottedExtensions.some((ext) => existsSync(basePath + ext));\n}\n\n/**\n * Vite's default `resolve.extensions` covers `.tsx/.ts/.jsx/.js/.json` (and\n * `.mjs/.mts`). When the user configures `pageExtensions` with values Vite\n * does not know about — e.g. `[\"platform.tsx\", \"tsx\", \"mdx\"]` from the\n * Next.js `resolve-extensions` fixture — extensionless imports of those\n * files fail to resolve, and the build crashes with \"Custom deploy script\n * failed: undefined (1)\".\n *\n * Build the merged extension list that Vite should use:\n *\n * 1. User-configured pageExtensions go first (each prefixed with `.`) so\n * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.\n * 2. Vite's defaults follow, with duplicates removed.\n *\n * The user's pageExtensions retain their relative order, which is what\n * Next.js / Turbopack do via the `resolveExtensions` config option.\n *\n * See: cloudflare/vinext#1502\n */\nexport function buildViteResolveExtensions(\n pageExtensions?: readonly string[] | null,\n viteDefaults: readonly string[] = [\".mjs\", \".js\", \".mts\", \".ts\", \".jsx\", \".tsx\", \".json\"],\n): string[] {\n const normalized = normalizePageExtensions(pageExtensions);\n const dotted = normalized.map((ext) => `.${ext}`);\n const seen = new Set<string>();\n const result: string[] = [];\n for (const ext of [...dotted, ...viteDefaults]) {\n if (seen.has(ext)) continue;\n seen.add(ext);\n result.push(ext);\n }\n return result;\n}\n\n/**\n * Use function-form exclude for Node < 22.14 compatibility.\n */\nexport async function* scanWithExtensions(\n stem: string,\n cwd: string,\n extensions: readonly string[],\n exclude?: (name: string) => boolean,\n): AsyncGenerator<string> {\n const pattern = buildExtensionGlob(stem, extensions);\n for await (const file of glob(pattern, {\n cwd,\n ...(exclude ? { exclude } : {}),\n })) {\n yield file;\n }\n}\n"],"mappings":";;;AAGA,MAAM,0BAA0B;CAAC;CAAO;CAAM;CAAO;CAAK;AAE1D,SAAS,YAAY,OAAuB;CAC1C,OAAO,MAAM,QAAQ,uBAAuB,OAAO;;AAGrD,SAAgB,wBAAwB,gBAAqD;CAC3F,IAAI,CAAC,MAAM,QAAQ,eAAe,IAAI,eAAe,WAAW,GAC9D,OAAO,CAAC,GAAG,wBAAwB;CAGrC,MAAM,WAAW,eACd,QAAQ,QAAuB,OAAO,QAAQ,SAAS,CACvD,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAC5C,QAAQ,QAAQ,IAAI,SAAS,EAAE;CAClC,OAAO,SAAS,SAAS,IAAI,CAAC,GAAG,SAAS,GAAG,CAAC,GAAG,wBAAwB;;AAG3E,SAAS,mBAAmB,MAAc,YAAuC;CAC/E,IAAI,WAAW,WAAW,GACxB,OAAO,GAAG,KAAK,GAAG,WAAW;CAE/B,OAAO,GAAG,KAAK,IAAI,WAAW,KAAK,IAAI,CAAC;;;;;;AAmB1C,SAAgB,uBACd,gBACkB;CAClB,MAAM,aAAa,wBAAwB,eAAe;CAC1D,MAAM,mBAAmB,WAAW,KAAK,QAAQ,IAAI,MAAM;CAC3D,MAAM,aAAa,MAAM,WAAW,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC;CAE7E,MAAM,iBAAiB,IAAI,OAAO,MAAM,WAAW,GAAG;CACtD,MAAM,qBAAqB,cAAyC;EAClE,MAAM,QAAQ,UAAU,WAAW,IAAI,UAAU,KAAK,IAAI,UAAU,KAAK,IAAI,CAAC;EAC9E,OAAO,IAAI,OAAO,KAAK,MAAM,UAAU,MAAM,MAAM,WAAW,GAAG;;CAGnE,MAAM,qBAAqB,kBAAkB,CAAC,QAAQ,QAAQ,CAAC;CAC/D,MAAM,sBAAsB,kBAAkB,CAAC,QAAQ,CAAC;CACxD,MAAM,iBAAiB,kBAAkB,CAAC,SAAS,CAAC;CACpD,MAAM,kBAAkB,kBAAkB,CAAC,UAAU,CAAC;CAEtD,OAAO;EACL;EACA;EACA;EACA,WAAW,UAAkB;GAC3B,OAAO,eAAe,KAAK,SAAS;;EAEtC,gBAAgB,UAAkB;GAChC,OAAO,mBAAmB,KAAK,SAAS;;EAE1C,iBAAiB,UAAkB;GACjC,OAAO,oBAAoB,KAAK,SAAS;;EAE3C,gBAAgB,UAAkB;GAChC,OAAO,eAAe,KAAK,SAAS;;EAEtC,iBAAiB,UAAkB;GACjC,OAAO,gBAAgB,KAAK,SAAS;;EAEvC,eAAe,UAAkB;GAC/B,OAAO,SAAS,QAAQ,gBAAgB,GAAG;;EAE9C;;;AAIH,SAAgB,uBAAuB,UAAkB,SAAoC;CAC3F,OAAO,QAAQ,iBAAiB,MAAM,QAAQ,WAAW,WAAW,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;AAsB3E,SAAgB,2BACd,gBACA,eAAkC;CAAC;CAAQ;CAAO;CAAQ;CAAO;CAAQ;CAAQ;CAAQ,EAC/E;CAEV,MAAM,SADa,wBAAwB,eAClB,CAAC,KAAK,QAAQ,IAAI,MAAM;CACjD,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,SAAmB,EAAE;CAC3B,KAAK,MAAM,OAAO,CAAC,GAAG,QAAQ,GAAG,aAAa,EAAE;EAC9C,IAAI,KAAK,IAAI,IAAI,EAAE;EACnB,KAAK,IAAI,IAAI;EACb,OAAO,KAAK,IAAI;;CAElB,OAAO;;;;;AAMT,gBAAuB,mBACrB,MACA,KACA,YACA,SACwB;CACxB,MAAM,UAAU,mBAAmB,MAAM,WAAW;CACpD,WAAW,MAAM,QAAQ,KAAK,SAAS;EACrC;EACA,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAC/B,CAAC,EACA,MAAM"}
@@ -23,7 +23,7 @@ declare function invalidateRouteCache(pagesDir: string): void;
23
23
  * - pages/about.tsx -> /about
24
24
  * - pages/posts/[id].tsx -> /posts/:id
25
25
  * - pages/[...slug].tsx -> /:slug+
26
- * - Ignores _app.tsx, _document.tsx, _error.tsx, files starting with _
26
+ * - Ignores _app.tsx, _document.tsx, _error.tsx (Next.js special files)
27
27
  * - Ignores pages/api/ (handled separately later)
28
28
  */
29
29
  declare function pagesRouter(pagesDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<Route[]>;
@@ -4,6 +4,12 @@ import { patternToNextFormat, validateRoutePatterns } from "./route-validation.j
4
4
  import { createRouteTrieCache, matchRouteWithTrie } from "./route-matching.js";
5
5
  import path from "node:path";
6
6
  //#region src/routing/pages-router.ts
7
+ /** Next.js special pages that should not produce routes. */
8
+ const RESERVED_PAGE_NAMES = new Set([
9
+ "_app",
10
+ "_document",
11
+ "_error"
12
+ ]);
7
13
  const routeCache = /* @__PURE__ */ new Map();
8
14
  /**
9
15
  * Invalidate cached routes for a given pages directory.
@@ -21,7 +27,7 @@ function invalidateRouteCache(pagesDir) {
21
27
  * - pages/about.tsx -> /about
22
28
  * - pages/posts/[id].tsx -> /posts/:id
23
29
  * - pages/[...slug].tsx -> /:slug+
24
- * - Ignores _app.tsx, _document.tsx, _error.tsx, files starting with _
30
+ * - Ignores _app.tsx, _document.tsx, _error.tsx (Next.js special files)
25
31
  * - Ignores pages/api/ (handled separately later)
26
32
  */
27
33
  async function pagesRouter(pagesDir, pageExtensions, matcher) {
@@ -43,7 +49,7 @@ async function pagesRouter(pagesDir, pageExtensions, matcher) {
43
49
  }
44
50
  async function scanPageRoutes(pagesDir, matcher) {
45
51
  const routes = [];
46
- for await (const file of scanWithExtensions("**/*", pagesDir, matcher.extensions, (name) => name === "api" || name.startsWith("_"))) {
52
+ for await (const file of scanWithExtensions("**/*", pagesDir, matcher.extensions, (name) => name === "api" || RESERVED_PAGE_NAMES.has(name))) {
47
53
  const route = fileToRoute(file, pagesDir, matcher);
48
54
  if (route) routes.push(route);
49
55
  }
@@ -93,6 +99,7 @@ function fileToRoute(file, pagesDir, matcher) {
93
99
  urlSegments.push(decodeRouteSegment(segment));
94
100
  }
95
101
  const pattern = "/" + urlSegments.join("/");
102
+ if (segments.length === 1 && RESERVED_PAGE_NAMES.has(segments[0])) return null;
96
103
  return {
97
104
  pattern: pattern === "/" ? "/" : pattern,
98
105
  patternParts: urlSegments.filter(Boolean),
@@ -139,7 +146,7 @@ async function scanApiRoutes(pagesDir, matcher) {
139
146
  let files;
140
147
  try {
141
148
  files = [];
142
- for await (const file of scanWithExtensions("**/*", apiDir, matcher.extensions, (name) => name.startsWith("_"))) files.push(file);
149
+ for await (const file of scanWithExtensions("**/*", apiDir, matcher.extensions)) files.push(file);
143
150
  } catch {
144
151
  files = [];
145
152
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pages-router.js","names":[],"sources":["../../src/routing/pages-router.ts"],"sourcesContent":["import path from \"node:path\";\nimport { compareRoutes, decodeRouteSegment } from \"./utils.js\";\nimport {\n createValidFileMatcher,\n scanWithExtensions,\n type ValidFileMatcher,\n} from \"./file-matcher.js\";\nimport { validateRoutePatterns } from \"./route-validation.js\";\nimport { createRouteTrieCache, matchRouteWithTrie } from \"./route-matching.js\";\n\nexport type Route = {\n /** URL pattern, e.g. \"/\" or \"/about\" or \"/posts/:id\" */\n pattern: string;\n /** Pre-split pattern segments (computed once at scan time, reused per request) */\n patternParts: string[];\n /** Absolute file path to the page component */\n filePath: string;\n /** Whether this is a dynamic route */\n isDynamic: boolean;\n /** Parameter names for dynamic segments */\n params: string[];\n};\n\n// Route cache — invalidated when pages directory changes\nconst routeCache = new Map<string, { routes: Route[]; promise: Promise<Route[]> }>();\n\n/**\n * Invalidate cached routes for a given pages directory.\n * Called by the file watcher when pages are added/removed.\n */\nexport function invalidateRouteCache(pagesDir: string): void {\n for (const key of routeCache.keys()) {\n if (key.startsWith(`pages:${pagesDir}:`) || key.startsWith(`api:${pagesDir}:`)) {\n routeCache.delete(key);\n }\n }\n}\n\n/**\n * Scan the pages/ directory and return a list of routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js Pages Router conventions:\n * - pages/index.tsx -> /\n * - pages/about.tsx -> /about\n * - pages/posts/[id].tsx -> /posts/:id\n * - pages/[...slug].tsx -> /:slug+\n * - Ignores _app.tsx, _document.tsx, _error.tsx, files starting with _\n * - Ignores pages/api/ (handled separately later)\n */\nexport async function pagesRouter(\n pagesDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<Route[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const cacheKey = `pages:${pagesDir}:${JSON.stringify(matcher.extensions)}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanPageRoutes(pagesDir, matcher);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanPageRoutes(pagesDir: string, matcher: ValidFileMatcher): Promise<Route[]> {\n const routes: Route[] = [];\n\n // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)\n for await (const file of scanWithExtensions(\n \"**/*\",\n pagesDir,\n matcher.extensions,\n (name: string) => name === \"api\" || name.startsWith(\"_\"),\n )) {\n const route = fileToRoute(file, pagesDir, matcher);\n if (route) routes.push(route);\n }\n\n validateRoutePatterns(routes.map((route) => route.pattern));\n\n // Sort: static routes first, then dynamic, then catch-all\n routes.sort(compareRoutes);\n\n return routes;\n}\n\n/**\n * Convert a file path relative to pages/ into a Route.\n */\nfunction fileToRoute(file: string, pagesDir: string, matcher: ValidFileMatcher): Route | null {\n // Remove extension\n const withoutExt = matcher.stripExtension(file);\n if (withoutExt === file) return null;\n\n // Convert to URL segments\n const segments = withoutExt.split(path.sep);\n\n // Handle index files: pages/index.tsx -> /\n const lastSegment = segments[segments.length - 1];\n if (lastSegment === \"index\") {\n segments.pop();\n }\n\n const params: string[] = [];\n let isDynamic = false;\n\n // Convert Next.js dynamic segments to URL patterns.\n // Catch-all segments are only valid in terminal position.\n const urlSegments: string[] = [];\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n\n // Catch-all: [...slug] -> :slug+ (param names may contain any non-] chars)\n // Matches Next.js PARAMETER_PATTERN.\n const catchAllMatch = segment.match(/^\\[\\.\\.\\.([^\\]]+)\\]$/);\n if (catchAllMatch) {\n if (i !== segments.length - 1) return null;\n // Guard: names ending in + or * would collide with internal pattern modifiers.\n if (catchAllMatch[1].endsWith(\"+\") || catchAllMatch[1].endsWith(\"*\")) return null;\n isDynamic = true;\n params.push(catchAllMatch[1]);\n urlSegments.push(`:${catchAllMatch[1]}+`);\n continue;\n }\n\n // Optional catch-all: [[...slug]] -> :slug* (param names may contain any non-] chars)\n const optionalCatchAllMatch = segment.match(/^\\[\\[\\.\\.\\.([^\\]]+)\\]\\]$/);\n if (optionalCatchAllMatch) {\n if (i !== segments.length - 1) return null;\n if (optionalCatchAllMatch[1].endsWith(\"+\") || optionalCatchAllMatch[1].endsWith(\"*\"))\n return null;\n isDynamic = true;\n params.push(optionalCatchAllMatch[1]);\n urlSegments.push(`:${optionalCatchAllMatch[1]}*`);\n continue;\n }\n\n // Dynamic segment: [id] -> :id (param names may contain any non-] chars)\n const dynamicMatch = segment.match(/^\\[([^\\]]+)\\]$/);\n if (dynamicMatch) {\n if (dynamicMatch[1].endsWith(\"+\") || dynamicMatch[1].endsWith(\"*\")) return null;\n isDynamic = true;\n params.push(dynamicMatch[1]);\n urlSegments.push(`:${dynamicMatch[1]}`);\n continue;\n }\n\n urlSegments.push(decodeRouteSegment(segment));\n }\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n\n return {\n pattern: pattern === \"/\" ? \"/\" : pattern,\n patternParts: urlSegments.filter(Boolean),\n filePath: path.join(pagesDir, file),\n isDynamic,\n params,\n };\n}\n\n// Trie cache — keyed by route array identity (same array = same trie)\nconst trieCache = createRouteTrieCache<Route>();\n\n/**\n * Match a URL path against a route pattern.\n * Returns the matched params or null if no match.\n */\nexport function matchRoute(\n url: string,\n routes: Route[],\n): { route: Route; params: Record<string, string | string[]> } | null {\n return matchRouteWithTrie(url, routes, trieCache);\n}\n\n/**\n * Scan the pages/api/ directory and return API routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js conventions:\n * - pages/api/hello.ts -> /api/hello\n * - pages/api/users/[id].ts -> /api/users/:id\n */\nexport async function apiRouter(\n pagesDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<Route[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const cacheKey = `api:${pagesDir}:${JSON.stringify(matcher.extensions)}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanApiRoutes(pagesDir, matcher);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanApiRoutes(pagesDir: string, matcher: ValidFileMatcher): Promise<Route[]> {\n const apiDir = path.join(pagesDir, \"api\");\n let files: string[];\n try {\n files = [];\n for await (const file of scanWithExtensions(\n \"**/*\",\n apiDir,\n matcher.extensions,\n (name: string) => name.startsWith(\"_\"),\n )) {\n files.push(file);\n }\n } catch {\n files = [];\n }\n\n const routes: Route[] = [];\n\n for (const file of files) {\n // Reuse fileToRoute but pretend the file is under a virtual \"api/\" prefix\n const route = fileToRoute(path.join(\"api\", file), pagesDir, matcher);\n if (route) {\n routes.push(route);\n }\n }\n\n validateRoutePatterns(routes.map((route) => route.pattern));\n\n // Sort same as page routes\n routes.sort(compareRoutes);\n\n return routes;\n}\n\n/**\n * Convert internal route pattern (e.g., \"/posts/:id\", \"/docs/:slug+\")\n * to Next.js bracket format (e.g., \"/posts/[id]\", \"/docs/[...slug]\").\n * Used for __NEXT_DATA__.page which apps expect in Next.js format.\n */\nexport { patternToNextFormat } from \"./route-validation.js\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"pages-router.js","names":[],"sources":["../../src/routing/pages-router.ts"],"sourcesContent":["import path from \"node:path\";\nimport { compareRoutes, decodeRouteSegment } from \"./utils.js\";\nimport {\n createValidFileMatcher,\n scanWithExtensions,\n type ValidFileMatcher,\n} from \"./file-matcher.js\";\nimport { validateRoutePatterns } from \"./route-validation.js\";\nimport { createRouteTrieCache, matchRouteWithTrie } from \"./route-matching.js\";\n\nexport type Route = {\n /** URL pattern, e.g. \"/\" or \"/about\" or \"/posts/:id\" */\n pattern: string;\n /** Pre-split pattern segments (computed once at scan time, reused per request) */\n patternParts: string[];\n /** Absolute file path to the page component */\n filePath: string;\n /** Whether this is a dynamic route */\n isDynamic: boolean;\n /** Parameter names for dynamic segments */\n params: string[];\n};\n\n/** Next.js special pages that should not produce routes. */\nconst RESERVED_PAGE_NAMES = new Set([\"_app\", \"_document\", \"_error\"]);\n\n// Route cache — invalidated when pages directory changes\nconst routeCache = new Map<string, { routes: Route[]; promise: Promise<Route[]> }>();\n\n/**\n * Invalidate cached routes for a given pages directory.\n * Called by the file watcher when pages are added/removed.\n */\nexport function invalidateRouteCache(pagesDir: string): void {\n for (const key of routeCache.keys()) {\n if (key.startsWith(`pages:${pagesDir}:`) || key.startsWith(`api:${pagesDir}:`)) {\n routeCache.delete(key);\n }\n }\n}\n\n/**\n * Scan the pages/ directory and return a list of routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js Pages Router conventions:\n * - pages/index.tsx -> /\n * - pages/about.tsx -> /about\n * - pages/posts/[id].tsx -> /posts/:id\n * - pages/[...slug].tsx -> /:slug+\n * - Ignores _app.tsx, _document.tsx, _error.tsx (Next.js special files)\n * - Ignores pages/api/ (handled separately later)\n */\nexport async function pagesRouter(\n pagesDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<Route[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const cacheKey = `pages:${pagesDir}:${JSON.stringify(matcher.extensions)}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanPageRoutes(pagesDir, matcher);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanPageRoutes(pagesDir: string, matcher: ValidFileMatcher): Promise<Route[]> {\n const routes: Route[] = [];\n\n // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14).\n // The `RESERVED_PAGE_NAMES` check here is a directory-traversal optimization only — glob's\n // exclude callback fires on directory names, not file names, so root-level files like\n // `_app.tsx` still get yielded and are filtered by the guard in `fileToRoute()` below.\n for await (const file of scanWithExtensions(\n \"**/*\",\n pagesDir,\n matcher.extensions,\n (name: string) => name === \"api\" || RESERVED_PAGE_NAMES.has(name),\n )) {\n const route = fileToRoute(file, pagesDir, matcher);\n if (route) routes.push(route);\n }\n\n validateRoutePatterns(routes.map((route) => route.pattern));\n\n // Sort: static routes first, then dynamic, then catch-all\n routes.sort(compareRoutes);\n\n return routes;\n}\n\n/**\n * Convert a file path relative to pages/ into a Route.\n */\nfunction fileToRoute(file: string, pagesDir: string, matcher: ValidFileMatcher): Route | null {\n // Remove extension\n const withoutExt = matcher.stripExtension(file);\n if (withoutExt === file) return null;\n\n // Convert to URL segments\n const segments = withoutExt.split(path.sep);\n\n // Handle index files: pages/index.tsx -> /\n const lastSegment = segments[segments.length - 1];\n if (lastSegment === \"index\") {\n segments.pop();\n }\n\n const params: string[] = [];\n let isDynamic = false;\n\n // Convert Next.js dynamic segments to URL patterns.\n // Catch-all segments are only valid in terminal position.\n const urlSegments: string[] = [];\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n\n // Catch-all: [...slug] -> :slug+ (param names may contain any non-] chars)\n // Matches Next.js PARAMETER_PATTERN.\n const catchAllMatch = segment.match(/^\\[\\.\\.\\.([^\\]]+)\\]$/);\n if (catchAllMatch) {\n if (i !== segments.length - 1) return null;\n // Guard: names ending in + or * would collide with internal pattern modifiers.\n if (catchAllMatch[1].endsWith(\"+\") || catchAllMatch[1].endsWith(\"*\")) return null;\n isDynamic = true;\n params.push(catchAllMatch[1]);\n urlSegments.push(`:${catchAllMatch[1]}+`);\n continue;\n }\n\n // Optional catch-all: [[...slug]] -> :slug* (param names may contain any non-] chars)\n const optionalCatchAllMatch = segment.match(/^\\[\\[\\.\\.\\.([^\\]]+)\\]\\]$/);\n if (optionalCatchAllMatch) {\n if (i !== segments.length - 1) return null;\n if (optionalCatchAllMatch[1].endsWith(\"+\") || optionalCatchAllMatch[1].endsWith(\"*\"))\n return null;\n isDynamic = true;\n params.push(optionalCatchAllMatch[1]);\n urlSegments.push(`:${optionalCatchAllMatch[1]}*`);\n continue;\n }\n\n // Dynamic segment: [id] -> :id (param names may contain any non-] chars)\n const dynamicMatch = segment.match(/^\\[([^\\]]+)\\]$/);\n if (dynamicMatch) {\n if (dynamicMatch[1].endsWith(\"+\") || dynamicMatch[1].endsWith(\"*\")) return null;\n isDynamic = true;\n params.push(dynamicMatch[1]);\n urlSegments.push(`:${dynamicMatch[1]}`);\n continue;\n }\n\n urlSegments.push(decodeRouteSegment(segment));\n }\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n\n // Skip Next.js special pages (_app, _document, _error) at the root level only.\n // Subdirectory files like admin/_app.tsx are not reserved and should be served.\n // Read segments[0] after the index pop so this is correct for both `_app.tsx`\n // and `_app/index.tsx` shapes, independent of the glob-level exclude.\n if (segments.length === 1 && RESERVED_PAGE_NAMES.has(segments[0])) {\n return null;\n }\n\n return {\n pattern: pattern === \"/\" ? \"/\" : pattern,\n patternParts: urlSegments.filter(Boolean),\n filePath: path.join(pagesDir, file),\n isDynamic,\n params,\n };\n}\n\n// Trie cache — keyed by route array identity (same array = same trie)\nconst trieCache = createRouteTrieCache<Route>();\n\n/**\n * Match a URL path against a route pattern.\n * Returns the matched params or null if no match.\n */\nexport function matchRoute(\n url: string,\n routes: Route[],\n): { route: Route; params: Record<string, string | string[]> } | null {\n return matchRouteWithTrie(url, routes, trieCache);\n}\n\n/**\n * Scan the pages/api/ directory and return API routes.\n * Results are cached — call invalidateRouteCache() when files change.\n *\n * Follows Next.js conventions:\n * - pages/api/hello.ts -> /api/hello\n * - pages/api/users/[id].ts -> /api/users/:id\n */\nexport async function apiRouter(\n pagesDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<Route[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const cacheKey = `api:${pagesDir}:${JSON.stringify(matcher.extensions)}`;\n const cached = routeCache.get(cacheKey);\n if (cached) return cached.promise;\n\n const promise = scanApiRoutes(pagesDir, matcher);\n routeCache.set(cacheKey, { routes: [], promise });\n const routes = await promise;\n routeCache.set(cacheKey, { routes, promise });\n return routes;\n}\n\nasync function scanApiRoutes(pagesDir: string, matcher: ValidFileMatcher): Promise<Route[]> {\n const apiDir = path.join(pagesDir, \"api\");\n let files: string[];\n try {\n files = [];\n for await (const file of scanWithExtensions(\"**/*\", apiDir, matcher.extensions)) {\n files.push(file);\n }\n } catch {\n files = [];\n }\n\n const routes: Route[] = [];\n\n for (const file of files) {\n // Reuse fileToRoute but pretend the file is under a virtual \"api/\" prefix\n const route = fileToRoute(path.join(\"api\", file), pagesDir, matcher);\n if (route) {\n routes.push(route);\n }\n }\n\n validateRoutePatterns(routes.map((route) => route.pattern));\n\n // Sort same as page routes\n routes.sort(compareRoutes);\n\n return routes;\n}\n\n/**\n * Convert internal route pattern (e.g., \"/posts/:id\", \"/docs/:slug+\")\n * to Next.js bracket format (e.g., \"/posts/[id]\", \"/docs/[...slug]\").\n * Used for __NEXT_DATA__.page which apps expect in Next.js format.\n */\nexport { patternToNextFormat } from \"./route-validation.js\";\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { matchRoute } from "../routing/pages-router.js";
2
2
  import "./server-globals.js";
3
3
  import { NextRequest } from "../shims/server.js";
4
- import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
5
4
  import { importModule, reportRequestError } from "./instrumentation.js";
5
+ import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
6
6
  import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
7
7
  import { isEdgeApiRuntime } from "./edge-api-runtime.js";
8
8
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT, resolveBodyParserConfig } from "./pages-body-parser-config.js";
@@ -22,21 +22,14 @@ type ServerActionInitiationSnapshot<TRouterState> = {
22
22
  declare function isServerActionResult<TRoot>(value: unknown): value is AppBrowserServerActionResult<TRoot>;
23
23
  declare function shouldClearClientNavigationCachesForServerActionResult<TRoot>(result: AppBrowserServerActionResult<TRoot> | TRoot, revalidation?: ServerActionRevalidationKind): boolean;
24
24
  declare function parseServerActionRevalidationHeader(headers: Pick<Headers, "get">): ServerActionRevalidationKind;
25
- type ServerActionRedirectLocation = {
26
- href: string;
27
- internal: boolean;
28
- };
29
- /**
30
- * Resolve a server-action redirect target against the URL that initiated the
31
- * action. Dot-relative redirects intentionally resolve as if the current route
32
- * pathname were a directory, matching Next.js' `assignLocation()` behavior:
33
- * `./subpage` from `/subdir` lands on `/subdir/subpage`, not `/subpage`.
34
- */
35
- declare function resolveServerActionRedirectLocation(options: {
36
- currentHref: string;
37
- location: string;
38
- origin: string;
39
- }): ServerActionRedirectLocation;
25
+ declare function normalizeServerActionThrownValue(data: unknown, responseStatus: number): unknown;
26
+ declare function readInvalidServerActionResponseError(response: Pick<Response, "headers" | "status" | "text">, hasRedirectLocation: boolean): Promise<Error | null>;
27
+ declare function shouldCheckRscCompatibilityForServerActionResponse(response: Pick<Response, "headers">): boolean;
28
+ declare function resolveServerActionRedirectCompatibilityHardNavigationTarget(options: {
29
+ actionRedirectHref: string | null;
30
+ clientCompatibilityId: string | null | undefined;
31
+ response: Pick<Response, "headers">;
32
+ }): string | null;
40
33
  declare function shouldScheduleRefreshForDiscardedServerAction(revalidation: ServerActionRevalidationKind): boolean;
41
34
  declare function createServerActionInitiationSnapshot<TRouterState>(options: {
42
35
  href: string;
@@ -55,5 +48,5 @@ type DiscardedServerActionRefreshSchedulerOptions = {
55
48
  };
56
49
  declare function createDiscardedServerActionRefreshScheduler(options: DiscardedServerActionRefreshSchedulerOptions): DiscardedServerActionRefreshScheduler;
57
50
  //#endregion
58
- export { AppBrowserServerActionResult, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, resolveServerActionRedirectLocation, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
51
+ export { AppBrowserServerActionResult, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, resolveServerActionRedirectCompatibilityHardNavigationTarget, shouldCheckRscCompatibilityForServerActionResponse, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
59
52
  //# sourceMappingURL=app-browser-action-result.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { ACTION_REVALIDATED_HEADER } from "./headers.js";
2
+ import { VINEXT_RSC_CONTENT_TYPE, isRscCompatibilityIdCompatible } from "./app-rsc-cache-busting.js";
2
3
  //#region src/server/app-browser-action-result.ts
3
4
  const ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;
4
5
  const ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;
@@ -31,19 +32,29 @@ function parseServerActionRevalidationHeader(headers) {
31
32
  default: return "none";
32
33
  }
33
34
  }
34
- /**
35
- * Resolve a server-action redirect target against the URL that initiated the
36
- * action. Dot-relative redirects intentionally resolve as if the current route
37
- * pathname were a directory, matching Next.js' `assignLocation()` behavior:
38
- * `./subpage` from `/subdir` lands on `/subdir/subpage`, not `/subpage`.
39
- */
40
- function resolveServerActionRedirectLocation(options) {
41
- const currentUrl = new URL(options.currentHref, options.origin);
42
- const redirectUrl = options.location.startsWith(".") ? new URL(options.location, `${currentUrl.origin}${currentUrl.pathname.endsWith("/") ? currentUrl.pathname : `${currentUrl.pathname}/`}`) : new URL(options.location, currentUrl.href);
43
- return {
44
- href: redirectUrl.href,
45
- internal: redirectUrl.origin === currentUrl.origin
46
- };
35
+ function createServerActionHttpFallbackError(status) {
36
+ if (status < 400 || status > 599) return null;
37
+ const digest = status === 404 ? "NEXT_HTTP_ERROR_FALLBACK;404" : `NEXT_HTTP_ERROR_FALLBACK;${status}`;
38
+ const error = /* @__PURE__ */ new Error(status === 404 ? "NEXT_NOT_FOUND" : `NEXT_HTTP_ERROR_FALLBACK;${status}`);
39
+ return Object.assign(error, { digest });
40
+ }
41
+ function normalizeServerActionThrownValue(data, responseStatus) {
42
+ return createServerActionHttpFallbackError(responseStatus) ?? data;
43
+ }
44
+ async function readInvalidServerActionResponseError(response, hasRedirectLocation) {
45
+ const contentType = response.headers.get("content-type") ?? "";
46
+ if (contentType.startsWith("text/x-component") || hasRedirectLocation) return null;
47
+ const message = response.status >= 400 && contentType.toLowerCase().startsWith("text/plain") ? await response.text() : "An unexpected response was received from the server.";
48
+ return new Error(message || "An unexpected response was received from the server.");
49
+ }
50
+ function shouldCheckRscCompatibilityForServerActionResponse(response) {
51
+ return (response.headers.get("content-type") ?? "").startsWith(VINEXT_RSC_CONTENT_TYPE);
52
+ }
53
+ function resolveServerActionRedirectCompatibilityHardNavigationTarget(options) {
54
+ if (!options.actionRedirectHref) return null;
55
+ if (!shouldCheckRscCompatibilityForServerActionResponse(options.response)) return null;
56
+ if (isRscCompatibilityIdCompatible(options.response.headers.get("X-Vinext-RSC-Compatibility-Id"), options.clientCompatibilityId)) return null;
57
+ return options.actionRedirectHref;
47
58
  }
48
59
  function shouldScheduleRefreshForDiscardedServerAction(revalidation) {
49
60
  return revalidation !== "none";
@@ -88,6 +99,6 @@ function createDiscardedServerActionRefreshScheduler(options) {
88
99
  };
89
100
  }
90
101
  //#endregion
91
- export { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, parseServerActionRevalidationHeader, resolveServerActionRedirectLocation, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
102
+ export { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, resolveServerActionRedirectCompatibilityHardNavigationTarget, shouldCheckRscCompatibilityForServerActionResponse, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
92
103
 
93
104
  //# sourceMappingURL=app-browser-action-result.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-browser-action-result.js","names":[],"sources":["../../src/server/app-browser-action-result.ts"],"sourcesContent":["import { ACTION_REVALIDATED_HEADER } from \"./headers.js\";\n\nexport type AppBrowserServerActionResult<TRoot> = {\n root?: TRoot;\n returnValue?: {\n ok: boolean;\n data: unknown;\n };\n};\n\nexport type ServerActionRevalidationKind = \"dynamicOnly\" | \"none\" | \"staticAndDynamic\";\n\nconst ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;\nconst ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;\n\ntype ServerActionInitiationSnapshot<TRouterState> = {\n href: string;\n navigationId: number;\n path: string;\n routerState: TRouterState;\n};\n\n/**\n * Structural discriminator: matches on `\"returnValue\"` or `\"root\"` keys.\n * This is safe because {@link AppWireElements} keys are prefixed (`route:`,\n * `slot:`, `__route`, etc.) and will never collide with these property names.\n * If the wire format ever adds a `\"root\"` key, this guard must be updated.\n */\nexport function isServerActionResult<TRoot>(\n value: unknown,\n): value is AppBrowserServerActionResult<TRoot> {\n return !!value && typeof value === \"object\" && (\"returnValue\" in value || \"root\" in value);\n}\n\nexport function shouldClearClientNavigationCachesForServerActionResult<TRoot>(\n result: AppBrowserServerActionResult<TRoot> | TRoot,\n revalidation: ServerActionRevalidationKind = \"none\",\n): boolean {\n if (revalidation !== \"none\") {\n return true;\n }\n\n if (!isServerActionResult<TRoot>(result)) {\n return true;\n }\n\n return result.root !== undefined;\n}\n\nexport function parseServerActionRevalidationHeader(\n headers: Pick<Headers, \"get\">,\n): ServerActionRevalidationKind {\n const value = headers.get(ACTION_REVALIDATED_HEADER);\n if (!value) return \"none\";\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return \"none\";\n }\n\n switch (parsed) {\n case ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC:\n return \"staticAndDynamic\";\n case ACTION_DID_REVALIDATE_DYNAMIC_ONLY:\n return \"dynamicOnly\";\n default:\n return \"none\";\n }\n}\n\ntype ServerActionRedirectLocation = {\n href: string;\n internal: boolean;\n};\n\n/**\n * Resolve a server-action redirect target against the URL that initiated the\n * action. Dot-relative redirects intentionally resolve as if the current route\n * pathname were a directory, matching Next.js' `assignLocation()` behavior:\n * `./subpage` from `/subdir` lands on `/subdir/subpage`, not `/subpage`.\n */\nexport function resolveServerActionRedirectLocation(options: {\n currentHref: string;\n location: string;\n origin: string;\n}): ServerActionRedirectLocation {\n const currentUrl = new URL(options.currentHref, options.origin);\n const redirectUrl = options.location.startsWith(\".\")\n ? new URL(\n options.location,\n `${currentUrl.origin}${currentUrl.pathname.endsWith(\"/\") ? currentUrl.pathname : `${currentUrl.pathname}/`}`,\n )\n : new URL(options.location, currentUrl.href);\n\n return {\n href: redirectUrl.href,\n internal: redirectUrl.origin === currentUrl.origin,\n };\n}\n\nexport function shouldScheduleRefreshForDiscardedServerAction(\n revalidation: ServerActionRevalidationKind,\n): boolean {\n return revalidation !== \"none\";\n}\n\nexport function createServerActionInitiationSnapshot<TRouterState>(options: {\n href: string;\n navigationId: number;\n origin?: string;\n routerState: TRouterState;\n}): ServerActionInitiationSnapshot<TRouterState> {\n const url =\n options.origin === undefined ? new URL(options.href) : new URL(options.href, options.origin);\n return {\n href: url.href,\n navigationId: options.navigationId,\n path: url.pathname + url.search,\n routerState: options.routerState,\n };\n}\n\ntype DiscardedServerActionRefreshScheduler = {\n markNavigationSettled(): void;\n markNavigationStart(): void;\n schedule(): void;\n};\n\ntype DiscardedServerActionRefreshSchedulerOptions = {\n queueTask?: (callback: () => void) => void;\n runRefresh: () => void;\n};\n\nexport function createDiscardedServerActionRefreshScheduler(\n options: DiscardedServerActionRefreshSchedulerOptions,\n): DiscardedServerActionRefreshScheduler {\n const queueTask = options.queueTask ?? queueMicrotask;\n let activeNavigationCount = 0;\n let flushQueued = false;\n let refreshPending = false;\n\n function flush(): void {\n flushQueued = false;\n if (!refreshPending || activeNavigationCount > 0) return;\n\n refreshPending = false;\n options.runRefresh();\n }\n\n function queueFlush(): void {\n if (flushQueued) return;\n flushQueued = true;\n queueTask(flush);\n }\n\n return {\n markNavigationSettled() {\n if (activeNavigationCount > 0) {\n activeNavigationCount -= 1;\n }\n queueFlush();\n },\n markNavigationStart() {\n activeNavigationCount += 1;\n },\n schedule() {\n refreshPending = true;\n queueFlush();\n },\n };\n}\n"],"mappings":";;AAYA,MAAM,2CAA2C;AACjD,MAAM,qCAAqC;;;;;;;AAe3C,SAAgB,qBACd,OAC8C;CAC9C,OAAO,CAAC,CAAC,SAAS,OAAO,UAAU,aAAa,iBAAiB,SAAS,UAAU;;AAGtF,SAAgB,uDACd,QACA,eAA6C,QACpC;CACT,IAAI,iBAAiB,QACnB,OAAO;CAGT,IAAI,CAAC,qBAA4B,OAAO,EACtC,OAAO;CAGT,OAAO,OAAO,SAAS,KAAA;;AAGzB,SAAgB,oCACd,SAC8B;CAC9B,MAAM,QAAQ,QAAQ,IAAI,0BAA0B;CACpD,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,MAAM;SACpB;EACN,OAAO;;CAGT,QAAQ,QAAR;EACE,KAAK,0CACH,OAAO;EACT,KAAK,oCACH,OAAO;EACT,SACE,OAAO;;;;;;;;;AAeb,SAAgB,oCAAoC,SAInB;CAC/B,MAAM,aAAa,IAAI,IAAI,QAAQ,aAAa,QAAQ,OAAO;CAC/D,MAAM,cAAc,QAAQ,SAAS,WAAW,IAAI,GAChD,IAAI,IACF,QAAQ,UACR,GAAG,WAAW,SAAS,WAAW,SAAS,SAAS,IAAI,GAAG,WAAW,WAAW,GAAG,WAAW,SAAS,KACzG,GACD,IAAI,IAAI,QAAQ,UAAU,WAAW,KAAK;CAE9C,OAAO;EACL,MAAM,YAAY;EAClB,UAAU,YAAY,WAAW,WAAW;EAC7C;;AAGH,SAAgB,8CACd,cACS;CACT,OAAO,iBAAiB;;AAG1B,SAAgB,qCAAmD,SAKlB;CAC/C,MAAM,MACJ,QAAQ,WAAW,KAAA,IAAY,IAAI,IAAI,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ,MAAM,QAAQ,OAAO;CAC9F,OAAO;EACL,MAAM,IAAI;EACV,cAAc,QAAQ;EACtB,MAAM,IAAI,WAAW,IAAI;EACzB,aAAa,QAAQ;EACtB;;AAcH,SAAgB,4CACd,SACuC;CACvC,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,wBAAwB;CAC5B,IAAI,cAAc;CAClB,IAAI,iBAAiB;CAErB,SAAS,QAAc;EACrB,cAAc;EACd,IAAI,CAAC,kBAAkB,wBAAwB,GAAG;EAElD,iBAAiB;EACjB,QAAQ,YAAY;;CAGtB,SAAS,aAAmB;EAC1B,IAAI,aAAa;EACjB,cAAc;EACd,UAAU,MAAM;;CAGlB,OAAO;EACL,wBAAwB;GACtB,IAAI,wBAAwB,GAC1B,yBAAyB;GAE3B,YAAY;;EAEd,sBAAsB;GACpB,yBAAyB;;EAE3B,WAAW;GACT,iBAAiB;GACjB,YAAY;;EAEf"}
1
+ {"version":3,"file":"app-browser-action-result.js","names":[],"sources":["../../src/server/app-browser-action-result.ts"],"sourcesContent":["import { ACTION_REVALIDATED_HEADER } from \"./headers.js\";\nimport {\n isRscCompatibilityIdCompatible,\n VINEXT_RSC_COMPATIBILITY_ID_HEADER,\n VINEXT_RSC_CONTENT_TYPE,\n} from \"./app-rsc-cache-busting.js\";\n\nexport type AppBrowserServerActionResult<TRoot> = {\n root?: TRoot;\n returnValue?: {\n ok: boolean;\n data: unknown;\n };\n};\n\nexport type ServerActionRevalidationKind = \"dynamicOnly\" | \"none\" | \"staticAndDynamic\";\n\nconst ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC = 1;\nconst ACTION_DID_REVALIDATE_DYNAMIC_ONLY = 2;\n\ntype ServerActionInitiationSnapshot<TRouterState> = {\n href: string;\n navigationId: number;\n path: string;\n routerState: TRouterState;\n};\n\n/**\n * Structural discriminator: matches on `\"returnValue\"` or `\"root\"` keys.\n * This is safe because {@link AppWireElements} keys are prefixed (`route:`,\n * `slot:`, `__route`, etc.) and will never collide with these property names.\n * If the wire format ever adds a `\"root\"` key, this guard must be updated.\n */\nexport function isServerActionResult<TRoot>(\n value: unknown,\n): value is AppBrowserServerActionResult<TRoot> {\n return !!value && typeof value === \"object\" && (\"returnValue\" in value || \"root\" in value);\n}\n\nexport function shouldClearClientNavigationCachesForServerActionResult<TRoot>(\n result: AppBrowserServerActionResult<TRoot> | TRoot,\n revalidation: ServerActionRevalidationKind = \"none\",\n): boolean {\n if (revalidation !== \"none\") {\n return true;\n }\n\n if (!isServerActionResult<TRoot>(result)) {\n return true;\n }\n\n return result.root !== undefined;\n}\n\nexport function parseServerActionRevalidationHeader(\n headers: Pick<Headers, \"get\">,\n): ServerActionRevalidationKind {\n const value = headers.get(ACTION_REVALIDATED_HEADER);\n if (!value) return \"none\";\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(value);\n } catch {\n return \"none\";\n }\n\n switch (parsed) {\n case ACTION_DID_REVALIDATE_STATIC_AND_DYNAMIC:\n return \"staticAndDynamic\";\n case ACTION_DID_REVALIDATE_DYNAMIC_ONLY:\n return \"dynamicOnly\";\n default:\n return \"none\";\n }\n}\n\nfunction createServerActionHttpFallbackError(status: number): (Error & { digest: string }) | null {\n if (status < 400 || status > 599) return null;\n\n const digest =\n status === 404 ? \"NEXT_HTTP_ERROR_FALLBACK;404\" : `NEXT_HTTP_ERROR_FALLBACK;${status}`;\n const error = new Error(status === 404 ? \"NEXT_NOT_FOUND\" : `NEXT_HTTP_ERROR_FALLBACK;${status}`);\n return Object.assign(error, { digest });\n}\n\nexport function normalizeServerActionThrownValue(data: unknown, responseStatus: number): unknown {\n return createServerActionHttpFallbackError(responseStatus) ?? data;\n}\n\nexport async function readInvalidServerActionResponseError(\n response: Pick<Response, \"headers\" | \"status\" | \"text\">,\n hasRedirectLocation: boolean,\n): Promise<Error | null> {\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n const isRscResponse = contentType.startsWith(VINEXT_RSC_CONTENT_TYPE);\n if (isRscResponse || hasRedirectLocation) return null;\n\n // Parity with Next.js' server-action reducer: any non-RSC action response,\n // including a 2xx, is surfaced to the action caller. Plain text 4xx/5xx\n // bodies are preserved when available; other responses use a stable generic\n // message.\n const message =\n response.status >= 400 && contentType.toLowerCase().startsWith(\"text/plain\")\n ? await response.text()\n : \"An unexpected response was received from the server.\";\n\n return new Error(message || \"An unexpected response was received from the server.\");\n}\n\nexport function shouldCheckRscCompatibilityForServerActionResponse(\n response: Pick<Response, \"headers\">,\n): boolean {\n return (response.headers.get(\"content-type\") ?? \"\").startsWith(VINEXT_RSC_CONTENT_TYPE);\n}\n\nexport function resolveServerActionRedirectCompatibilityHardNavigationTarget(options: {\n actionRedirectHref: string | null;\n clientCompatibilityId: string | null | undefined;\n response: Pick<Response, \"headers\">;\n}): string | null {\n if (!options.actionRedirectHref) return null;\n if (!shouldCheckRscCompatibilityForServerActionResponse(options.response)) return null;\n if (\n isRscCompatibilityIdCompatible(\n options.response.headers.get(VINEXT_RSC_COMPATIBILITY_ID_HEADER),\n options.clientCompatibilityId,\n )\n ) {\n return null;\n }\n return options.actionRedirectHref;\n}\n\nexport function shouldScheduleRefreshForDiscardedServerAction(\n revalidation: ServerActionRevalidationKind,\n): boolean {\n return revalidation !== \"none\";\n}\n\nexport function createServerActionInitiationSnapshot<TRouterState>(options: {\n href: string;\n navigationId: number;\n origin?: string;\n routerState: TRouterState;\n}): ServerActionInitiationSnapshot<TRouterState> {\n const url =\n options.origin === undefined ? new URL(options.href) : new URL(options.href, options.origin);\n return {\n href: url.href,\n navigationId: options.navigationId,\n path: url.pathname + url.search,\n routerState: options.routerState,\n };\n}\n\ntype DiscardedServerActionRefreshScheduler = {\n markNavigationSettled(): void;\n markNavigationStart(): void;\n schedule(): void;\n};\n\ntype DiscardedServerActionRefreshSchedulerOptions = {\n queueTask?: (callback: () => void) => void;\n runRefresh: () => void;\n};\n\nexport function createDiscardedServerActionRefreshScheduler(\n options: DiscardedServerActionRefreshSchedulerOptions,\n): DiscardedServerActionRefreshScheduler {\n const queueTask = options.queueTask ?? queueMicrotask;\n let activeNavigationCount = 0;\n let flushQueued = false;\n let refreshPending = false;\n\n function flush(): void {\n flushQueued = false;\n if (!refreshPending || activeNavigationCount > 0) return;\n\n refreshPending = false;\n options.runRefresh();\n }\n\n function queueFlush(): void {\n if (flushQueued) return;\n flushQueued = true;\n queueTask(flush);\n }\n\n return {\n markNavigationSettled() {\n if (activeNavigationCount > 0) {\n activeNavigationCount -= 1;\n }\n queueFlush();\n },\n markNavigationStart() {\n activeNavigationCount += 1;\n },\n schedule() {\n refreshPending = true;\n queueFlush();\n },\n };\n}\n"],"mappings":";;;AAiBA,MAAM,2CAA2C;AACjD,MAAM,qCAAqC;;;;;;;AAe3C,SAAgB,qBACd,OAC8C;CAC9C,OAAO,CAAC,CAAC,SAAS,OAAO,UAAU,aAAa,iBAAiB,SAAS,UAAU;;AAGtF,SAAgB,uDACd,QACA,eAA6C,QACpC;CACT,IAAI,iBAAiB,QACnB,OAAO;CAGT,IAAI,CAAC,qBAA4B,OAAO,EACtC,OAAO;CAGT,OAAO,OAAO,SAAS,KAAA;;AAGzB,SAAgB,oCACd,SAC8B;CAC9B,MAAM,QAAQ,QAAQ,IAAI,0BAA0B;CACpD,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,MAAM;SACpB;EACN,OAAO;;CAGT,QAAQ,QAAR;EACE,KAAK,0CACH,OAAO;EACT,KAAK,oCACH,OAAO;EACT,SACE,OAAO;;;AAIb,SAAS,oCAAoC,QAAqD;CAChG,IAAI,SAAS,OAAO,SAAS,KAAK,OAAO;CAEzC,MAAM,SACJ,WAAW,MAAM,iCAAiC,4BAA4B;CAChF,MAAM,wBAAQ,IAAI,MAAM,WAAW,MAAM,mBAAmB,4BAA4B,SAAS;CACjG,OAAO,OAAO,OAAO,OAAO,EAAE,QAAQ,CAAC;;AAGzC,SAAgB,iCAAiC,MAAe,gBAAiC;CAC/F,OAAO,oCAAoC,eAAe,IAAI;;AAGhE,eAAsB,qCACpB,UACA,qBACuB;CACvB,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe,IAAI;CAE5D,IADsB,YAAY,WAAA,mBACjB,IAAI,qBAAqB,OAAO;CAMjD,MAAM,UACJ,SAAS,UAAU,OAAO,YAAY,aAAa,CAAC,WAAW,aAAa,GACxE,MAAM,SAAS,MAAM,GACrB;CAEN,OAAO,IAAI,MAAM,WAAW,uDAAuD;;AAGrF,SAAgB,mDACd,UACS;CACT,QAAQ,SAAS,QAAQ,IAAI,eAAe,IAAI,IAAI,WAAW,wBAAwB;;AAGzF,SAAgB,6DAA6D,SAI3D;CAChB,IAAI,CAAC,QAAQ,oBAAoB,OAAO;CACxC,IAAI,CAAC,mDAAmD,QAAQ,SAAS,EAAE,OAAO;CAClF,IACE,+BACE,QAAQ,SAAS,QAAQ,IAAA,gCAAuC,EAChE,QAAQ,sBACT,EAED,OAAO;CAET,OAAO,QAAQ;;AAGjB,SAAgB,8CACd,cACS;CACT,OAAO,iBAAiB;;AAG1B,SAAgB,qCAAmD,SAKlB;CAC/C,MAAM,MACJ,QAAQ,WAAW,KAAA,IAAY,IAAI,IAAI,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ,MAAM,QAAQ,OAAO;CAC9F,OAAO;EACL,MAAM,IAAI;EACV,cAAc,QAAQ;EACtB,MAAM,IAAI,WAAW,IAAI;EACzB,aAAa,QAAQ;EACtB;;AAcH,SAAgB,4CACd,SACuC;CACvC,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,wBAAwB;CAC5B,IAAI,cAAc;CAClB,IAAI,iBAAiB;CAErB,SAAS,QAAc;EACrB,cAAc;EACd,IAAI,CAAC,kBAAkB,wBAAwB,GAAG;EAElD,iBAAiB;EACjB,QAAQ,YAAY;;CAGtB,SAAS,aAAmB;EAC1B,IAAI,aAAa;EACjB,cAAc;EACd,UAAU,MAAM;;CAGlB,OAAO;EACL,wBAAwB;GACtB,IAAI,wBAAwB,GAC1B,yBAAyB;GAE3B,YAAY;;EAEd,sBAAsB;GACpB,yBAAyB;;EAE3B,WAAW;GACT,iBAAiB;GACjB,YAAY;;EAEf"}