@qzsy/vinext 0.1.12 → 0.1.81

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 (231) hide show
  1. package/README.md +19 -5
  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/pages-client-assets-module.d.ts +11 -0
  5. package/dist/build/pages-client-assets-module.js +27 -0
  6. package/dist/build/prerender.d.ts +2 -1
  7. package/dist/build/prerender.js +11 -4
  8. package/dist/build/report.d.ts +2 -1
  9. package/dist/build/report.js +2 -1
  10. package/dist/build/run-prerender.d.ts +7 -0
  11. package/dist/build/run-prerender.js +9 -0
  12. package/dist/build/standalone.js +2 -0
  13. package/dist/check.d.ts +18 -0
  14. package/dist/check.js +77 -19
  15. package/dist/cli-dev-config.d.ts +12 -0
  16. package/dist/cli-dev-config.js +23 -0
  17. package/dist/cli.js +64 -28
  18. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  19. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  20. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  21. package/dist/{server → client}/dev-error-overlay.js +2 -2
  22. package/dist/cloudflare/deploy-config.d.ts +51 -0
  23. package/dist/cloudflare/deploy-config.js +153 -0
  24. package/dist/cloudflare/index.d.ts +1 -1
  25. package/dist/cloudflare/index.js +1 -1
  26. package/dist/cloudflare/project.d.ts +41 -0
  27. package/dist/cloudflare/project.js +243 -0
  28. package/dist/cloudflare/tpr.js +1 -1
  29. package/dist/config/config-matchers.js +14 -10
  30. package/dist/config/next-config.d.ts +6 -3
  31. package/dist/config/next-config.js +47 -1
  32. package/dist/config/server-external-packages.d.ts +4 -0
  33. package/dist/config/server-external-packages.js +91 -0
  34. package/dist/deploy.d.ts +2 -122
  35. package/dist/deploy.js +20 -793
  36. package/dist/entries/app-rsc-entry.d.ts +2 -1
  37. package/dist/entries/app-rsc-entry.js +70 -12
  38. package/dist/entries/app-rsc-manifest.js +8 -0
  39. package/dist/entries/pages-client-entry.d.ts +1 -0
  40. package/dist/entries/pages-client-entry.js +2 -1
  41. package/dist/entries/pages-server-entry.js +6 -2
  42. package/dist/image/image-adapters-virtual.d.ts +59 -0
  43. package/dist/image/image-adapters-virtual.js +50 -0
  44. package/dist/index.d.ts +12 -0
  45. package/dist/index.js +160 -160
  46. package/dist/init-cloudflare.d.ts +43 -0
  47. package/dist/init-cloudflare.js +1000 -0
  48. package/dist/init-platform.d.ts +38 -0
  49. package/dist/init-platform.js +150 -0
  50. package/dist/init.d.ts +14 -37
  51. package/dist/init.js +205 -95
  52. package/dist/node_modules/.pnpm/am-i-vibing@0.5.0/node_modules/am-i-vibing/dist/detector-1yx2Hoe0.js +294 -0
  53. package/dist/node_modules/.pnpm/process-ancestry@0.1.0/node_modules/process-ancestry/dist/index.js +94 -0
  54. package/dist/{cloudflare → packages/cloudflare}/src/cache/cdn-adapter.runtime.js +1 -1
  55. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.d.ts +2 -2
  56. package/dist/{cloudflare → packages/cloudflare}/src/cache/kv-data-adapter.runtime.js +1 -1
  57. package/dist/plugins/ast-scope.d.ts +16 -0
  58. package/dist/plugins/ast-scope.js +62 -0
  59. package/dist/plugins/ast-utils.js +3 -0
  60. package/dist/plugins/css-module-imports.d.ts +14 -0
  61. package/dist/plugins/css-module-imports.js +59 -0
  62. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  63. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  64. package/dist/plugins/middleware-server-only.d.ts +8 -6
  65. package/dist/plugins/middleware-server-only.js +8 -7
  66. package/dist/plugins/optimize-imports.js +1 -1
  67. package/dist/plugins/typeof-window.d.ts +1 -1
  68. package/dist/plugins/typeof-window.js +28 -56
  69. package/dist/routing/app-route-graph.d.ts +13 -2
  70. package/dist/routing/app-route-graph.js +116 -32
  71. package/dist/routing/app-router.d.ts +5 -0
  72. package/dist/routing/app-router.js +5 -0
  73. package/dist/routing/file-matcher.d.ts +8 -0
  74. package/dist/routing/file-matcher.js +10 -1
  75. package/dist/routing/pages-router.js +2 -2
  76. package/dist/server/app-browser-action-result.d.ts +2 -1
  77. package/dist/server/app-browser-action-result.js +5 -1
  78. package/dist/server/app-browser-entry.js +17 -12
  79. package/dist/server/app-browser-history-controller.d.ts +2 -1
  80. package/dist/server/app-browser-history-controller.js +6 -2
  81. package/dist/server/app-browser-interception-context.d.ts +1 -0
  82. package/dist/server/app-browser-interception-context.js +4 -2
  83. package/dist/server/app-browser-navigation-controller.js +1 -0
  84. package/dist/server/app-browser-server-action-client.js +2 -3
  85. package/dist/server/app-browser-state.d.ts +1 -0
  86. package/dist/server/app-browser-state.js +3 -2
  87. package/dist/server/app-fallback-renderer.d.ts +3 -2
  88. package/dist/server/app-fallback-renderer.js +12 -7
  89. package/dist/server/app-middleware.d.ts +2 -3
  90. package/dist/server/app-middleware.js +3 -2
  91. package/dist/server/app-optimistic-routing.js +1 -1
  92. package/dist/server/app-page-boundary-render.d.ts +1 -0
  93. package/dist/server/app-page-boundary-render.js +12 -3
  94. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  95. package/dist/server/app-page-cache-finalizer.js +10 -3
  96. package/dist/server/app-page-cache-render.d.ts +1 -0
  97. package/dist/server/app-page-cache-render.js +8 -4
  98. package/dist/server/app-page-cache.d.ts +1 -0
  99. package/dist/server/app-page-cache.js +4 -1
  100. package/dist/server/app-page-dispatch.d.ts +11 -3
  101. package/dist/server/app-page-dispatch.js +55 -15
  102. package/dist/server/app-page-element-builder.d.ts +5 -1
  103. package/dist/server/app-page-element-builder.js +57 -20
  104. package/dist/server/app-page-head.d.ts +12 -0
  105. package/dist/server/app-page-head.js +42 -19
  106. package/dist/server/app-page-params.d.ts +2 -1
  107. package/dist/server/app-page-params.js +8 -1
  108. package/dist/server/app-page-probe.d.ts +1 -0
  109. package/dist/server/app-page-probe.js +6 -1
  110. package/dist/server/app-page-render-identity.d.ts +1 -0
  111. package/dist/server/app-page-render-identity.js +1 -1
  112. package/dist/server/app-page-render.d.ts +4 -1
  113. package/dist/server/app-page-render.js +8 -3
  114. package/dist/server/app-page-request.d.ts +22 -1
  115. package/dist/server/app-page-request.js +89 -13
  116. package/dist/server/app-page-route-wiring.d.ts +6 -1
  117. package/dist/server/app-page-route-wiring.js +31 -15
  118. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  119. package/dist/server/app-page-search-params-observation.js +11 -7
  120. package/dist/server/app-page-segment-state.js +2 -0
  121. package/dist/server/app-route-handler-dispatch.js +1 -0
  122. package/dist/server/app-route-handler-execution.js +7 -2
  123. package/dist/server/app-route-handler-response.js +1 -0
  124. package/dist/server/app-route-handler-runtime.js +1 -1
  125. package/dist/server/app-route-module-loader.d.ts +2 -0
  126. package/dist/server/app-route-module-loader.js +1 -0
  127. package/dist/server/app-router-entry.d.ts +12 -0
  128. package/dist/server/app-router-entry.js +22 -8
  129. package/dist/server/app-router-image-optimization.d.ts +37 -0
  130. package/dist/server/app-router-image-optimization.js +40 -0
  131. package/dist/server/app-rsc-errors.js +7 -1
  132. package/dist/server/app-rsc-handler.js +27 -14
  133. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  134. package/dist/server/app-rsc-route-matching.js +36 -3
  135. package/dist/server/app-segment-config.d.ts +12 -0
  136. package/dist/server/app-segment-config.js +91 -5
  137. package/dist/server/app-server-action-execution.d.ts +5 -0
  138. package/dist/server/app-server-action-execution.js +106 -33
  139. package/dist/server/app-ssr-entry.js +12 -1
  140. package/dist/server/app-static-generation.d.ts +1 -0
  141. package/dist/server/app-static-generation.js +1 -0
  142. package/dist/server/client-trace-metadata.js +26 -0
  143. package/dist/server/default-global-not-found-module.d.ts +14 -0
  144. package/dist/server/default-global-not-found-module.js +14 -0
  145. package/dist/server/dev-response-headers.d.ts +19 -0
  146. package/dist/server/dev-response-headers.js +78 -0
  147. package/dist/server/dev-server.js +8 -15
  148. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  149. package/dist/server/dev-stack-sourcemap.js +1 -1
  150. package/dist/server/headers.d.ts +7 -15
  151. package/dist/server/headers.js +6 -15
  152. package/dist/server/image-optimization.d.ts +51 -1
  153. package/dist/server/image-optimization.js +52 -2
  154. package/dist/server/isr-cache.d.ts +1 -1
  155. package/dist/server/isr-cache.js +2 -2
  156. package/dist/server/middleware-runtime.js +6 -1
  157. package/dist/server/navigation-planner.d.ts +1 -0
  158. package/dist/server/navigation-planner.js +14 -3
  159. package/dist/server/pages-asset-tags.d.ts +4 -6
  160. package/dist/server/pages-asset-tags.js +12 -12
  161. package/dist/server/pages-client-assets.d.ts +12 -0
  162. package/dist/server/pages-client-assets.js +10 -0
  163. package/dist/server/pages-page-data.d.ts +23 -1
  164. package/dist/server/pages-page-data.js +43 -24
  165. package/dist/server/pages-page-handler.d.ts +2 -1
  166. package/dist/server/pages-page-handler.js +10 -4
  167. package/dist/server/pages-request-pipeline.d.ts +2 -0
  168. package/dist/server/pages-request-pipeline.js +25 -1
  169. package/dist/server/prerender-manifest.d.ts +3 -1
  170. package/dist/server/prerender-route-params.js +1 -1
  171. package/dist/server/prod-server.d.ts +1 -1
  172. package/dist/server/prod-server.js +47 -25
  173. package/dist/server/request-log.d.ts +5 -14
  174. package/dist/server/request-log.js +7 -1
  175. package/dist/server/request-pipeline.js +1 -0
  176. package/dist/server/seed-cache.js +4 -4
  177. package/dist/server/server-action-logger.d.ts +39 -0
  178. package/dist/server/server-action-logger.js +104 -0
  179. package/dist/server/worker-utils.d.ts +2 -1
  180. package/dist/server/worker-utils.js +7 -1
  181. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  182. package/dist/shims/app-router-scroll-state.js +1 -0
  183. package/dist/shims/app-router-scroll.js +2 -1
  184. package/dist/shims/cache.js +19 -15
  185. package/dist/shims/cdn-cache.js +1 -1
  186. package/dist/shims/dynamic-preload-chunks.js +2 -1
  187. package/dist/shims/error-boundary.d.ts +19 -1
  188. package/dist/shims/error-boundary.js +11 -1
  189. package/dist/shims/form.d.ts +3 -1
  190. package/dist/shims/form.js +37 -43
  191. package/dist/shims/headers.d.ts +9 -1
  192. package/dist/shims/headers.js +31 -6
  193. package/dist/shims/image-optimization-url.d.ts +4 -0
  194. package/dist/shims/image-optimization-url.js +33 -1
  195. package/dist/shims/image.js +46 -13
  196. package/dist/shims/internal/app-route-detection.d.ts +2 -17
  197. package/dist/shims/internal/app-route-detection.js +4 -17
  198. package/dist/shims/internal/hybrid-client-route-owner-direct.d.ts +23 -0
  199. package/dist/shims/internal/hybrid-client-route-owner-direct.js +51 -0
  200. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -5
  201. package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
  202. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  203. package/dist/shims/internal/pages-router-components.js +13 -0
  204. package/dist/shims/link.js +23 -16
  205. package/dist/shims/metadata.d.ts +3 -2
  206. package/dist/shims/metadata.js +8 -4
  207. package/dist/shims/navigation.js +4 -2
  208. package/dist/shims/root-params.d.ts +15 -1
  209. package/dist/shims/root-params.js +21 -1
  210. package/dist/shims/router.d.ts +2 -5
  211. package/dist/shims/router.js +41 -22
  212. package/dist/shims/server.js +3 -2
  213. package/dist/typegen.js +6 -5
  214. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  215. package/dist/utils/client-runtime-metadata.js +31 -22
  216. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  217. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  218. package/dist/utils/domain-locale.d.ts +6 -3
  219. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  220. package/dist/{server → utils}/middleware-request-headers.js +2 -2
  221. package/dist/utils/path.d.ts +2 -1
  222. package/dist/utils/path.js +1 -1
  223. package/dist/utils/project.d.ts +9 -1
  224. package/dist/utils/project.js +21 -4
  225. package/dist/utils/protocol-headers.d.ts +17 -0
  226. package/dist/utils/protocol-headers.js +17 -0
  227. package/dist/utils/react-version.d.ts +4 -0
  228. package/dist/utils/react-version.js +44 -0
  229. package/package.json +29 -23
  230. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  231. /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[];
@@ -272,6 +276,13 @@ type RouteManifest = {
272
276
  graphVersion: GraphVersion;
273
277
  segmentGraph: StaticSegmentGraph;
274
278
  };
279
+ /**
280
+ * Build the App Router route graph by scanning `appDir`.
281
+ *
282
+ * `appDir` must be forward-slash. Every path in the graph is derived from it
283
+ * with `path.posix.*` and `findFile`, so a native appDir would produce mixed
284
+ * separators on Windows. Production callers normalize it at their entry.
285
+ */
275
286
  declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher): Promise<{
276
287
  routes: AppRouteGraphRoute[];
277
288
  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";
@@ -344,6 +344,13 @@ function createRouteManifestGraphVersion(segmentGraph) {
344
344
  };
345
345
  return `graph:${createHash("sha256").update(JSON.stringify(stableShape)).digest("hex")}`;
346
346
  }
347
+ /**
348
+ * Build the App Router route graph by scanning `appDir`.
349
+ *
350
+ * `appDir` must be forward-slash. Every path in the graph is derived from it
351
+ * with `path.posix.*` and `findFile`, so a native appDir would produce mixed
352
+ * separators on Windows. Production callers normalize it at their entry.
353
+ */
347
354
  async function buildAppRouteGraph(appDir, matcher) {
348
355
  const routes = [];
349
356
  const scanMatcher = { ...matcher };
@@ -360,12 +367,16 @@ async function buildAppRouteGraph(appDir, matcher) {
360
367
  const routePatterns = new Set(routes.map((route) => route.pattern));
361
368
  const ghostParentRoutes = [];
362
369
  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);
370
+ const dir = path.posix.dirname(file);
371
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
365
372
  if (!hasParallelSlotDirectory(routeDir)) continue;
366
- if (discoverParallelSlots(routeDir, appDir, scanMatcher).length === 0) continue;
367
- const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null);
373
+ if (discoverParallelSlots(routeDir, appDir, scanMatcher, true).length === 0) continue;
374
+ const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null, true);
368
375
  if (!route) continue;
376
+ if (routes.some((candidate) => candidate.patternParts.length === route.patternParts.length + 1 && candidate.patternParts.at(-1)?.endsWith("*") && patternsStructurallyEquivalent(candidate.patternParts.slice(0, -1), route.patternParts))) {
377
+ ghostParentRoutes.push(route);
378
+ continue;
379
+ }
369
380
  if (routePatterns.has(route.pattern)) {
370
381
  ghostParentRoutes.push(route);
371
382
  continue;
@@ -434,11 +445,16 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
434
445
  const applySlotSubPages = (route, slotPages, rawSegments) => {
435
446
  route.parallelSlots = route.parallelSlots.map((slot) => {
436
447
  const subPage = slotPages.get(slot.key);
437
- if (subPage !== void 0) return {
438
- ...slot,
439
- pagePath: subPage,
440
- routeSegments: rawSegments
441
- };
448
+ if (subPage !== void 0) {
449
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage, matcher);
450
+ return {
451
+ ...slot,
452
+ pagePath: subPage,
453
+ configLayoutPaths,
454
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
455
+ routeSegments: rawSegments
456
+ };
457
+ }
442
458
  return slot;
443
459
  });
444
460
  };
@@ -495,9 +511,12 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
495
511
  if (Array.from(routesByPattern.values()).some((r) => patternsStructurallyEquivalent(r.patternParts, syntheticParts))) continue;
496
512
  const subSlots = parentRoute.parallelSlots.map((slot) => {
497
513
  const subPage = slotPages.get(slot.key);
514
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, subPage ?? null, matcher);
498
515
  return {
499
516
  ...slot,
500
517
  pagePath: subPage || null,
518
+ configLayoutPaths,
519
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
501
520
  routeSegments: subPage ? rawSegments : null
502
521
  };
503
522
  });
@@ -574,6 +593,23 @@ function findSlotSubPages(slotDir, matcher) {
574
593
  perMatcher.set(slotDir, results);
575
594
  return results;
576
595
  }
596
+ function findSlotConfigLayoutPaths(slotDir, pagePath, matcher) {
597
+ if (!pagePath) return [];
598
+ const layouts = [];
599
+ let dir = path.dirname(pagePath);
600
+ while (dir !== slotDir && dir.startsWith(`${slotDir}${path.sep}`)) {
601
+ const layoutPath = findFile(dir, "layout", matcher);
602
+ if (layoutPath) layouts.unshift(layoutPath);
603
+ dir = path.dirname(dir);
604
+ }
605
+ return layouts;
606
+ }
607
+ function findSlotConfigLayoutTreePositions(slotDir, layoutPaths) {
608
+ return layoutPaths.map((layoutPath) => {
609
+ const relativeDir = path.relative(slotDir, path.dirname(layoutPath));
610
+ return relativeDir ? relativeDir.split(path.sep).filter(Boolean).length : 0;
611
+ });
612
+ }
577
613
  /**
578
614
  * Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
579
615
  * `[[...slug]]` directory that contains a `page` file. Returns the absolute
@@ -604,17 +640,29 @@ function findCatchAllPage(dir, matcher) {
604
640
  }
605
641
  /**
606
642
  * Convert a file path relative to app/ into an AppRoute.
643
+ *
644
+ * `file` and `appDir` must be forward-slash. `file` comes from
645
+ * `scanWithExtensions` (already forward-slash) and is joined onto `appDir` with
646
+ * `path.posix.join` to form the page/route path, so a native input would
647
+ * produce a mixed separator on Windows.
607
648
  */
608
649
  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);
650
+ let dir = path.posix.dirname(file);
651
+ if (type === "page" && dir !== "." && path.posix.basename(dir) === "@children") {
652
+ const parent = path.posix.dirname(dir);
612
653
  dir = parent === "" || parent === "." ? "." : parent;
613
654
  }
614
- return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
655
+ return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.posix.join(appDir, file) : null, type === "route" ? path.posix.join(appDir, file) : null);
615
656
  }
616
- function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
617
- const segments = dir === "." ? [] : dir.split(path.sep);
657
+ /**
658
+ * `dir`, `appDir`, `pagePath`, and `routePath` must all be forward-slash. `dir`
659
+ * is split on `path.posix.sep` and joined onto `appDir` with `path.posix.join`.
660
+ * `appDir` is threaded to the layout/slot/boundary discovery below, which builds
661
+ * paths the same way. `pagePath` and `routePath` are stored on the route node as
662
+ * canonical ids that get compared and re-joined downstream.
663
+ */
664
+ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeNestedOnlySlots = false) {
665
+ const segments = dir === "." ? [] : dir.split("/");
618
666
  const params = [];
619
667
  let isDynamic = false;
620
668
  const convertedRoute = convertSegmentsToRouteParts(segments);
@@ -631,7 +679,8 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
631
679
  const errorEntries = discoverSegmentErrors(segments, appDir, matcher);
632
680
  const errorPaths = errorEntries.map((entry) => entry.path);
633
681
  const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
634
- const routeDir = dir === "." ? appDir : path.join(appDir, dir);
682
+ const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
683
+ const effectivePagePath = pagePath ?? (routePath ? null : findFile(routeDir, "default", matcher));
635
684
  const loadingPath = findFile(routeDir, "loading", matcher);
636
685
  const errorPath = findFile(routeDir, "error", matcher);
637
686
  const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
@@ -640,11 +689,11 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
640
689
  const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
641
690
  const forbiddenPaths = discoverBoundaryFilePerLayout(layouts, "forbidden", matcher);
642
691
  const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
643
- const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
692
+ const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots);
644
693
  return {
645
694
  ids: createAppRouteSemanticIds({
646
695
  pattern: pattern === "/" ? "/" : pattern,
647
- pagePath,
696
+ pagePath: effectivePagePath,
648
697
  routePath,
649
698
  routeSegments: segments,
650
699
  layoutTreePositions,
@@ -652,7 +701,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
652
701
  slots: parallelSlots
653
702
  }),
654
703
  pattern: pattern === "/" ? "/" : pattern,
655
- pagePath,
704
+ pagePath: effectivePagePath,
656
705
  routePath,
657
706
  layouts,
658
707
  templates,
@@ -863,8 +912,12 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
863
912
  * Walk from appDir through each segment to the route's directory. At each level
864
913
  * that has @slot dirs, collect them. Slots at the route's own directory level
865
914
  * use page.tsx; slots at ancestor levels use default.tsx only.
915
+ *
916
+ * `appDir` and `routeDir` must be forward-slash — `currentDir` descends from
917
+ * `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
918
+ * below only matches when both share the canonical separator.
866
919
  */
867
- function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
920
+ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots = false) {
868
921
  const slotMap = /* @__PURE__ */ new Map();
869
922
  let currentDir = appDir;
870
923
  const dirsToCheck = [];
@@ -875,7 +928,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
875
928
  segmentIndex: 0
876
929
  });
877
930
  for (let i = 0; i < segments.length; i++) {
878
- currentDir = path.join(currentDir, segments[i]);
931
+ currentDir = path.posix.join(currentDir, segments[i]);
879
932
  if (findFile(currentDir, "layout", matcher)) layoutIdx++;
880
933
  dirsToCheck.push({
881
934
  dir: currentDir,
@@ -887,9 +940,9 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
887
940
  for (const { dir, layoutIdx: lvlLayoutIdx, segmentIndex } of dirsToCheck) {
888
941
  if (lvlLayoutIdx < 0 && routeHasLayout) continue;
889
942
  const slotLayoutIdx = Math.max(lvlLayoutIdx, 0);
890
- const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
891
943
  const segmentsBelow = segments.slice(segmentIndex);
892
944
  const isActiveUrlLevel = dir === routeDir || segmentsBelow.every(isInvisibleSegment);
945
+ const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots && isActiveUrlLevel);
893
946
  for (const slot of slotsAtLevel) if (isActiveUrlLevel) {
894
947
  slot.layoutIndex = slotLayoutIdx;
895
948
  slotMap.set(slot.key, slot);
@@ -902,9 +955,12 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
902
955
  slotPatternParts = [...ownerUrl?.urlSegments ?? [], ...mirror.slotUrlSegments];
903
956
  slotParamNames = [...ownerUrl?.params ?? [], ...mirror.slotParamNames];
904
957
  }
958
+ const configLayoutPaths = findSlotConfigLayoutPaths(slot.ownerDir, mirror?.pagePath ?? null, matcher);
905
959
  const inheritedSlot = {
906
960
  ...slot,
907
961
  pagePath: mirror?.pagePath ?? null,
962
+ configLayoutPaths,
963
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slot.ownerDir, configLayoutPaths),
908
964
  layoutIndex: slotLayoutIdx,
909
965
  routeSegments: mirror?.segments ?? null,
910
966
  slotPatternParts,
@@ -1035,6 +1091,9 @@ function patternsStructurallyEquivalent(a, b) {
1035
1091
  *
1036
1092
  * Returns the absolute page path, or null if no root-level page is found.
1037
1093
  *
1094
+ * `slotDir` must be forward-slash: the `path.posix.join` descent stays a
1095
+ * canonical id only when the base already is.
1096
+ *
1038
1097
  * Only descends into route-group directories (those whose name starts with `(`
1039
1098
  * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1040
1099
  * are not transparent and are therefore not searched.
@@ -1051,16 +1110,22 @@ function findSlotRootPage(slotDir, matcher) {
1051
1110
  for (const entry of entries) {
1052
1111
  if (!entry.isDirectory()) continue;
1053
1112
  if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1054
- const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1113
+ const found = findSlotRootPage(path.posix.join(slotDir, entry.name), matcher);
1055
1114
  if (found) return found;
1056
1115
  }
1057
1116
  return null;
1058
1117
  }
1059
1118
  /**
1060
1119
  * 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.
1120
+ * Returns a ParallelSlot for each @-prefixed subdirectory that has a page,
1121
+ * default component, intercepting route, or nested page-backed sub-route.
1122
+ *
1123
+ * `dir` and `appDir` must be forward-slash. The slot directory is built from
1124
+ * `dir` with `path.posix.join`, and the owner segments and slot key come from
1125
+ * `path.posix.relative(appDir, …)`, which only yields a forward-slash relative
1126
+ * path when both operands already are.
1062
1127
  */
1063
- function discoverParallelSlots(dir, appDir, matcher) {
1128
+ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
1064
1129
  if (!fs.existsSync(dir)) return [];
1065
1130
  const entries = fs.readdirSync(dir, { withFileTypes: true });
1066
1131
  const slots = [];
@@ -1068,16 +1133,18 @@ function discoverParallelSlots(dir, appDir, matcher) {
1068
1133
  if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
1069
1134
  if (entry.name === "@children") continue;
1070
1135
  const slotName = entry.name.slice(1);
1071
- const slotDir = path.join(dir, entry.name);
1136
+ const slotDir = path.posix.join(dir, entry.name);
1072
1137
  const pagePath = findSlotRootPage(slotDir, matcher);
1073
1138
  const defaultPath = findFile(slotDir, "default", matcher);
1074
1139
  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);
1140
+ const hasNestedPages = includeNestedOnlySlots && findSlotSubPages(slotDir, matcher).length > 0;
1141
+ if (!pagePath && !defaultPath && interceptingRoutes.length === 0 && !hasNestedPages) continue;
1142
+ const ownerSegments = path.posix.relative(appDir, dir).split("/").filter((segment) => segment.length > 0);
1077
1143
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1144
+ const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1078
1145
  slots.push({
1079
1146
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1080
- key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
1147
+ key: `${slotName}@${path.posix.relative(appDir, slotDir)}`,
1081
1148
  name: slotName,
1082
1149
  ownerDir: slotDir,
1083
1150
  ownerTreePath,
@@ -1085,6 +1152,8 @@ function discoverParallelSlots(dir, appDir, matcher) {
1085
1152
  pagePath,
1086
1153
  defaultPath,
1087
1154
  layoutPath: findFile(slotDir, "layout", matcher),
1155
+ configLayoutPaths,
1156
+ configLayoutTreePositions: findSlotConfigLayoutTreePositions(slotDir, configLayoutPaths),
1088
1157
  loadingPath: findFile(slotDir, "loading", matcher),
1089
1158
  errorPath: findFile(slotDir, "error", matcher),
1090
1159
  interceptingRoutes,
@@ -1134,6 +1203,10 @@ function isInterceptionMarkerDir(name) {
1134
1203
  * Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.
1135
1204
  * They intercept navigation to another route and render within the slot instead.
1136
1205
  *
1206
+ * `slotDir`, `routeDir`, and `appDir` must be forward-slash. They are passed
1207
+ * down to `path.posix.join` and `path.posix.relative` when building the
1208
+ * intercept page paths and target patterns.
1209
+ *
1137
1210
  * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
1138
1211
  * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
1139
1212
  * @param appDir - The root app directory
@@ -1237,6 +1310,10 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1237
1310
  /**
1238
1311
  * Recursively scan a directory tree for page.tsx files that are inside
1239
1312
  * intercepting route directories.
1313
+ *
1314
+ * `currentDir`, `routeDir`, and `appDir` must be forward-slash. `currentDir`
1315
+ * descends with `path.posix.join` and all three reach the `path.posix.join` /
1316
+ * `path.posix.relative` calls that build the intercept page paths and patterns.
1240
1317
  */
1241
1318
  function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1242
1319
  if (!fs.existsSync(currentDir)) return;
@@ -1245,11 +1322,11 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
1245
1322
  if (!entry.isDirectory()) continue;
1246
1323
  if (entry.name.startsWith("_")) continue;
1247
1324
  const interceptMatch = matchInterceptConvention(entry.name);
1325
+ const interceptDir = path.posix.join(currentDir, entry.name);
1248
1326
  if (interceptMatch) {
1249
1327
  const restOfName = entry.name.slice(interceptMatch.prefix.length);
1250
- const interceptDir = path.join(currentDir, entry.name);
1251
1328
  collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, results, matcher);
1252
- } else scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results, matcher);
1329
+ } else scanForInterceptingPages(interceptDir, routeDir, appDir, results, matcher);
1253
1330
  }
1254
1331
  }
1255
1332
  /**
@@ -1272,8 +1349,12 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1272
1349
  if (targetPattern) {
1273
1350
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1274
1351
  results.push({
1352
+ branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
1275
1353
  convention,
1276
1354
  layoutPaths: [...layoutPaths],
1355
+ layoutSegments: layoutPaths.map((layoutPath) => {
1356
+ return [interceptSegment, ...path.relative(interceptRoot, path.dirname(layoutPath)).split(path.sep).filter(Boolean)];
1357
+ }),
1277
1358
  targetPattern: targetPattern.pattern,
1278
1359
  sourceMatchPattern,
1279
1360
  pagePath: page,
@@ -1407,6 +1488,9 @@ const findFileProbeCache = /* @__PURE__ */ new WeakMap();
1407
1488
  * registered per-scan cache; otherwise falls back to a direct probe (identical
1408
1489
  * result). The `null` "not found" outcome is cached too, so repeated misses on
1409
1490
  * shared ancestors cost a single set of `existsSync` calls per scan.
1491
+ *
1492
+ * `dir` must be forward-slash. The returned path comes from `findFileWithExts`,
1493
+ * so it is forward-slash too.
1410
1494
  */
1411
1495
  function findFile(dir, name, matcher) {
1412
1496
  const cache = findFileProbeCache.get(matcher);
@@ -12,11 +12,16 @@ declare function invalidateAppRouteCache(): void;
12
12
  * TODO(#726): Layer 4 should consume this read model directly once the
13
13
  * navigation planner owns route graph facts.
14
14
  *
15
+ * `appDir` must be forward-slash — callers normalize it at their entry, and it
16
+ * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
17
+ *
15
18
  * @internal
16
19
  */
17
20
  declare function appRouteGraph(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraph>;
18
21
  /**
19
22
  * Scan the app/ directory and return a list of routes.
23
+ *
24
+ * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
20
25
  */
21
26
  declare function appRouter(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraphRoute[]>;
22
27
  /**
@@ -30,6 +30,9 @@ function invalidateAppRouteCache() {
30
30
  * TODO(#726): Layer 4 should consume this read model directly once the
31
31
  * navigation planner owns route graph facts.
32
32
  *
33
+ * `appDir` must be forward-slash — callers normalize it at their entry, and it
34
+ * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
35
+ *
33
36
  * @internal
34
37
  */
35
38
  async function appRouteGraph(appDir, pageExtensions, matcher) {
@@ -44,6 +47,8 @@ async function appRouteGraph(appDir, pageExtensions, matcher) {
44
47
  }
45
48
  /**
46
49
  * Scan the app/ directory and return a list of routes.
50
+ *
51
+ * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
47
52
  */
48
53
  async function appRouter(appDir, pageExtensions, matcher) {
49
54
  return (await appRouteGraph(appDir, pageExtensions, matcher)).routes;
@@ -21,6 +21,9 @@ declare function findFileWithExtensions(basePath: string, matcher: ValidFileMatc
21
21
  /**
22
22
  * Find a file by basename and configured page extension in a directory.
23
23
  * Returns the first matching absolute path, or null if not found.
24
+ *
25
+ * `dir` must be forward-slash. The returned path is built with `path.posix.join`,
26
+ * so it is forward-slash too.
24
27
  */
25
28
  declare function findFileWithExts(dir: string, name: string, matcher: ValidFileMatcher): string | null;
26
29
  /**
@@ -54,6 +57,11 @@ declare function buildViteResolveExtensions(pageExtensions?: readonly string[] |
54
57
  declare function normalizeViteResolveExtensions(extensions: readonly string[]): string[];
55
58
  /**
56
59
  * Use function-form exclude for Node < 22.14 compatibility.
60
+ *
61
+ * Yields forward-slash relative paths: node's glob emits native (backslash)
62
+ * separators on Windows, so each match is normalized — this is the entry point
63
+ * that lets downstream consumers treat the scanned paths as canonical
64
+ * forward-slash ids.
57
65
  */
58
66
  declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
59
67
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { escapeRegExp } from "../utils/regex.js";
2
+ import { normalizePathSeparators } from "../utils/path.js";
2
3
  import { existsSync } from "node:fs";
3
4
  import path from "node:path";
4
5
  import { glob } from "node:fs/promises";
@@ -66,6 +67,9 @@ function findFileWithExtensions(basePath, matcher) {
66
67
  /**
67
68
  * Find a file by basename and configured page extension in a directory.
68
69
  * Returns the first matching absolute path, or null if not found.
70
+ *
71
+ * `dir` must be forward-slash. The returned path is built with `path.posix.join`,
72
+ * so it is forward-slash too.
69
73
  */
70
74
  function findFileWithExts(dir, name, matcher) {
71
75
  for (const ext of matcher.dottedExtensions) {
@@ -135,13 +139,18 @@ function normalizeViteResolveExtensions(extensions) {
135
139
  }
136
140
  /**
137
141
  * Use function-form exclude for Node < 22.14 compatibility.
142
+ *
143
+ * Yields forward-slash relative paths: node's glob emits native (backslash)
144
+ * separators on Windows, so each match is normalized — this is the entry point
145
+ * that lets downstream consumers treat the scanned paths as canonical
146
+ * forward-slash ids.
138
147
  */
139
148
  async function* scanWithExtensions(stem, cwd, extensions, exclude) {
140
149
  const pattern = buildExtensionGlob(stem, extensions);
141
150
  for await (const file of glob(pattern, {
142
151
  cwd,
143
152
  ...exclude ? { exclude } : {}
144
- })) yield file;
153
+ })) yield normalizePathSeparators(file);
145
154
  }
146
155
  //#endregion
147
156
  export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
@@ -63,7 +63,7 @@ async function scanPageRoutes(pagesDir, matcher) {
63
63
  function fileToRoute(file, pagesDir, matcher) {
64
64
  const withoutExt = matcher.stripExtension(file);
65
65
  if (withoutExt === file) return null;
66
- const segments = withoutExt.split(path.sep);
66
+ const segments = withoutExt.split("/");
67
67
  if (segments[segments.length - 1] === "index") segments.pop();
68
68
  const params = [];
69
69
  let isDynamic = false;
@@ -152,7 +152,7 @@ async function scanApiRoutes(pagesDir, matcher) {
152
152
  }
153
153
  const routes = [];
154
154
  for (const file of files) {
155
- const route = fileToRoute(path.join("api", file), pagesDir, matcher);
155
+ const route = fileToRoute(path.posix.join("api", file), pagesDir, matcher);
156
156
  if (route) routes.push(route);
157
157
  }
158
158
  validateRoutePatterns(routes.map((route) => route.pattern));
@@ -25,6 +25,7 @@ declare function isServerActionResult<TRoot>(value: unknown): value is AppBrowse
25
25
  declare function shouldClearClientNavigationCachesForServerActionResult<TRoot>(result: AppBrowserServerActionResult<TRoot> | TRoot, revalidation?: ServerActionRevalidationKind): boolean;
26
26
  declare function parseServerActionRevalidationHeader(headers: Pick<Headers, "get">): ServerActionRevalidationKind;
27
27
  declare function normalizeServerActionThrownValue(data: unknown, responseStatus: number): unknown;
28
+ declare function shouldSyncServerActionHttpFallbackHead<TRoot>(result: AppBrowserServerActionResult<TRoot> | TRoot): boolean;
28
29
  declare function readInvalidServerActionResponseError(response: Pick<Response, "headers" | "status" | "text">, hasRedirectLocation: boolean): Promise<Error | null>;
29
30
  type ServerActionResultResponseFactsInput = {
30
31
  actionRedirectHref: string | null;
@@ -61,4 +62,4 @@ type DiscardedServerActionRefreshSchedulerOptions = {
61
62
  };
62
63
  declare function createDiscardedServerActionRefreshScheduler(options: DiscardedServerActionRefreshSchedulerOptions): DiscardedServerActionRefreshScheduler;
63
64
  //#endregion
64
- export { AppBrowserServerActionResult, ServerActionResultResponseFactsInput, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
65
+ export { AppBrowserServerActionResult, ServerActionResultResponseFactsInput, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction, shouldSyncServerActionHttpFallbackHead };