vinext 0.1.7 → 0.2.0

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 (279) hide show
  1. package/README.md +51 -30
  2. package/dist/build/inject-pregenerated-paths.d.ts +4 -0
  3. package/dist/build/inject-pregenerated-paths.js +18 -0
  4. package/dist/build/layout-classification.js +1 -1
  5. package/dist/build/nitro-route-rules.js +1 -1
  6. package/dist/build/pages-client-assets-module.d.ts +11 -0
  7. package/dist/build/pages-client-assets-module.js +27 -0
  8. package/dist/build/prerender.d.ts +2 -1
  9. package/dist/build/prerender.js +13 -6
  10. package/dist/build/report.d.ts +13 -2
  11. package/dist/build/report.js +99 -2
  12. package/dist/build/run-prerender.d.ts +7 -0
  13. package/dist/build/run-prerender.js +10 -1
  14. package/dist/build/standalone.js +2 -0
  15. package/dist/cache/cache-adapters-virtual.js +3 -3
  16. package/dist/check.d.ts +18 -0
  17. package/dist/check.js +77 -19
  18. package/dist/cli-args.d.ts +1 -2
  19. package/dist/cli-args.js +1 -1
  20. package/dist/cli-dev-config.d.ts +12 -0
  21. package/dist/cli-dev-config.js +23 -0
  22. package/dist/cli.js +113 -97
  23. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  24. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  25. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  26. package/dist/{server → client}/dev-error-overlay.js +2 -2
  27. package/dist/client/navigation-runtime.d.ts +2 -0
  28. package/dist/client/navigation-runtime.js +3 -1
  29. package/dist/client/window-next.d.ts +2 -1
  30. package/dist/cloudflare/index.d.ts +2 -2
  31. package/dist/cloudflare/index.js +2 -2
  32. package/dist/config/config-matchers.js +14 -10
  33. package/dist/config/next-config.d.ts +6 -3
  34. package/dist/config/next-config.js +47 -1
  35. package/dist/config/prerender.d.ts +34 -0
  36. package/dist/config/prerender.js +55 -0
  37. package/dist/config/server-external-packages.d.ts +4 -0
  38. package/dist/config/server-external-packages.js +91 -0
  39. package/dist/entries/app-rsc-entry.d.ts +2 -1
  40. package/dist/entries/app-rsc-entry.js +89 -15
  41. package/dist/entries/app-rsc-manifest.js +10 -0
  42. package/dist/entries/pages-client-entry.d.ts +3 -0
  43. package/dist/entries/pages-client-entry.js +16 -1
  44. package/dist/entries/pages-server-entry.js +6 -2
  45. package/dist/image/image-adapters-virtual.d.ts +59 -0
  46. package/dist/image/image-adapters-virtual.js +59 -0
  47. package/dist/index.d.ts +33 -0
  48. package/dist/index.js +211 -118
  49. package/dist/init-cloudflare.d.ts +43 -0
  50. package/dist/init-cloudflare.js +839 -0
  51. package/dist/init-platform.d.ts +40 -0
  52. package/dist/init-platform.js +195 -0
  53. package/dist/init.d.ts +16 -38
  54. package/dist/init.js +208 -97
  55. package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
  56. package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
  57. package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
  58. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
  59. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +1 -1
  60. package/dist/packages/cloudflare/src/deploy-config.js +150 -0
  61. package/dist/packages/cloudflare/src/deploy-help.js +55 -0
  62. package/dist/packages/cloudflare/src/deploy.js +276 -0
  63. package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
  64. package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +18 -15
  65. package/dist/plugins/ast-scope.d.ts +16 -0
  66. package/dist/plugins/ast-scope.js +62 -0
  67. package/dist/plugins/ast-utils.d.ts +27 -1
  68. package/dist/plugins/ast-utils.js +32 -1
  69. package/dist/plugins/css-module-imports.d.ts +14 -0
  70. package/dist/plugins/css-module-imports.js +59 -0
  71. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  72. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  73. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  74. package/dist/plugins/middleware-server-only.d.ts +8 -6
  75. package/dist/plugins/middleware-server-only.js +8 -7
  76. package/dist/plugins/optimize-imports.js +1 -1
  77. package/dist/plugins/typeof-window.d.ts +1 -1
  78. package/dist/plugins/typeof-window.js +28 -56
  79. package/dist/routing/app-route-graph.d.ts +29 -4
  80. package/dist/routing/app-route-graph.js +182 -40
  81. package/dist/routing/app-router.d.ts +5 -0
  82. package/dist/routing/app-router.js +5 -0
  83. package/dist/routing/file-matcher.d.ts +8 -0
  84. package/dist/routing/file-matcher.js +10 -1
  85. package/dist/routing/pages-router.js +2 -2
  86. package/dist/server/api-handler.js +9 -14
  87. package/dist/server/app-browser-action-result.d.ts +4 -1
  88. package/dist/server/app-browser-action-result.js +8 -1
  89. package/dist/server/app-browser-entry.js +194 -190
  90. package/dist/server/app-browser-history-controller.d.ts +2 -1
  91. package/dist/server/app-browser-history-controller.js +6 -2
  92. package/dist/server/app-browser-interception-context.d.ts +1 -0
  93. package/dist/server/app-browser-interception-context.js +4 -2
  94. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  95. package/dist/server/app-browser-navigation-controller.js +60 -31
  96. package/dist/server/app-browser-server-action-client.d.ts +32 -0
  97. package/dist/server/app-browser-server-action-client.js +127 -0
  98. package/dist/server/app-browser-state.d.ts +11 -1
  99. package/dist/server/app-browser-state.js +25 -8
  100. package/dist/server/app-browser-visible-commit.d.ts +6 -1
  101. package/dist/server/app-browser-visible-commit.js +43 -25
  102. package/dist/server/app-elements-wire.d.ts +9 -2
  103. package/dist/server/app-elements-wire.js +8 -1
  104. package/dist/server/app-elements.d.ts +2 -2
  105. package/dist/server/app-elements.js +4 -3
  106. package/dist/server/app-fallback-renderer.d.ts +5 -2
  107. package/dist/server/app-fallback-renderer.js +15 -7
  108. package/dist/server/app-hydration-cache-publication.d.ts +11 -0
  109. package/dist/server/app-hydration-cache-publication.js +43 -0
  110. package/dist/server/app-middleware.d.ts +4 -3
  111. package/dist/server/app-middleware.js +8 -3
  112. package/dist/server/app-optimistic-routing.js +11 -4
  113. package/dist/server/app-page-boundary-render.d.ts +3 -0
  114. package/dist/server/app-page-boundary-render.js +14 -3
  115. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  116. package/dist/server/app-page-cache-finalizer.js +10 -3
  117. package/dist/server/app-page-cache-render.d.ts +1 -0
  118. package/dist/server/app-page-cache-render.js +8 -4
  119. package/dist/server/app-page-cache.d.ts +1 -0
  120. package/dist/server/app-page-cache.js +5 -2
  121. package/dist/server/app-page-dispatch.d.ts +33 -4
  122. package/dist/server/app-page-dispatch.js +63 -90
  123. package/dist/server/app-page-element-builder.d.ts +9 -2
  124. package/dist/server/app-page-element-builder.js +114 -23
  125. package/dist/server/app-page-head.d.ts +16 -1
  126. package/dist/server/app-page-head.js +44 -22
  127. package/dist/server/app-page-params.d.ts +2 -1
  128. package/dist/server/app-page-params.js +8 -1
  129. package/dist/server/app-page-ppr-runtime.d.ts +7 -0
  130. package/dist/server/app-page-ppr-runtime.js +70 -0
  131. package/dist/server/app-page-probe.d.ts +1 -0
  132. package/dist/server/app-page-probe.js +6 -1
  133. package/dist/server/app-page-render-identity.d.ts +1 -0
  134. package/dist/server/app-page-render-identity.js +1 -1
  135. package/dist/server/app-page-render.d.ts +4 -1
  136. package/dist/server/app-page-render.js +26 -3
  137. package/dist/server/app-page-request.d.ts +22 -1
  138. package/dist/server/app-page-request.js +92 -15
  139. package/dist/server/app-page-route-wiring.d.ts +14 -2
  140. package/dist/server/app-page-route-wiring.js +61 -25
  141. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  142. package/dist/server/app-page-search-params-observation.js +11 -7
  143. package/dist/server/app-page-segment-state.d.ts +2 -1
  144. package/dist/server/app-page-segment-state.js +3 -1
  145. package/dist/server/app-page-stream.d.ts +5 -0
  146. package/dist/server/app-page-stream.js +3 -1
  147. package/dist/server/app-ppr-fallback-shell-render.d.ts +4 -3
  148. package/dist/server/app-route-handler-dispatch.js +1 -0
  149. package/dist/server/app-route-handler-execution.js +7 -2
  150. package/dist/server/app-route-handler-response.js +1 -0
  151. package/dist/server/app-route-handler-runtime.js +1 -1
  152. package/dist/server/app-route-module-loader.d.ts +2 -0
  153. package/dist/server/app-route-module-loader.js +1 -0
  154. package/dist/server/app-router-entry.js +16 -8
  155. package/dist/server/app-rsc-errors.js +7 -1
  156. package/dist/server/app-rsc-handler.d.ts +3 -1
  157. package/dist/server/app-rsc-handler.js +67 -39
  158. package/dist/server/app-rsc-request-normalization.d.ts +3 -2
  159. package/dist/server/app-rsc-request-normalization.js +6 -3
  160. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  161. package/dist/server/app-rsc-route-matching.js +36 -3
  162. package/dist/server/app-segment-config.d.ts +12 -0
  163. package/dist/server/app-segment-config.js +91 -5
  164. package/dist/server/app-server-action-execution.d.ts +5 -0
  165. package/dist/server/app-server-action-execution.js +94 -33
  166. package/dist/server/app-ssr-entry.d.ts +3 -0
  167. package/dist/server/app-ssr-entry.js +19 -8
  168. package/dist/server/app-ssr-stream.d.ts +7 -3
  169. package/dist/server/app-ssr-stream.js +10 -6
  170. package/dist/server/app-static-generation.d.ts +1 -0
  171. package/dist/server/app-static-generation.js +1 -0
  172. package/dist/server/app-visited-response-cache.d.ts +6 -0
  173. package/dist/server/app-visited-response-cache.js +3 -1
  174. package/dist/server/client-trace-metadata.js +26 -0
  175. package/dist/server/default-global-not-found-module.d.ts +14 -0
  176. package/dist/server/default-global-not-found-module.js +14 -0
  177. package/dist/server/dev-route-files.js +4 -0
  178. package/dist/server/dev-server.js +19 -18
  179. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  180. package/dist/server/dev-stack-sourcemap.js +1 -1
  181. package/dist/server/fetch-handler.d.ts +2 -0
  182. package/dist/server/fetch-handler.js +18 -0
  183. package/dist/server/headers.d.ts +5 -15
  184. package/dist/server/headers.js +4 -15
  185. package/dist/server/image-optimization.d.ts +51 -1
  186. package/dist/server/image-optimization.js +52 -2
  187. package/dist/server/implicit-tags.js +1 -1
  188. package/dist/server/isr-cache.d.ts +1 -1
  189. package/dist/server/isr-cache.js +2 -2
  190. package/dist/server/metadata-routes.d.ts +3 -1
  191. package/dist/server/metadata-routes.js +12 -2
  192. package/dist/server/middleware-runtime.js +6 -1
  193. package/dist/server/navigation-planner.d.ts +1 -0
  194. package/dist/server/navigation-planner.js +14 -3
  195. package/dist/server/pages-api-route.js +2 -2
  196. package/dist/server/pages-asset-tags.d.ts +4 -6
  197. package/dist/server/pages-asset-tags.js +12 -12
  198. package/dist/server/pages-client-assets.d.ts +12 -0
  199. package/dist/server/pages-client-assets.js +10 -0
  200. package/dist/server/pages-node-compat.d.ts +8 -1
  201. package/dist/server/pages-node-compat.js +32 -4
  202. package/dist/server/pages-page-data.d.ts +24 -1
  203. package/dist/server/pages-page-data.js +45 -24
  204. package/dist/server/pages-page-handler.d.ts +2 -1
  205. package/dist/server/pages-page-handler.js +19 -12
  206. package/dist/server/pages-page-response.d.ts +7 -0
  207. package/dist/server/pages-page-response.js +4 -1
  208. package/dist/server/pages-request-pipeline.d.ts +2 -0
  209. package/dist/server/pages-request-pipeline.js +25 -1
  210. package/dist/server/pages-router-entry.d.ts +30 -0
  211. package/dist/server/pages-router-entry.js +108 -0
  212. package/dist/server/prerender-manifest.d.ts +3 -1
  213. package/dist/server/prerender-route-params.js +1 -1
  214. package/dist/server/prod-server.d.ts +1 -1
  215. package/dist/server/prod-server.js +48 -26
  216. package/dist/server/request-pipeline.js +1 -0
  217. package/dist/server/seed-cache.js +4 -4
  218. package/dist/server/worker-utils.d.ts +2 -1
  219. package/dist/server/worker-utils.js +7 -1
  220. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  221. package/dist/shims/app-router-scroll-state.js +1 -0
  222. package/dist/shims/app-router-scroll.js +2 -1
  223. package/dist/shims/cache.js +19 -15
  224. package/dist/shims/cdn-cache.js +1 -1
  225. package/dist/shims/dynamic-preload-chunks.js +2 -1
  226. package/dist/shims/error-boundary.d.ts +19 -1
  227. package/dist/shims/error-boundary.js +11 -1
  228. package/dist/shims/form.d.ts +3 -1
  229. package/dist/shims/form.js +37 -43
  230. package/dist/shims/headers.d.ts +9 -1
  231. package/dist/shims/headers.js +31 -6
  232. package/dist/shims/image.js +75 -14
  233. package/dist/shims/internal/app-route-detection.d.ts +2 -17
  234. package/dist/shims/internal/app-route-detection.js +4 -17
  235. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
  236. package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
  237. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -15
  238. package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
  239. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +9 -5
  240. package/dist/shims/internal/pages-data-fetch-dedup.js +73 -14
  241. package/dist/shims/internal/pages-data-target.d.ts +10 -9
  242. package/dist/shims/internal/pages-data-target.js +91 -12
  243. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  244. package/dist/shims/internal/pages-router-components.js +13 -0
  245. package/dist/shims/layout-segment-context.d.ts +4 -1
  246. package/dist/shims/layout-segment-context.js +18 -4
  247. package/dist/shims/link.js +70 -37
  248. package/dist/shims/metadata.d.ts +3 -2
  249. package/dist/shims/metadata.js +8 -4
  250. package/dist/shims/navigation.d.ts +5 -1
  251. package/dist/shims/navigation.js +45 -10
  252. package/dist/shims/navigation.react-server.js +1 -1
  253. package/dist/shims/root-params.d.ts +15 -1
  254. package/dist/shims/root-params.js +21 -1
  255. package/dist/shims/router.d.ts +5 -7
  256. package/dist/shims/router.js +374 -94
  257. package/dist/shims/server.js +3 -2
  258. package/dist/shims/slot.js +3 -6
  259. package/dist/typegen.js +6 -5
  260. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  261. package/dist/utils/client-runtime-metadata.js +31 -22
  262. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  263. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  264. package/dist/utils/domain-locale.d.ts +6 -3
  265. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  266. package/dist/{server → utils}/middleware-request-headers.js +2 -2
  267. package/dist/utils/path.d.ts +2 -1
  268. package/dist/utils/path.js +1 -1
  269. package/dist/utils/project.d.ts +52 -2
  270. package/dist/utils/project.js +230 -5
  271. package/dist/utils/protocol-headers.d.ts +17 -0
  272. package/dist/utils/protocol-headers.js +17 -0
  273. package/dist/utils/react-version.d.ts +4 -0
  274. package/dist/utils/react-version.js +44 -0
  275. package/package.json +53 -4
  276. package/dist/deploy.d.ts +0 -190
  277. package/dist/deploy.js +0 -1033
  278. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  279. /package/dist/{cloudflare → packages/cloudflare}/src/utils/cache-control-metadata.js +0 -0
@@ -1,55 +1,17 @@
1
1
  import { collectBindingNames, forEachAstChild, hasRange, isAstRecord, isIdentifierNamed, nodeArray } from "./ast-utils.js";
2
+ import { collectDirectScopeBindings, collectLoopScopeBindings, collectSwitchScopeBindings, collectVarScopeBindings, createAstScope, hasAstBinding, isFunctionNode } from "./ast-scope.js";
3
+ import path from "node:path";
2
4
  import { parseAst } from "vite";
3
5
  import MagicString from "magic-string";
4
6
  //#region src/plugins/typeof-window.ts
5
- function createScope(parent) {
6
- return {
7
- parent,
8
- bindings: /* @__PURE__ */ new Set()
9
- };
10
- }
11
- function hasBinding(scope, name) {
12
- for (let current = scope; current; current = current.parent) if (current.bindings.has(name)) return true;
13
- return false;
14
- }
15
- function collectScopeBindings(node, scope) {
16
- forEachAstChild(node, (child) => {
17
- if (child.type === "ExportNamedDeclaration" || child.type === "ExportDefaultDeclaration") {
18
- if (isAstRecord(child.declaration)) collectScopeBindings(child, scope);
19
- return;
20
- }
21
- if (child.type === "FunctionDeclaration" || child.type === "ClassDeclaration") {
22
- collectBindingNames(child.id, scope.bindings);
23
- return;
24
- }
25
- if (child.type === "VariableDeclaration") {
26
- for (const declaration of nodeArray(child.declarations)) if (isAstRecord(declaration)) collectBindingNames(declaration.id, scope.bindings);
27
- return;
28
- }
29
- if (child.type === "ImportDeclaration") {
30
- for (const specifier of nodeArray(child.specifiers)) if (isAstRecord(specifier)) collectBindingNames(specifier.local, scope.bindings);
31
- }
32
- });
33
- }
34
- function collectVarBindings(node, scope, isRoot = true) {
35
- if (!isRoot && (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression")) return;
36
- if (node.type === "VariableDeclaration" && node.kind === "var") {
37
- for (const declaration of nodeArray(node.declarations)) if (isAstRecord(declaration)) collectBindingNames(declaration.id, scope.bindings);
38
- }
39
- forEachAstChild(node, (child) => collectVarBindings(child, scope, false));
40
- }
41
- function isFunctionNode(node) {
42
- return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
43
- }
44
7
  function createChildScope(node, parent) {
45
- if (node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "StaticBlock" && node.type !== "SwitchStatement" && node.type !== "CatchClause" && node.type !== "ForStatement" && node.type !== "ForInStatement" && node.type !== "ForOfStatement" && node.type !== "ClassDeclaration" && node.type !== "ClassExpression") return null;
46
- const scope = createScope(parent);
8
+ if (node.type !== "Program" && node.type !== "BlockStatement" && node.type !== "StaticBlock" && node.type !== "TSModuleBlock" && node.type !== "CatchClause" && node.type !== "ForStatement" && node.type !== "ForInStatement" && node.type !== "ForOfStatement" && node.type !== "ClassDeclaration" && node.type !== "ClassExpression") return null;
9
+ const scope = createAstScope(parent);
47
10
  if (node.type === "ClassDeclaration" || node.type === "ClassExpression") collectBindingNames(node.id, scope.bindings);
48
11
  else if (node.type === "CatchClause") collectBindingNames(node.param, scope.bindings);
49
- collectScopeBindings(node, scope);
50
- if (node.type === "SwitchStatement") {
51
- for (const switchCase of nodeArray(node.cases)) if (isAstRecord(switchCase)) collectScopeBindings(switchCase, scope);
52
- }
12
+ collectDirectScopeBindings(node, scope);
13
+ if (node.type === "StaticBlock" || node.type === "TSModuleBlock") collectVarScopeBindings(node, scope);
14
+ if (node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement") collectLoopScopeBindings(node, scope);
53
15
  return scope;
54
16
  }
55
17
  function getTypeofWindowReplacement(environment) {
@@ -70,42 +32,52 @@ function evaluateTypeofWindowComparison(node, replacement, scope) {
70
32
  ].includes(String(node.operator))) return null;
71
33
  const left = isAstRecord(node.left) ? node.left : null;
72
34
  const right = isAstRecord(node.right) ? node.right : null;
73
- const leftIsTypeofWindow = left?.type === "UnaryExpression" && left.operator === "typeof" && isIdentifierNamed(left.argument, "window") && !hasBinding(scope, "window");
74
- const rightIsTypeofWindow = right?.type === "UnaryExpression" && right.operator === "typeof" && isIdentifierNamed(right.argument, "window") && !hasBinding(scope, "window");
35
+ const leftIsTypeofWindow = left?.type === "UnaryExpression" && left.operator === "typeof" && isIdentifierNamed(left.argument, "window") && !hasAstBinding(scope, "window");
36
+ const rightIsTypeofWindow = right?.type === "UnaryExpression" && right.operator === "typeof" && isIdentifierNamed(right.argument, "window") && !hasAstBinding(scope, "window");
75
37
  const comparedValue = leftIsTypeofWindow ? stringLiteralValue(right) : rightIsTypeofWindow ? stringLiteralValue(left) : null;
76
38
  if (comparedValue === null) return null;
77
39
  const equal = replacement === comparedValue;
78
40
  return node.operator === "==" || node.operator === "===" ? equal : !equal;
79
41
  }
80
- function replaceTypeofWindow(code, replacement) {
42
+ function replaceTypeofWindow(code, replacement, id = "file.js") {
81
43
  if (!/typeof\s+window/.test(code)) return null;
44
+ const extension = path.extname(id.split("?", 1)[0]);
45
+ const lang = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : extension === ".tsx" ? "tsx" : extension === ".jsx" ? "jsx" : "js";
82
46
  let ast;
83
47
  try {
84
- ast = parseAst(code);
48
+ ast = parseAst(code, { lang });
85
49
  } catch {
86
50
  return null;
87
51
  }
88
52
  const output = new MagicString(code);
89
53
  let changed = false;
90
54
  if (!isAstRecord(ast)) return null;
91
- const rootScope = createScope(null);
92
- collectScopeBindings(ast, rootScope);
93
- collectVarBindings(ast, rootScope);
55
+ const rootScope = createAstScope(null);
56
+ collectDirectScopeBindings(ast, rootScope);
57
+ collectVarScopeBindings(ast, rootScope);
94
58
  function visit(node, parentScope) {
95
59
  if (isFunctionNode(node)) {
96
- const parameterScope = createScope(parentScope);
60
+ const parameterScope = createAstScope(parentScope);
97
61
  collectBindingNames(node.id, parameterScope.bindings);
98
62
  for (const parameter of nodeArray(node.params)) {
99
63
  collectBindingNames(parameter, parameterScope.bindings);
100
64
  if (isAstRecord(parameter)) visit(parameter, parameterScope);
101
65
  }
102
66
  if (isAstRecord(node.body)) if (node.body.type === "BlockStatement") {
103
- const bodyScope = createScope(parameterScope);
104
- collectVarBindings(node.body, bodyScope);
67
+ const bodyScope = createAstScope(parameterScope);
68
+ collectDirectScopeBindings(node.body, bodyScope);
69
+ collectVarScopeBindings(node.body, bodyScope);
105
70
  visit(node.body, bodyScope);
106
71
  } else visit(node.body, parameterScope);
107
72
  return;
108
73
  }
74
+ if (node.type === "SwitchStatement") {
75
+ if (isAstRecord(node.discriminant)) visit(node.discriminant, parentScope);
76
+ const switchScope = createAstScope(parentScope);
77
+ collectSwitchScopeBindings(node, switchScope);
78
+ for (const switchCase of nodeArray(node.cases)) if (isAstRecord(switchCase)) visit(switchCase, switchScope);
79
+ return;
80
+ }
109
81
  const scope = createChildScope(node, parentScope) ?? parentScope;
110
82
  if (node.type === "IfStatement" && hasRange(node)) {
111
83
  const result = evaluateTypeofWindowComparison(node.test, replacement, scope);
@@ -132,7 +104,7 @@ function replaceTypeofWindow(code, replacement) {
132
104
  return;
133
105
  }
134
106
  }
135
- if (node.type === "UnaryExpression" && node.operator === "typeof" && isIdentifierNamed(node.argument, "window") && !hasBinding(scope, "window") && hasRange(node)) {
107
+ if (node.type === "UnaryExpression" && node.operator === "typeof" && isIdentifierNamed(node.argument, "window") && !hasAstBinding(scope, "window") && hasRange(node)) {
136
108
  output.overwrite(node.start, node.end, JSON.stringify(replacement));
137
109
  changed = true;
138
110
  return;
@@ -23,7 +23,9 @@ type InterceptingRoute = {
23
23
  sourceMatchPattern: string; /** Absolute path to the intercepting page component */
24
24
  pagePath: string; /** Filesystem segments from app/ root to the intercepting page directory. */
25
25
  sourcePageSegments?: string[]; /** Absolute layout paths inside the intercepting route tree, outermost to innermost */
26
- layoutPaths: string[]; /** Parameter names for dynamic segments */
26
+ layoutPaths: string[]; /** Normalized branch segments accumulated at each intercept layout. */
27
+ layoutSegments?: string[][]; /** Full normalized interception branch segments through the page. */
28
+ branchSegments?: string[]; /** Parameter names for dynamic segments */
27
29
  params: string[];
28
30
  /**
29
31
  * Synthetic page-carrier slot id for sibling (slot-less) interception.
@@ -40,7 +42,9 @@ type ParallelSlot = {
40
42
  hasPage: boolean; /** Absolute path to the slot's page component */
41
43
  pagePath: string | null; /** Absolute path to the slot's default.tsx fallback */
42
44
  defaultPath: string | null; /** Absolute path to the slot's layout component (wraps slot content) */
43
- layoutPath: string | null; /** Absolute path to the slot's loading component */
45
+ layoutPath: string | null; /** Nested active-branch layouts whose exports contribute route config. */
46
+ configLayoutPaths?: string[]; /** Tree positions of configLayoutPaths relative to the slot root. */
47
+ configLayoutTreePositions?: number[]; /** Absolute path to the slot's loading component */
44
48
  loadingPath: string | null; /** Absolute path to the slot's error component */
45
49
  errorPath: string | null; /** Intercepting routes within this slot */
46
50
  interceptingRoutes: InterceptingRoute[];
@@ -79,7 +83,12 @@ type AppRoute = {
79
83
  routePath: string | null; /** Ordered list of layout files from root to leaf */
80
84
  layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
81
85
  templates: string[]; /** Parallel route slots (from @slot directories at the route's directory level) */
82
- parallelSlots: ParallelSlot[];
86
+ parallelSlots: ParallelSlot[]; /** Stable implicit children-slot identity for parallel-slot sub-route families. */
87
+ childrenSlot?: {
88
+ id: string;
89
+ ownerTreePath: string;
90
+ state: "active" | "default" | "unmatched";
91
+ };
83
92
  /**
84
93
  * Interception markers not wrapped in an `@slot` directory.
85
94
  * On soft-nav, the intercepting page replaces the entire page response.
@@ -118,7 +127,16 @@ type AppRoute = {
118
127
  * Includes route groups and dynamic segments (as template strings like "[id]").
119
128
  * Used at render time to compute the child segments for useSelectedLayoutSegments().
120
129
  */
121
- routeSegments: string[]; /** Tree position (directory depth from app/ root) for each template. */
130
+ routeSegments: string[];
131
+ /**
132
+ * Active filesystem segments for the default `children` slot.
133
+ *
134
+ * Synthetic routes materialized only from named parallel-slot pages still
135
+ * use the full `routeSegments` for URL matching, params, and route identity,
136
+ * but their children slot renders the parent's default.tsx. In that case the
137
+ * active children segments remain at the parent route.
138
+ */
139
+ childrenRouteSegments?: string[]; /** Tree position (directory depth from app/ root) for each template. */
122
140
  templateTreePositions?: number[];
123
141
  /**
124
142
  * Tree position (directory depth from app/ root) for each layout.
@@ -272,6 +290,13 @@ type RouteManifest = {
272
290
  graphVersion: GraphVersion;
273
291
  segmentGraph: StaticSegmentGraph;
274
292
  };
293
+ /**
294
+ * Build the App Router route graph by scanning `appDir`.
295
+ *
296
+ * `appDir` must be forward-slash. Every path in the graph is derived from it
297
+ * with `path.posix.*` and `findFile`, so a native appDir would produce mixed
298
+ * separators on Windows. Production callers normalize it at their entry.
299
+ */
275
300
  declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher): Promise<{
276
301
  routes: AppRouteGraphRoute[];
277
302
  routeManifest: RouteManifest;
@@ -1,5 +1,5 @@
1
- import { normalizePathSeparators } from "../utils/path.js";
2
1
  import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
2
+ import { normalizePathSeparators } from "../utils/path.js";
3
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
4
4
  import { validateRoutePatterns } from "./route-validation.js";
5
5
  import { compareStrings } from "../utils/compare.js";
@@ -83,6 +83,19 @@ function createStaticSegmentGraph(routes) {
83
83
  templateIds: [...route.ids.templates],
84
84
  slotIds: route.parallelSlots.map((slot) => slot.id).sort(compareStableStrings)
85
85
  });
86
+ if (route.childrenSlot) {
87
+ const ownerLayoutId = findRouteManifestOwnerLayoutIdByTreePath(route, route.childrenSlot.ownerTreePath);
88
+ routeEntries.get(route.ids.route).slotIds = [...routeEntries.get(route.ids.route).slotIds, route.childrenSlot.id].sort(compareStableStrings);
89
+ slotBindings.set(`${route.ids.route}::${route.childrenSlot.id}`, {
90
+ id: `${route.ids.route}::${route.childrenSlot.id}`,
91
+ routeId: route.ids.route,
92
+ slotId: route.childrenSlot.id,
93
+ ownerLayoutId,
94
+ state: route.childrenSlot.state,
95
+ defaultId: null,
96
+ routeSegments: null
97
+ });
98
+ }
86
99
  if (route.ids.page) pages.set(route.ids.page, {
87
100
  id: route.ids.page,
88
101
  routeId: route.ids.route,
@@ -225,6 +238,10 @@ function findRouteManifestOwnerLayoutId(route, treePosition) {
225
238
  const layoutIndex = route.layoutTreePositions.indexOf(treePosition);
226
239
  return route.ids.layouts[layoutIndex] ?? null;
227
240
  }
241
+ function findRouteManifestOwnerLayoutIdByTreePath(route, treePath) {
242
+ const layoutIndex = route.layoutTreePositions.findIndex((treePosition) => createAppRouteGraphTreePath(route.routeSegments, treePosition) === treePath);
243
+ return route.ids.layouts[layoutIndex] ?? null;
244
+ }
228
245
  function findSlotOwnerLayoutId(route, slot) {
229
246
  if (slot.layoutIndex < 0) return null;
230
247
  return route.ids.layouts[slot.layoutIndex] ?? null;
@@ -344,6 +361,13 @@ function createRouteManifestGraphVersion(segmentGraph) {
344
361
  };
345
362
  return `graph:${createHash("sha256").update(JSON.stringify(stableShape)).digest("hex")}`;
346
363
  }
364
+ /**
365
+ * Build the App Router route graph by scanning `appDir`.
366
+ *
367
+ * `appDir` must be forward-slash. Every path in the graph is derived from it
368
+ * with `path.posix.*` and `findFile`, so a native appDir would produce mixed
369
+ * separators on Windows. Production callers normalize it at their entry.
370
+ */
347
371
  async function buildAppRouteGraph(appDir, matcher) {
348
372
  const routes = [];
349
373
  const scanMatcher = { ...matcher };
@@ -360,12 +384,16 @@ async function buildAppRouteGraph(appDir, matcher) {
360
384
  const routePatterns = new Set(routes.map((route) => route.pattern));
361
385
  const ghostParentRoutes = [];
362
386
  for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
363
- const dir = path.dirname(file);
364
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
387
+ const dir = path.posix.dirname(file);
388
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
365
389
  if (!hasParallelSlotDirectory(routeDir)) continue;
366
- if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
367
- const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
390
+ if (discoverParallelSlots(routeDir, appDir, scanMatcher, true).length === 0) continue;
391
+ const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null, true);
368
392
  if (!route) continue;
393
+ if (routes.some((candidate) => candidate.patternParts.length === route.patternParts.length + 1 && candidate.patternParts.at(-1)?.endsWith("*") && patternsStructurallyEquivalent(candidate.patternParts.slice(0, -1), route.patternParts))) {
394
+ ghostParentRoutes.push(route);
395
+ continue;
396
+ }
369
397
  if (routePatterns.has(route.pattern)) {
370
398
  ghostParentRoutes.push(route);
371
399
  continue;
@@ -434,11 +462,16 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
434
462
  const applySlotSubPages = (route, slotPages, rawSegments) => {
435
463
  route.parallelSlots = route.parallelSlots.map((slot) => {
436
464
  const subPage = slotPages.get(slot.key);
437
- if (subPage !== void 0) return {
438
- ...slot,
439
- pagePath: subPage,
440
- routeSegments: rawSegments
441
- };
465
+ if (subPage !== void 0) {
466
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage, matcher);
467
+ return {
468
+ ...slot,
469
+ pagePath: subPage,
470
+ configLayoutPaths,
471
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
472
+ routeSegments: rawSegments
473
+ };
474
+ }
442
475
  return slot;
443
476
  });
444
477
  };
@@ -477,8 +510,29 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
477
510
  }
478
511
  }
479
512
  if (subPathMap.size === 0) continue;
513
+ const childrenOwnerTreePath = parentRoute.parallelSlots.find((slot) => path.dirname(slot.ownerDir) === parentPageDir)?.ownerTreePath;
514
+ if (!childrenOwnerTreePath) throw new Error(`[vinext] App route graph invariant violated: missing children slot owner for ${parentRoute.pattern}`);
480
515
  const childrenDefault = findFile(parentPageDir, "default", matcher);
481
516
  if (parentRoute.pagePath && !childrenDefault) continue;
517
+ const childrenSlotId = createAppRouteGraphSlotId("children", childrenOwnerTreePath);
518
+ if (parentRoute.pagePath) parentRoute.childrenSlot = {
519
+ id: childrenSlotId,
520
+ ownerTreePath: childrenOwnerTreePath,
521
+ state: "active"
522
+ };
523
+ for (const route of routes) {
524
+ if (!route.pagePath || route === parentRoute) continue;
525
+ const relativePageDir = path.relative(parentPageDir, path.dirname(route.pagePath));
526
+ if (relativePageDir === "" || relativePageDir === ".." || relativePageDir.startsWith(`..${path.sep}`) || path.isAbsolute(relativePageDir)) continue;
527
+ const existingOwnerDepth = route.childrenSlot?.ownerTreePath.split("/").filter(Boolean).length;
528
+ const candidateOwnerDepth = childrenOwnerTreePath.split("/").filter(Boolean).length;
529
+ if (existingOwnerDepth !== void 0 && existingOwnerDepth >= candidateOwnerDepth) continue;
530
+ route.childrenSlot = {
531
+ id: childrenSlotId,
532
+ ownerTreePath: childrenOwnerTreePath,
533
+ state: "active"
534
+ };
535
+ }
482
536
  const childrenCatchAll = childrenDefault ? null : findCatchAllPage(parentPageDir, matcher);
483
537
  const childrenFallback = childrenDefault ?? childrenCatchAll;
484
538
  for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
@@ -495,9 +549,12 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
495
549
  if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
496
550
  const subSlots = parentRoute.parallelSlots.map((slot) => {
497
551
  const subPage = slotPages.get(slot.key);
552
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage ?? null, matcher);
498
553
  return {
499
554
  ...slot,
500
555
  pagePath: subPage || null,
556
+ configLayoutPaths,
557
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
501
558
  routeSegments: subPage ? rawSegments : null
502
559
  };
503
560
  });
@@ -517,6 +574,11 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
517
574
  layouts: parentRoute.layouts,
518
575
  templates: parentRoute.templates,
519
576
  parallelSlots: subSlots,
577
+ childrenSlot: {
578
+ id: childrenSlotId,
579
+ ownerTreePath: childrenOwnerTreePath,
580
+ state: childrenDefault ? "default" : childrenCatchAll ? "active" : "unmatched"
581
+ },
520
582
  loadingPath: parentRoute.loadingPath,
521
583
  errorPath: parentRoute.errorPath,
522
584
  layoutErrorPaths: parentRoute.layoutErrorPaths,
@@ -527,6 +589,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
527
589
  unauthorizedPath: parentRoute.unauthorizedPath,
528
590
  unauthorizedPaths: parentRoute.unauthorizedPaths,
529
591
  routeSegments: [...parentRoute.routeSegments, ...rawSegments],
592
+ childrenRouteSegments: childrenDefault ? parentRoute.routeSegments : void 0,
530
593
  templateTreePositions: parentRoute.templateTreePositions,
531
594
  layoutTreePositions: parentRoute.layoutTreePositions,
532
595
  isDynamic: parentRoute.isDynamic || subIsDynamic,
@@ -541,6 +604,26 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
541
604
  }
542
605
  return syntheticRoutes;
543
606
  }
607
+ const dirEntriesCache = /* @__PURE__ */ new WeakMap();
608
+ function readDirEntriesCached(dir, matcher) {
609
+ let perMatcher = dirEntriesCache.get(matcher);
610
+ if (!perMatcher) {
611
+ perMatcher = /* @__PURE__ */ new Map();
612
+ dirEntriesCache.set(matcher, perMatcher);
613
+ }
614
+ let entries = perMatcher.get(dir);
615
+ if (entries === void 0) {
616
+ try {
617
+ entries = fs.readdirSync(dir, { withFileTypes: true });
618
+ } catch (error) {
619
+ const code = error.code;
620
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error;
621
+ entries = [];
622
+ }
623
+ perMatcher.set(dir, entries);
624
+ }
625
+ return entries;
626
+ }
544
627
  const findSlotSubPagesCache = /* @__PURE__ */ new WeakMap();
545
628
  function findSlotSubPages(slotDir, matcher) {
546
629
  let perMatcher = findSlotSubPagesCache.get(matcher);
@@ -574,6 +657,23 @@ function findSlotSubPages(slotDir, matcher) {
574
657
  perMatcher.set(slotDir, results);
575
658
  return results;
576
659
  }
660
+ function findSlotConfigLayoutPaths(slotDir, pagePath, matcher) {
661
+ if (!pagePath) return [];
662
+ const layouts = [];
663
+ let dir = path.dirname(pagePath);
664
+ while (dir !== slotDir && dir.startsWith(`${slotDir}${path.sep}`)) {
665
+ const layoutPath = findFile(dir, "layout", matcher);
666
+ if (layoutPath) layouts.unshift(layoutPath);
667
+ dir = path.dirname(dir);
668
+ }
669
+ return layouts;
670
+ }
671
+ function findSlotConfigLayoutTreePositions(slotDir, layoutPaths) {
672
+ return layoutPaths.map((layoutPath) => {
673
+ const relativeDir = path.relative(slotDir, path.dirname(layoutPath));
674
+ return relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0;
675
+ });
676
+ }
577
677
  /**
578
678
  * Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
579
679
  * `[[...slug]]` directory that contains a `page` file. Returns the absolute
@@ -604,17 +704,29 @@ function findCatchAllPage(dir, matcher) {
604
704
  }
605
705
  /**
606
706
  * Convert a file path relative to app/ into an AppRoute.
707
+ *
708
+ * `file` and `appDir` must be forward-slash. `file` comes from
709
+ * `scanWithExtensions` (already forward-slash) and is joined onto `appDir` with
710
+ * `path.posix.join` to form the page/route path, so a native input would
711
+ * produce a mixed separator on Windows.
607
712
  */
608
713
  function fileToAppRoute(file, appDir, type, matcher) {
609
- let dir = path.dirname(file);
610
- if (type === "page" && dir !== "." && path.basename(dir) === "@children") {
611
- const parent = path.dirname(dir);
714
+ let dir = path.posix.dirname(file);
715
+ if (type === "page" && dir !== "." && path.posix.basename(dir) === "@children") {
716
+ const parent = path.posix.dirname(dir);
612
717
  dir = parent === "" || parent === "." ? "." : parent;
613
718
  }
614
- return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
719
+ return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.posix.join(appDir, file) : null, type === "route" ? path.posix.join(appDir, file) : null);
615
720
  }
616
- function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
617
- const segments = dir === "." ? [] : dir.split(path.sep);
721
+ /**
722
+ * `dir`, `appDir`, `pagePath`, and `routePath` must all be forward-slash. `dir`
723
+ * is split on `path.posix.sep` and joined onto `appDir` with `path.posix.join`.
724
+ * `appDir` is threaded to the layout/slot/boundary discovery below, which builds
725
+ * paths the same way. `pagePath` and `routePath` are stored on the route node as
726
+ * canonical ids that get compared and re-joined downstream.
727
+ */
728
+ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeNestedOnlySlots = false) {
729
+ const segments = dir === "." ? [] : dir.split("/");
618
730
  const params = [];
619
731
  let isDynamic = false;
620
732
  const convertedRoute = convertSegmentsToRouteParts(segments);
@@ -631,7 +743,8 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
631
743
  const errorEntries = discoverSegmentErrors(segments, appDir, matcher);
632
744
  const errorPaths = errorEntries.map((entry) => entry.path);
633
745
  const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
634
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
746
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
747
+ const effectivePagePath = pagePath ?? (routePath ? null : findFile(routeDir, "default", matcher));
635
748
  const loadingPath = findFile(routeDir, "loading", matcher);
636
749
  const errorPath = findFile(routeDir, "error", matcher);
637
750
  const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
@@ -640,11 +753,11 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
640
753
  const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
641
754
  const forbiddenPaths = discoverBoundaryFilePerLayout(layouts, "forbidden", matcher);
642
755
  const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
643
- const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
756
+ const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots);
644
757
  return {
645
758
  ids: createAppRouteSemanticIds({
646
759
  pattern: pattern === "/" ? "/" : pattern,
647
- pagePath,
760
+ pagePath: effectivePagePath,
648
761
  routePath,
649
762
  routeSegments: segments,
650
763
  layoutTreePositions,
@@ -652,7 +765,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
652
765
  slots: parallelSlots
653
766
  }),
654
767
  pattern: pattern === "/" ? "/" : pattern,
655
- pagePath,
768
+ pagePath: effectivePagePath,
656
769
  routePath,
657
770
  layouts,
658
771
  templates,
@@ -863,8 +976,12 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
863
976
  * Walk from appDir through each segment to the route's directory. At each level
864
977
  * that has @slot dirs, collect them. Slots at the route's own directory level
865
978
  * use page.tsx; slots at ancestor levels use default.tsx only.
979
+ *
980
+ * `appDir` and `routeDir` must be forward-slash — `currentDir` descends from
981
+ * `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
982
+ * below only matches when both share the canonical separator.
866
983
  */
867
- function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
984
+ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots = false) {
868
985
  const slotMap = /* @__PURE__ */ new Map();
869
986
  let currentDir = appDir;
870
987
  const dirsToCheck = [];
@@ -875,7 +992,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
875
992
  segmentIndex: 0
876
993
  });
877
994
  for (let i = 0; i < segments.length; i++) {
878
- currentDir = path.join(currentDir, segments[i]);
995
+ currentDir = path.posix.join(currentDir, segments[i]);
879
996
  if (findFile(currentDir, "layout", matcher)) layoutIdx++;
880
997
  dirsToCheck.push({
881
998
  dir: currentDir,
@@ -887,9 +1004,9 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
887
1004
  for (const { dir, layoutIdx: lvlLayoutIdx, segmentIndex } of dirsToCheck) {
888
1005
  if (lvlLayoutIdx < 0 && routeHasLayout) continue;
889
1006
  const slotLayoutIdx = Math.max(lvlLayoutIdx, 0);
890
- const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
891
1007
  const segmentsBelow = segments.slice(segmentIndex);
892
1008
  const isActiveUrlLevel = dir === routeDir || segmentsBelow.every(isInvisibleSegment);
1009
+ const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots && isActiveUrlLevel);
893
1010
  for (const slot of slotsAtLevel) if (isActiveUrlLevel) {
894
1011
  slot.layoutIndex = slotLayoutIdx;
895
1012
  slotMap.set(slot.key, slot);
@@ -902,9 +1019,12 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
902
1019
  slotPatternParts = [...ownerUrl?.urlSegments ?? [], ...mirror.slotUrlSegments];
903
1020
  slotParamNames = [...ownerUrl?.params ?? [], ...mirror.slotParamNames];
904
1021
  }
1022
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, mirror?.pagePath ?? null, matcher);
905
1023
  const inheritedSlot = {
906
1024
  ...slot,
907
1025
  pagePath: mirror?.pagePath ?? null,
1026
+ configLayoutPaths,
1027
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
908
1028
  layoutIndex: slotLayoutIdx,
909
1029
  routeSegments: mirror?.segments ?? null,
910
1030
  slotPatternParts,
@@ -1035,6 +1155,9 @@ function patternsStructurallyEquivalent(a, b) {
1035
1155
  *
1036
1156
  * Returns the absolute page path, or null if no root-level page is found.
1037
1157
  *
1158
+ * `slotDir` must be forward-slash: the `path.posix.join` descent stays a
1159
+ * canonical id only when the base already is.
1160
+ *
1038
1161
  * Only descends into route-group directories (those whose name starts with `(`
1039
1162
  * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1040
1163
  * are not transparent and are therefore not searched.
@@ -1042,42 +1165,44 @@ function patternsStructurallyEquivalent(a, b) {
1042
1165
  function findSlotRootPage(slotDir, matcher) {
1043
1166
  const directPage = findFile(slotDir, "page", matcher);
1044
1167
  if (directPage) return directPage;
1045
- let entries;
1046
- try {
1047
- entries = fs.readdirSync(slotDir, { withFileTypes: true });
1048
- } catch {
1049
- return null;
1050
- }
1168
+ const entries = readDirEntriesCached(slotDir, matcher);
1051
1169
  for (const entry of entries) {
1052
1170
  if (!entry.isDirectory()) continue;
1053
1171
  if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1054
- const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1172
+ const found = findSlotRootPage(path.posix.join(slotDir, entry.name), matcher);
1055
1173
  if (found) return found;
1056
1174
  }
1057
1175
  return null;
1058
1176
  }
1059
1177
  /**
1060
1178
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
1061
- * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
1179
+ * Returns a ParallelSlot for each @-prefixed subdirectory that has a page,
1180
+ * default component, intercepting route, or nested page-backed sub-route.
1181
+ *
1182
+ * `dir` and `appDir` must be forward-slash. The slot directory is built from
1183
+ * `dir` with `path.posix.join`, and the owner segments and slot key come from
1184
+ * `path.posix.relative(appDir, …)`, which only yields a forward-slash relative
1185
+ * path when both operands already are.
1062
1186
  */
1063
- function discoverParallelSlots(dir, appDir, matcher) {
1064
- if (!fs.existsSync(dir)) return [];
1065
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1187
+ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
1188
+ const entries = readDirEntriesCached(dir, matcher);
1066
1189
  const slots = [];
1067
1190
  for (const entry of entries) {
1068
1191
  if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
1069
1192
  if (entry.name === "@children") continue;
1070
1193
  const slotName = entry.name.slice(1);
1071
- const slotDir = path.join(dir, entry.name);
1194
+ const slotDir = path.posix.join(dir, entry.name);
1072
1195
  const pagePath = findSlotRootPage(slotDir, matcher);
1073
1196
  const defaultPath = findFile(slotDir, "default", matcher);
1074
1197
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1075
- if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
1076
- const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
1198
+ const hasNestedPages = includeNestedOnlySlots && findSlotSubPages(slotDir, matcher).length > 0;
1199
+ if (!pagePath && !defaultPath && interceptingRoutes.length === 0 && !hasNestedPages) continue;
1200
+ const ownerSegments = path.posix.relative(appDir, dir).split("/").filter((segment) => segment.length > 0);
1077
1201
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1202
+ const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1078
1203
  slots.push({
1079
1204
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1080
- key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
1205
+ key: `${slotName}@${path.posix.relative(appDir, slotDir)}`,
1081
1206
  name: slotName,
1082
1207
  ownerDir: slotDir,
1083
1208
  ownerTreePath,
@@ -1085,6 +1210,8 @@ function discoverParallelSlots(dir, appDir, matcher) {
1085
1210
  pagePath,
1086
1211
  defaultPath,
1087
1212
  layoutPath: findFile(slotDir, "layout", matcher),
1213
+ configLayoutPaths,
1214
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slotDir, configLayoutPaths),
1088
1215
  loadingPath: findFile(slotDir, "loading", matcher),
1089
1216
  errorPath: findFile(slotDir, "error", matcher),
1090
1217
  interceptingRoutes,
@@ -1134,6 +1261,10 @@ function isInterceptionMarkerDir(name) {
1134
1261
  * Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.
1135
1262
  * They intercept navigation to another route and render within the slot instead.
1136
1263
  *
1264
+ * `slotDir`, `routeDir`, and `appDir` must be forward-slash. They are passed
1265
+ * down to `path.posix.join` and `path.posix.relative` when building the
1266
+ * intercept page paths and target patterns.
1267
+ *
1137
1268
  * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
1138
1269
  * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
1139
1270
  * @param appDir - The root app directory
@@ -1237,6 +1368,10 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1237
1368
  /**
1238
1369
  * Recursively scan a directory tree for page.tsx files that are inside
1239
1370
  * intercepting route directories.
1371
+ *
1372
+ * `currentDir`, `routeDir`, and `appDir` must be forward-slash. `currentDir`
1373
+ * descends with `path.posix.join` and all three reach the `path.posix.join` /
1374
+ * `path.posix.relative` calls that build the intercept page paths and patterns.
1240
1375
  */
1241
1376
  function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1242
1377
  if (!fs.existsSync(currentDir)) return;
@@ -1245,11 +1380,11 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
1245
1380
  if (!entry.isDirectory()) continue;
1246
1381
  if (entry.name.startsWith("_")) continue;
1247
1382
  const interceptMatch = matchInterceptConvention(entry.name);
1383
+ const interceptDir = path.posix.join(currentDir, entry.name);
1248
1384
  if (interceptMatch) {
1249
1385
  const restOfName = entry.name.slice(interceptMatch.prefix.length);
1250
- const interceptDir = path.join(currentDir, entry.name);
1251
1386
  collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, results, matcher);
1252
- } else scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results, matcher);
1387
+ } else scanForInterceptingPages(interceptDir, routeDir, appDir, results, matcher);
1253
1388
  }
1254
1389
  }
1255
1390
  /**
@@ -1272,8 +1407,12 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1272
1407
  if (targetPattern) {
1273
1408
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1274
1409
  results.push({
1410
+ branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
1275
1411
  convention,
1276
1412
  layoutPaths: [...layoutPaths],
1413
+ layoutSegments: layoutPaths.map((layoutPath) => {
1414
+ return [interceptSegment, ...path.relative(interceptRoot, path.dirname(layoutPath)).split(path.sep).filter(Boolean)];
1415
+ }),
1277
1416
  targetPattern: targetPattern.pattern,
1278
1417
  sourceMatchPattern,
1279
1418
  pagePath: page,
@@ -1407,6 +1546,9 @@ const findFileProbeCache = /* @__PURE__ */ new WeakMap();
1407
1546
  * registered per-scan cache; otherwise falls back to a direct probe (identical
1408
1547
  * result). The `null` "not found" outcome is cached too, so repeated misses on
1409
1548
  * shared ancestors cost a single set of `existsSync` calls per scan.
1549
+ *
1550
+ * `dir` must be forward-slash. The returned path comes from `findFileWithExts`,
1551
+ * so it is forward-slash too.
1410
1552
  */
1411
1553
  function findFile(dir, name, matcher) {
1412
1554
  const cache = findFileProbeCache.get(matcher);