vinext 0.1.8 → 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 (237) 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.js +40 -2
  17. package/dist/cli-args.d.ts +1 -2
  18. package/dist/cli-args.js +1 -1
  19. package/dist/cli-dev-config.d.ts +12 -0
  20. package/dist/cli-dev-config.js +23 -0
  21. package/dist/cli.js +111 -95
  22. package/dist/{server → client}/dev-error-overlay-store.d.ts +1 -1
  23. package/dist/{server → client}/dev-error-overlay-store.js +1 -1
  24. package/dist/{server → client}/dev-error-overlay.d.ts +1 -1
  25. package/dist/{server → client}/dev-error-overlay.js +2 -2
  26. package/dist/client/navigation-runtime.d.ts +2 -0
  27. package/dist/client/navigation-runtime.js +3 -1
  28. package/dist/client/window-next.d.ts +2 -1
  29. package/dist/cloudflare/index.d.ts +2 -2
  30. package/dist/cloudflare/index.js +2 -2
  31. package/dist/config/config-matchers.js +14 -10
  32. package/dist/config/next-config.d.ts +5 -3
  33. package/dist/config/next-config.js +44 -1
  34. package/dist/config/prerender.d.ts +34 -0
  35. package/dist/config/prerender.js +55 -0
  36. package/dist/entries/app-rsc-entry.d.ts +2 -1
  37. package/dist/entries/app-rsc-entry.js +40 -10
  38. package/dist/entries/app-rsc-manifest.js +2 -0
  39. package/dist/entries/pages-client-entry.d.ts +3 -0
  40. package/dist/entries/pages-client-entry.js +15 -1
  41. package/dist/entries/pages-server-entry.js +5 -2
  42. package/dist/image/image-adapters-virtual.d.ts +59 -0
  43. package/dist/image/image-adapters-virtual.js +59 -0
  44. package/dist/index.d.ts +33 -0
  45. package/dist/index.js +167 -100
  46. package/dist/init-cloudflare.d.ts +43 -0
  47. package/dist/init-cloudflare.js +839 -0
  48. package/dist/init-platform.d.ts +40 -0
  49. package/dist/init-platform.js +195 -0
  50. package/dist/init.d.ts +16 -38
  51. package/dist/init.js +209 -99
  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/packages/cloudflare/src/deploy-config.js +150 -0
  58. package/dist/packages/cloudflare/src/deploy-help.js +55 -0
  59. package/dist/packages/cloudflare/src/deploy.js +276 -0
  60. package/dist/{cloudflare → packages/cloudflare/src}/tpr.d.ts +4 -41
  61. package/dist/{cloudflare → packages/cloudflare/src}/tpr.js +18 -15
  62. package/dist/plugins/ast-scope.d.ts +16 -0
  63. package/dist/plugins/ast-scope.js +62 -0
  64. package/dist/plugins/ast-utils.d.ts +27 -1
  65. package/dist/plugins/ast-utils.js +32 -1
  66. package/dist/plugins/css-module-imports.d.ts +14 -0
  67. package/dist/plugins/css-module-imports.js +59 -0
  68. package/dist/plugins/extensionless-dynamic-import.js +2 -2
  69. package/dist/plugins/ignore-dynamic-requests.d.ts +11 -0
  70. package/dist/plugins/ignore-dynamic-requests.js +530 -0
  71. package/dist/plugins/typeof-window.d.ts +1 -1
  72. package/dist/plugins/typeof-window.js +28 -56
  73. package/dist/routing/app-route-graph.d.ts +23 -2
  74. package/dist/routing/app-route-graph.js +126 -32
  75. package/dist/routing/file-matcher.d.ts +3 -0
  76. package/dist/routing/file-matcher.js +3 -0
  77. package/dist/routing/pages-router.js +2 -2
  78. package/dist/server/api-handler.js +9 -14
  79. package/dist/server/app-browser-action-result.d.ts +4 -1
  80. package/dist/server/app-browser-action-result.js +8 -1
  81. package/dist/server/app-browser-entry.js +161 -52
  82. package/dist/server/app-browser-interception-context.d.ts +1 -0
  83. package/dist/server/app-browser-interception-context.js +4 -2
  84. package/dist/server/app-browser-navigation-controller.d.ts +2 -0
  85. package/dist/server/app-browser-navigation-controller.js +60 -31
  86. package/dist/server/app-browser-server-action-client.d.ts +1 -1
  87. package/dist/server/app-browser-server-action-client.js +3 -4
  88. package/dist/server/app-browser-state.d.ts +11 -1
  89. package/dist/server/app-browser-state.js +25 -8
  90. package/dist/server/app-browser-visible-commit.d.ts +6 -1
  91. package/dist/server/app-browser-visible-commit.js +43 -25
  92. package/dist/server/app-elements-wire.d.ts +9 -2
  93. package/dist/server/app-elements-wire.js +8 -1
  94. package/dist/server/app-elements.d.ts +2 -2
  95. package/dist/server/app-elements.js +4 -3
  96. package/dist/server/app-fallback-renderer.d.ts +2 -1
  97. package/dist/server/app-fallback-renderer.js +10 -6
  98. package/dist/server/app-hydration-cache-publication.d.ts +11 -0
  99. package/dist/server/app-hydration-cache-publication.js +43 -0
  100. package/dist/server/app-middleware.d.ts +4 -3
  101. package/dist/server/app-middleware.js +8 -3
  102. package/dist/server/app-optimistic-routing.js +11 -4
  103. package/dist/server/app-page-cache-finalizer.js +2 -1
  104. package/dist/server/app-page-cache-render.d.ts +1 -0
  105. package/dist/server/app-page-cache-render.js +8 -4
  106. package/dist/server/app-page-cache.d.ts +1 -0
  107. package/dist/server/app-page-cache.js +5 -2
  108. package/dist/server/app-page-dispatch.js +1 -0
  109. package/dist/server/app-page-element-builder.d.ts +2 -1
  110. package/dist/server/app-page-element-builder.js +58 -4
  111. package/dist/server/app-page-probe.js +5 -0
  112. package/dist/server/app-page-render-identity.d.ts +1 -0
  113. package/dist/server/app-page-render-identity.js +1 -1
  114. package/dist/server/app-page-render.js +18 -0
  115. package/dist/server/app-page-request.d.ts +14 -0
  116. package/dist/server/app-page-request.js +69 -4
  117. package/dist/server/app-page-route-wiring.d.ts +8 -1
  118. package/dist/server/app-page-route-wiring.js +32 -18
  119. package/dist/server/app-page-segment-state.d.ts +2 -1
  120. package/dist/server/app-page-segment-state.js +3 -1
  121. package/dist/server/app-page-stream.d.ts +5 -0
  122. package/dist/server/app-page-stream.js +3 -1
  123. package/dist/server/app-route-handler-execution.js +5 -1
  124. package/dist/server/app-route-handler-response.js +1 -0
  125. package/dist/server/app-route-handler-runtime.js +1 -1
  126. package/dist/server/app-router-entry.js +9 -2
  127. package/dist/server/app-rsc-handler.d.ts +1 -0
  128. package/dist/server/app-rsc-handler.js +62 -31
  129. package/dist/server/app-rsc-request-normalization.d.ts +3 -2
  130. package/dist/server/app-rsc-request-normalization.js +6 -3
  131. package/dist/server/app-segment-config.d.ts +11 -0
  132. package/dist/server/app-segment-config.js +62 -6
  133. package/dist/server/app-server-action-execution.d.ts +1 -0
  134. package/dist/server/app-server-action-execution.js +58 -28
  135. package/dist/server/app-ssr-entry.d.ts +3 -0
  136. package/dist/server/app-ssr-entry.js +15 -7
  137. package/dist/server/app-ssr-stream.d.ts +7 -3
  138. package/dist/server/app-ssr-stream.js +10 -6
  139. package/dist/server/app-visited-response-cache.d.ts +6 -0
  140. package/dist/server/app-visited-response-cache.js +3 -1
  141. package/dist/server/client-trace-metadata.js +26 -0
  142. package/dist/server/default-global-not-found-module.d.ts +14 -0
  143. package/dist/server/default-global-not-found-module.js +14 -0
  144. package/dist/server/dev-route-files.js +4 -0
  145. package/dist/server/dev-server.js +19 -18
  146. package/dist/server/dev-stack-sourcemap.d.ts +1 -1
  147. package/dist/server/dev-stack-sourcemap.js +1 -1
  148. package/dist/server/fetch-handler.d.ts +2 -0
  149. package/dist/server/fetch-handler.js +18 -0
  150. package/dist/server/headers.d.ts +3 -15
  151. package/dist/server/headers.js +2 -15
  152. package/dist/server/image-optimization.d.ts +51 -1
  153. package/dist/server/image-optimization.js +52 -2
  154. package/dist/server/implicit-tags.js +1 -1
  155. package/dist/server/isr-cache.d.ts +1 -1
  156. package/dist/server/isr-cache.js +2 -2
  157. package/dist/server/metadata-routes.d.ts +3 -1
  158. package/dist/server/metadata-routes.js +12 -2
  159. package/dist/server/middleware-runtime.js +6 -1
  160. package/dist/server/navigation-planner.d.ts +1 -0
  161. package/dist/server/navigation-planner.js +14 -3
  162. package/dist/server/pages-api-route.js +2 -2
  163. package/dist/server/pages-asset-tags.d.ts +4 -6
  164. package/dist/server/pages-asset-tags.js +12 -12
  165. package/dist/server/pages-client-assets.d.ts +12 -0
  166. package/dist/server/pages-client-assets.js +10 -0
  167. package/dist/server/pages-node-compat.d.ts +8 -1
  168. package/dist/server/pages-node-compat.js +32 -4
  169. package/dist/server/pages-page-data.d.ts +24 -1
  170. package/dist/server/pages-page-data.js +45 -24
  171. package/dist/server/pages-page-handler.d.ts +2 -1
  172. package/dist/server/pages-page-handler.js +19 -12
  173. package/dist/server/pages-page-response.d.ts +7 -0
  174. package/dist/server/pages-page-response.js +4 -1
  175. package/dist/server/pages-request-pipeline.d.ts +2 -0
  176. package/dist/server/pages-request-pipeline.js +25 -1
  177. package/dist/server/pages-router-entry.d.ts +30 -0
  178. package/dist/server/pages-router-entry.js +108 -0
  179. package/dist/server/prerender-manifest.d.ts +3 -1
  180. package/dist/server/prerender-route-params.js +1 -1
  181. package/dist/server/prod-server.d.ts +1 -1
  182. package/dist/server/prod-server.js +33 -20
  183. package/dist/server/request-pipeline.js +1 -0
  184. package/dist/server/seed-cache.js +4 -4
  185. package/dist/shims/app-router-scroll-state.d.ts +1 -0
  186. package/dist/shims/app-router-scroll-state.js +1 -0
  187. package/dist/shims/app-router-scroll.js +2 -1
  188. package/dist/shims/cache.js +19 -15
  189. package/dist/shims/cdn-cache.js +1 -1
  190. package/dist/shims/dynamic-preload-chunks.js +2 -1
  191. package/dist/shims/form.d.ts +3 -1
  192. package/dist/shims/form.js +37 -43
  193. package/dist/shims/headers.d.ts +7 -1
  194. package/dist/shims/headers.js +17 -3
  195. package/dist/shims/image.js +75 -14
  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 -15
  201. package/dist/shims/internal/hybrid-client-route-owner.js +9 -60
  202. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +9 -5
  203. package/dist/shims/internal/pages-data-fetch-dedup.js +73 -14
  204. package/dist/shims/internal/pages-data-target.d.ts +10 -9
  205. package/dist/shims/internal/pages-data-target.js +91 -12
  206. package/dist/shims/internal/pages-router-components.d.ts +7 -0
  207. package/dist/shims/internal/pages-router-components.js +13 -0
  208. package/dist/shims/layout-segment-context.d.ts +4 -1
  209. package/dist/shims/layout-segment-context.js +18 -4
  210. package/dist/shims/link.js +55 -26
  211. package/dist/shims/navigation.d.ts +5 -1
  212. package/dist/shims/navigation.js +45 -10
  213. package/dist/shims/navigation.react-server.js +1 -1
  214. package/dist/shims/root-params.d.ts +15 -1
  215. package/dist/shims/root-params.js +21 -1
  216. package/dist/shims/router.d.ts +5 -7
  217. package/dist/shims/router.js +362 -93
  218. package/dist/shims/server.js +3 -2
  219. package/dist/shims/slot.js +3 -6
  220. package/dist/utils/client-runtime-metadata.d.ts +2 -18
  221. package/dist/utils/client-runtime-metadata.js +31 -22
  222. package/dist/utils/dev-stack-sourcemap-endpoint.d.ts +4 -0
  223. package/dist/{server → utils}/dev-stack-sourcemap-endpoint.js +1 -1
  224. package/dist/utils/domain-locale.d.ts +6 -3
  225. package/dist/{server → utils}/middleware-request-headers.d.ts +1 -1
  226. package/dist/{server → utils}/middleware-request-headers.js +2 -2
  227. package/dist/utils/project.d.ts +48 -2
  228. package/dist/utils/project.js +225 -4
  229. package/dist/utils/protocol-headers.d.ts +17 -0
  230. package/dist/utils/protocol-headers.js +17 -0
  231. package/dist/utils/react-version.d.ts +4 -0
  232. package/dist/utils/react-version.js +44 -0
  233. package/package.json +47 -2
  234. package/dist/deploy.d.ts +0 -197
  235. package/dist/deploy.js +0 -1040
  236. package/dist/server/dev-stack-sourcemap-endpoint.d.ts +0 -4
  237. /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;
@@ -83,7 +83,12 @@ type AppRoute = {
83
83
  routePath: string | null; /** Ordered list of layout files from root to leaf */
84
84
  layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
85
85
  templates: string[]; /** Parallel route slots (from @slot directories at the route's directory level) */
86
- 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
+ };
87
92
  /**
88
93
  * Interception markers not wrapped in an `@slot` directory.
89
94
  * On soft-nav, the intercepting page replaces the entire page response.
@@ -122,7 +127,16 @@ type AppRoute = {
122
127
  * Includes route groups and dynamic segments (as template strings like "[id]").
123
128
  * Used at render time to compute the child segments for useSelectedLayoutSegments().
124
129
  */
125
- 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. */
126
140
  templateTreePositions?: number[];
127
141
  /**
128
142
  * Tree position (directory depth from app/ root) for each layout.
@@ -276,6 +290,13 @@ type RouteManifest = {
276
290
  graphVersion: GraphVersion;
277
291
  segmentGraph: StaticSegmentGraph;
278
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
+ */
279
300
  declare function buildAppRouteGraph(appDir: string, matcher: ValidFileMatcher): Promise<{
280
301
  routes: AppRouteGraphRoute[];
281
302
  routeManifest: RouteManifest;
@@ -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 };
@@ -363,9 +387,13 @@ async function buildAppRouteGraph(appDir, matcher) {
363
387
  const dir = path.posix.dirname(file);
364
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;
@@ -482,8 +510,29 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
482
510
  }
483
511
  }
484
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}`);
485
515
  const childrenDefault = findFile(parentPageDir, "default", matcher);
486
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
+ }
487
536
  const childrenCatchAll = childrenDefault ? null : findCatchAllPage(parentPageDir, matcher);
488
537
  const childrenFallback = childrenDefault ?? childrenCatchAll;
489
538
  for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
@@ -525,6 +574,11 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
525
574
  layouts: parentRoute.layouts,
526
575
  templates: parentRoute.templates,
527
576
  parallelSlots: subSlots,
577
+ childrenSlot: {
578
+ id: childrenSlotId,
579
+ ownerTreePath: childrenOwnerTreePath,
580
+ state: childrenDefault ? "default" : childrenCatchAll ? "active" : "unmatched"
581
+ },
528
582
  loadingPath: parentRoute.loadingPath,
529
583
  errorPath: parentRoute.errorPath,
530
584
  layoutErrorPaths: parentRoute.layoutErrorPaths,
@@ -535,6 +589,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
535
589
  unauthorizedPath: parentRoute.unauthorizedPath,
536
590
  unauthorizedPaths: parentRoute.unauthorizedPaths,
537
591
  routeSegments: [...parentRoute.routeSegments, ...rawSegments],
592
+ childrenRouteSegments: childrenDefault ? parentRoute.routeSegments : void 0,
538
593
  templateTreePositions: parentRoute.templateTreePositions,
539
594
  layoutTreePositions: parentRoute.layoutTreePositions,
540
595
  isDynamic: parentRoute.isDynamic || subIsDynamic,
@@ -549,6 +604,26 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
549
604
  }
550
605
  return syntheticRoutes;
551
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
+ }
552
627
  const findSlotSubPagesCache = /* @__PURE__ */ new WeakMap();
553
628
  function findSlotSubPages(slotDir, matcher) {
554
629
  let perMatcher = findSlotSubPagesCache.get(matcher);
@@ -629,23 +704,29 @@ function findCatchAllPage(dir, matcher) {
629
704
  }
630
705
  /**
631
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.
632
712
  */
633
713
  function fileToAppRoute(file, appDir, type, matcher) {
634
- let dir = path.dirname(file);
635
- if (type === "page" && dir !== "." && path.basename(dir) === "@children") {
636
- 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);
637
717
  dir = parent === "" || parent === "." ? "." : parent;
638
718
  }
639
- 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);
640
720
  }
641
721
  /**
642
- * `dir` and `appDir` must both be forward-slash. `dir` is split on
643
- * `path.posix.sep` and joined onto `appDir` with `path.posix.join`, and `appDir`
644
- * is threaded to the layout/slot/boundary discovery below, which build paths the
645
- * same way.
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.
646
727
  */
647
- function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
648
- const segments = dir === "." ? [] : dir.split(path.posix.sep);
728
+ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeNestedOnlySlots = false) {
729
+ const segments = dir === "." ? [] : dir.split("/");
649
730
  const params = [];
650
731
  let isDynamic = false;
651
732
  const convertedRoute = convertSegmentsToRouteParts(segments);
@@ -663,6 +744,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
663
744
  const errorPaths = errorEntries.map((entry) => entry.path);
664
745
  const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
665
746
  const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
747
+ const effectivePagePath = pagePath ?? (routePath ? null : findFile(routeDir, "default", matcher));
666
748
  const loadingPath = findFile(routeDir, "loading", matcher);
667
749
  const errorPath = findFile(routeDir, "error", matcher);
668
750
  const notFoundPath = discoverBoundaryFile(segments, appDir, "not-found", matcher);
@@ -671,11 +753,11 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
671
753
  const notFoundPaths = discoverBoundaryFilePerLayout(layouts, "not-found", matcher);
672
754
  const forbiddenPaths = discoverBoundaryFilePerLayout(layouts, "forbidden", matcher);
673
755
  const unauthorizedPaths = discoverBoundaryFilePerLayout(layouts, "unauthorized", matcher);
674
- const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
756
+ const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots);
675
757
  return {
676
758
  ids: createAppRouteSemanticIds({
677
759
  pattern: pattern === "/" ? "/" : pattern,
678
- pagePath,
760
+ pagePath: effectivePagePath,
679
761
  routePath,
680
762
  routeSegments: segments,
681
763
  layoutTreePositions,
@@ -683,7 +765,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
683
765
  slots: parallelSlots
684
766
  }),
685
767
  pattern: pattern === "/" ? "/" : pattern,
686
- pagePath,
768
+ pagePath: effectivePagePath,
687
769
  routePath,
688
770
  layouts,
689
771
  templates,
@@ -899,7 +981,7 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
899
981
  * `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
900
982
  * below only matches when both share the canonical separator.
901
983
  */
902
- function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
984
+ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots = false) {
903
985
  const slotMap = /* @__PURE__ */ new Map();
904
986
  let currentDir = appDir;
905
987
  const dirsToCheck = [];
@@ -922,9 +1004,9 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
922
1004
  for (const { dir, layoutIdx: lvlLayoutIdx, segmentIndex } of dirsToCheck) {
923
1005
  if (lvlLayoutIdx < 0 && routeHasLayout) continue;
924
1006
  const slotLayoutIdx = Math.max(lvlLayoutIdx, 0);
925
- const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
926
1007
  const segmentsBelow = segments.slice(segmentIndex);
927
1008
  const isActiveUrlLevel = dir === routeDir || segmentsBelow.every(isInvisibleSegment);
1009
+ const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots && isActiveUrlLevel);
928
1010
  for (const slot of slotsAtLevel) if (isActiveUrlLevel) {
929
1011
  slot.layoutIndex = slotLayoutIdx;
930
1012
  slotMap.set(slot.key, slot);
@@ -1083,12 +1165,7 @@ function patternsStructurallyEquivalent(a, b) {
1083
1165
  function findSlotRootPage(slotDir, matcher) {
1084
1166
  const directPage = findFile(slotDir, "page", matcher);
1085
1167
  if (directPage) return directPage;
1086
- let entries;
1087
- try {
1088
- entries = fs.readdirSync(slotDir, { withFileTypes: true });
1089
- } catch {
1090
- return null;
1091
- }
1168
+ const entries = readDirEntriesCached(slotDir, matcher);
1092
1169
  for (const entry of entries) {
1093
1170
  if (!entry.isDirectory()) continue;
1094
1171
  if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
@@ -1099,11 +1176,16 @@ function findSlotRootPage(slotDir, matcher) {
1099
1176
  }
1100
1177
  /**
1101
1178
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
1102
- * 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.
1103
1186
  */
1104
- function discoverParallelSlots(dir, appDir, matcher) {
1105
- if (!fs.existsSync(dir)) return [];
1106
- const entries = fs.readdirSync(dir, { withFileTypes: true });
1187
+ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
1188
+ const entries = readDirEntriesCached(dir, matcher);
1107
1189
  const slots = [];
1108
1190
  for (const entry of entries) {
1109
1191
  if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
@@ -1113,13 +1195,14 @@ function discoverParallelSlots(dir, appDir, matcher) {
1113
1195
  const pagePath = findSlotRootPage(slotDir, matcher);
1114
1196
  const defaultPath = findFile(slotDir, "default", matcher);
1115
1197
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1116
- if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
1117
- 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);
1118
1201
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1119
1202
  const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1120
1203
  slots.push({
1121
1204
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1122
- key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
1205
+ key: `${slotName}@${path.posix.relative(appDir, slotDir)}`,
1123
1206
  name: slotName,
1124
1207
  ownerDir: slotDir,
1125
1208
  ownerTreePath,
@@ -1178,6 +1261,10 @@ function isInterceptionMarkerDir(name) {
1178
1261
  * Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.
1179
1262
  * They intercept navigation to another route and render within the slot instead.
1180
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
+ *
1181
1268
  * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
1182
1269
  * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
1183
1270
  * @param appDir - The root app directory
@@ -1281,6 +1368,10 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1281
1368
  /**
1282
1369
  * Recursively scan a directory tree for page.tsx files that are inside
1283
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.
1284
1375
  */
1285
1376
  function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1286
1377
  if (!fs.existsSync(currentDir)) return;
@@ -1289,11 +1380,11 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
1289
1380
  if (!entry.isDirectory()) continue;
1290
1381
  if (entry.name.startsWith("_")) continue;
1291
1382
  const interceptMatch = matchInterceptConvention(entry.name);
1383
+ const interceptDir = path.posix.join(currentDir, entry.name);
1292
1384
  if (interceptMatch) {
1293
1385
  const restOfName = entry.name.slice(interceptMatch.prefix.length);
1294
- const interceptDir = path.join(currentDir, entry.name);
1295
1386
  collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, results, matcher);
1296
- } else scanForInterceptingPages(path.join(currentDir, entry.name), routeDir, appDir, results, matcher);
1387
+ } else scanForInterceptingPages(interceptDir, routeDir, appDir, results, matcher);
1297
1388
  }
1298
1389
  }
1299
1390
  /**
@@ -1455,6 +1546,9 @@ const findFileProbeCache = /* @__PURE__ */ new WeakMap();
1455
1546
  * registered per-scan cache; otherwise falls back to a direct probe (identical
1456
1547
  * result). The `null` "not found" outcome is cached too, so repeated misses on
1457
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.
1458
1552
  */
1459
1553
  function findFile(dir, name, matcher) {
1460
1554
  const cache = findFileProbeCache.get(matcher);
@@ -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
  /**
@@ -67,6 +67,9 @@ function findFileWithExtensions(basePath, matcher) {
67
67
  /**
68
68
  * Find a file by basename and configured page extension in a directory.
69
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.
70
73
  */
71
74
  function findFileWithExts(dir, name, matcher) {
72
75
  for (const ext of matcher.dottedExtensions) {
@@ -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));
@@ -1,15 +1,15 @@
1
1
  import { hasBasePath } from "../utils/base-path.js";
2
2
  import { matchRoute } from "../routing/pages-router.js";
3
3
  import "./server-globals.js";
4
- import { parseCookieHeader } from "../utils/parse-cookie.js";
5
4
  import { NextRequest } from "../shims/server.js";
6
5
  import { importModule, reportRequestError } from "./instrumentation.js";
7
6
  import { mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } from "../utils/query.js";
8
- import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
9
- import { isEdgeApiRuntime } from "./edge-api-runtime.js";
10
7
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT, resolveBodyParserConfig } from "./pages-body-parser-config.js";
8
+ import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
11
9
  import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
12
10
  import { performOnDemandRevalidate } from "./pages-revalidate.js";
11
+ import { attachPagesRequestCookies } from "./pages-node-compat.js";
12
+ import { isEdgeApiRuntime } from "./edge-api-runtime.js";
13
13
  import { decode } from "node:querystring";
14
14
  import { Buffer } from "node:buffer";
15
15
  //#region src/server/api-handler.ts
@@ -69,12 +69,6 @@ async function parseBody(req, sizeLimit = MAX_BODY_SIZE) {
69
69
  });
70
70
  });
71
71
  }
72
- /**
73
- * Parse cookies from the Cookie header.
74
- */
75
- function parseCookies(req) {
76
- return parseCookieHeader(req.headers.cookie);
77
- }
78
72
  function isEdgeApiRouteModule(module) {
79
73
  if (typeof module.default !== "function") return false;
80
74
  const bare = module.runtime;
@@ -167,12 +161,13 @@ async function writeEdgeApiResponseBody(res, body) {
167
161
  * Enhance a Node.js req/res pair with Next.js API route helpers.
168
162
  */
169
163
  function enhanceApiObjects(req, res, query, body) {
164
+ const apiReq = Object.assign(req, {
165
+ body,
166
+ query
167
+ });
168
+ attachPagesRequestCookies(apiReq);
170
169
  return {
171
- apiReq: Object.assign(req, {
172
- body,
173
- cookies: parseCookies(req),
174
- query
175
- }),
170
+ apiReq,
176
171
  apiRes: Object.assign(res, {
177
172
  status(code) {
178
173
  this.statusCode = code;
@@ -1,3 +1,4 @@
1
+ import { OperationLane } from "./operation-token.js";
1
2
  import { ServerActionResultFacts } from "./navigation-planner.js";
2
3
 
3
4
  //#region src/server/app-browser-action-result.d.ts
@@ -24,7 +25,9 @@ type ServerActionInitiationSnapshot<TRouterState> = {
24
25
  declare function isServerActionResult<TRoot>(value: unknown): value is AppBrowserServerActionResult<TRoot>;
25
26
  declare function shouldClearClientNavigationCachesForServerActionResult<TRoot>(result: AppBrowserServerActionResult<TRoot> | TRoot, revalidation?: ServerActionRevalidationKind): boolean;
26
27
  declare function parseServerActionRevalidationHeader(headers: Pick<Headers, "get">): ServerActionRevalidationKind;
28
+ declare function resolveServerActionOperationLane(revalidation: ServerActionRevalidationKind): Extract<OperationLane, "refresh" | "server-action">;
27
29
  declare function normalizeServerActionThrownValue(data: unknown, responseStatus: number): unknown;
30
+ declare function shouldSyncServerActionHttpFallbackHead<TRoot>(result: AppBrowserServerActionResult<TRoot> | TRoot): boolean;
28
31
  declare function readInvalidServerActionResponseError(response: Pick<Response, "headers" | "status" | "text">, hasRedirectLocation: boolean): Promise<Error | null>;
29
32
  type ServerActionResultResponseFactsInput = {
30
33
  actionRedirectHref: string | null;
@@ -61,4 +64,4 @@ type DiscardedServerActionRefreshSchedulerOptions = {
61
64
  };
62
65
  declare function createDiscardedServerActionRefreshScheduler(options: DiscardedServerActionRefreshSchedulerOptions): DiscardedServerActionRefreshScheduler;
63
66
  //#endregion
64
- export { AppBrowserServerActionResult, ServerActionResultResponseFactsInput, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
67
+ export { AppBrowserServerActionResult, ServerActionResultResponseFactsInput, ServerActionRevalidationKind, createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, resolveServerActionOperationLane, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction, shouldSyncServerActionHttpFallbackHead };
@@ -32,6 +32,9 @@ function parseServerActionRevalidationHeader(headers) {
32
32
  default: return "none";
33
33
  }
34
34
  }
35
+ function resolveServerActionOperationLane(revalidation) {
36
+ return revalidation === "none" ? "server-action" : "refresh";
37
+ }
35
38
  function createServerActionHttpFallbackError(status) {
36
39
  if (status !== 401 && status !== 403 && status !== 404) return null;
37
40
  const digest = status === 404 ? "NEXT_HTTP_ERROR_FALLBACK;404" : `NEXT_HTTP_ERROR_FALLBACK;${status}`;
@@ -41,6 +44,10 @@ function createServerActionHttpFallbackError(status) {
41
44
  function normalizeServerActionThrownValue(data, responseStatus) {
42
45
  return createServerActionHttpFallbackError(responseStatus) ?? data;
43
46
  }
47
+ function shouldSyncServerActionHttpFallbackHead(result) {
48
+ if (!isServerActionResult(result) || result.root !== void 0) return false;
49
+ return result.returnValue?.ok !== false;
50
+ }
44
51
  async function readInvalidServerActionResponseError(response, hasRedirectLocation) {
45
52
  const contentType = response.headers.get("content-type") ?? "";
46
53
  if (contentType.startsWith("text/x-component") || hasRedirectLocation) return null;
@@ -108,4 +115,4 @@ function createDiscardedServerActionRefreshScheduler(options) {
108
115
  };
109
116
  }
110
117
  //#endregion
111
- export { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction };
118
+ export { createDiscardedServerActionRefreshScheduler, createServerActionInitiationSnapshot, createServerActionResultFacts, isServerActionResult, normalizeServerActionThrownValue, parseServerActionRevalidationHeader, readInvalidServerActionResponseError, resolveServerActionOperationLane, shouldClearClientNavigationCachesForServerActionResult, shouldScheduleRefreshForDiscardedServerAction, shouldSyncServerActionHttpFallbackHead };