vinext 0.1.0 → 0.1.2

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 (205) hide show
  1. package/README.md +2 -5
  2. package/dist/build/assets-ignore.d.ts +32 -0
  3. package/dist/build/assets-ignore.js +48 -0
  4. package/dist/build/client-build-config.d.ts +33 -1
  5. package/dist/build/client-build-config.js +66 -1
  6. package/dist/check.js +4 -3
  7. package/dist/cli.js +2 -0
  8. package/dist/client/navigation-runtime.d.ts +11 -2
  9. package/dist/client/navigation-runtime.js +1 -1
  10. package/dist/client/vinext-next-data.d.ts +2 -1
  11. package/dist/client/window-next.d.ts +6 -4
  12. package/dist/config/config-matchers.d.ts +31 -5
  13. package/dist/config/config-matchers.js +50 -3
  14. package/dist/config/next-config.d.ts +29 -3
  15. package/dist/config/next-config.js +32 -2
  16. package/dist/deploy.js +47 -304
  17. package/dist/entries/app-rsc-entry.d.ts +8 -2
  18. package/dist/entries/app-rsc-entry.js +61 -5
  19. package/dist/entries/app-rsc-manifest.js +20 -2
  20. package/dist/entries/pages-client-entry.js +1 -1
  21. package/dist/entries/pages-server-entry.js +16 -7
  22. package/dist/index.d.ts +0 -2
  23. package/dist/index.js +233 -280
  24. package/dist/plugins/dynamic-preload-metadata.d.ts +13 -0
  25. package/dist/plugins/dynamic-preload-metadata.js +415 -0
  26. package/dist/plugins/og-assets.js +2 -2
  27. package/dist/plugins/optimize-imports.d.ts +8 -4
  28. package/dist/plugins/optimize-imports.js +16 -12
  29. package/dist/plugins/postcss.js +18 -14
  30. package/dist/plugins/require-context.d.ts +6 -0
  31. package/dist/plugins/require-context.js +184 -0
  32. package/dist/plugins/sass.d.ts +53 -24
  33. package/dist/plugins/sass.js +249 -1
  34. package/dist/plugins/wasm-module-import.d.ts +15 -0
  35. package/dist/plugins/wasm-module-import.js +50 -0
  36. package/dist/routing/app-route-graph.d.ts +35 -2
  37. package/dist/routing/app-route-graph.js +179 -8
  38. package/dist/routing/file-matcher.js +1 -1
  39. package/dist/routing/route-pattern.d.ts +2 -1
  40. package/dist/routing/route-pattern.js +16 -1
  41. package/dist/server/api-handler.js +4 -0
  42. package/dist/server/app-browser-entry.js +155 -215
  43. package/dist/server/app-browser-error.d.ts +4 -1
  44. package/dist/server/app-browser-error.js +7 -1
  45. package/dist/server/app-browser-history-controller.d.ts +104 -0
  46. package/dist/server/app-browser-history-controller.js +210 -0
  47. package/dist/server/app-browser-interception-context.d.ts +2 -1
  48. package/dist/server/app-browser-interception-context.js +15 -2
  49. package/dist/server/app-browser-navigation-controller.d.ts +13 -2
  50. package/dist/server/app-browser-navigation-controller.js +83 -4
  51. package/dist/server/app-browser-popstate.d.ts +12 -3
  52. package/dist/server/app-browser-popstate.js +19 -4
  53. package/dist/server/app-browser-rsc-redirect.d.ts +11 -2
  54. package/dist/server/app-browser-rsc-redirect.js +30 -8
  55. package/dist/server/app-browser-state.d.ts +3 -0
  56. package/dist/server/app-browser-state.js +10 -10
  57. package/dist/server/app-browser-visible-commit.js +10 -8
  58. package/dist/server/app-fallback-renderer.d.ts +2 -1
  59. package/dist/server/app-fallback-renderer.js +3 -1
  60. package/dist/server/app-history-state.d.ts +45 -1
  61. package/dist/server/app-history-state.js +109 -1
  62. package/dist/server/app-middleware.js +1 -0
  63. package/dist/server/app-optimistic-routing.js +22 -1
  64. package/dist/server/app-page-boundary-render.d.ts +2 -1
  65. package/dist/server/app-page-boundary-render.js +45 -21
  66. package/dist/server/app-page-cache.js +9 -7
  67. package/dist/server/app-page-dispatch.d.ts +14 -0
  68. package/dist/server/app-page-dispatch.js +21 -6
  69. package/dist/server/app-page-element-builder.d.ts +23 -2
  70. package/dist/server/app-page-element-builder.js +58 -17
  71. package/dist/server/app-page-execution.d.ts +1 -1
  72. package/dist/server/app-page-execution.js +32 -17
  73. package/dist/server/app-page-render.d.ts +7 -1
  74. package/dist/server/app-page-render.js +11 -16
  75. package/dist/server/app-page-request.d.ts +9 -6
  76. package/dist/server/app-page-request.js +14 -10
  77. package/dist/server/app-page-response.d.ts +2 -2
  78. package/dist/server/app-page-response.js +2 -2
  79. package/dist/server/app-page-route-wiring.d.ts +3 -1
  80. package/dist/server/app-page-route-wiring.js +10 -8
  81. package/dist/server/app-page-stream.d.ts +37 -7
  82. package/dist/server/app-page-stream.js +36 -6
  83. package/dist/server/app-pages-bridge.d.ts +16 -0
  84. package/dist/server/app-pages-bridge.js +23 -3
  85. package/dist/server/app-route-handler-cache.d.ts +1 -0
  86. package/dist/server/app-route-handler-cache.js +1 -0
  87. package/dist/server/app-route-handler-dispatch.d.ts +1 -0
  88. package/dist/server/app-route-handler-dispatch.js +2 -0
  89. package/dist/server/app-route-handler-execution.d.ts +1 -0
  90. package/dist/server/app-route-handler-execution.js +1 -0
  91. package/dist/server/app-route-handler-response.js +11 -10
  92. package/dist/server/app-route-handler-runtime.d.ts +1 -0
  93. package/dist/server/app-route-handler-runtime.js +15 -3
  94. package/dist/server/app-rsc-handler.d.ts +1 -0
  95. package/dist/server/app-rsc-handler.js +5 -4
  96. package/dist/server/app-rsc-response-finalizer.js +1 -1
  97. package/dist/server/app-rsc-route-matching.d.ts +20 -1
  98. package/dist/server/app-rsc-route-matching.js +29 -4
  99. package/dist/server/app-server-action-execution.d.ts +22 -1
  100. package/dist/server/app-server-action-execution.js +73 -12
  101. package/dist/server/app-ssr-entry.d.ts +6 -0
  102. package/dist/server/app-ssr-entry.js +19 -3
  103. package/dist/server/app-ssr-stream.js +9 -1
  104. package/dist/server/dev-lockfile.js +2 -1
  105. package/dist/server/dev-server.d.ts +1 -1
  106. package/dist/server/dev-server.js +97 -43
  107. package/dist/server/headers.d.ts +8 -1
  108. package/dist/server/headers.js +8 -1
  109. package/dist/server/instrumentation-runtime.d.ts +6 -0
  110. package/dist/server/instrumentation-runtime.js +8 -0
  111. package/dist/server/isr-cache.d.ts +37 -1
  112. package/dist/server/isr-cache.js +85 -1
  113. package/dist/server/isr-decision.d.ts +79 -0
  114. package/dist/server/isr-decision.js +70 -0
  115. package/dist/server/metadata-route-response.js +5 -3
  116. package/dist/server/middleware-runtime.d.ts +13 -0
  117. package/dist/server/middleware-runtime.js +11 -7
  118. package/dist/server/middleware.js +1 -0
  119. package/dist/server/navigation-planner.d.ts +62 -1
  120. package/dist/server/navigation-planner.js +193 -3
  121. package/dist/server/navigation-trace.d.ts +12 -2
  122. package/dist/server/navigation-trace.js +11 -1
  123. package/dist/server/normalize-path.d.ts +0 -8
  124. package/dist/server/normalize-path.js +3 -1
  125. package/dist/server/otel-tracer-extension.d.ts +45 -0
  126. package/dist/server/otel-tracer-extension.js +89 -0
  127. package/dist/server/pages-api-route.d.ts +14 -3
  128. package/dist/server/pages-api-route.js +6 -1
  129. package/dist/server/pages-asset-tags.d.ts +15 -4
  130. package/dist/server/pages-asset-tags.js +18 -12
  131. package/dist/server/pages-data-route.js +5 -1
  132. package/dist/server/pages-node-compat.d.ts +5 -11
  133. package/dist/server/pages-node-compat.js +175 -118
  134. package/dist/server/pages-page-data.d.ts +38 -7
  135. package/dist/server/pages-page-data.js +64 -18
  136. package/dist/server/pages-page-handler.d.ts +10 -2
  137. package/dist/server/pages-page-handler.js +49 -20
  138. package/dist/server/pages-page-response.d.ts +55 -2
  139. package/dist/server/pages-page-response.js +74 -6
  140. package/dist/server/pages-readiness.d.ts +36 -0
  141. package/dist/server/pages-readiness.js +21 -0
  142. package/dist/server/pages-request-pipeline.d.ts +113 -0
  143. package/dist/server/pages-request-pipeline.js +230 -0
  144. package/dist/server/pages-revalidate.d.ts +15 -0
  145. package/dist/server/pages-revalidate.js +19 -0
  146. package/dist/server/prod-server.d.ts +45 -3
  147. package/dist/server/prod-server.js +182 -234
  148. package/dist/server/socket-error-backstop.d.ts +19 -1
  149. package/dist/server/socket-error-backstop.js +77 -4
  150. package/dist/shims/app-router-scroll.js +22 -4
  151. package/dist/shims/cache-runtime.js +39 -2
  152. package/dist/shims/dynamic-preload-chunks.d.ts +8 -0
  153. package/dist/shims/dynamic-preload-chunks.js +77 -0
  154. package/dist/shims/dynamic.d.ts +4 -0
  155. package/dist/shims/dynamic.js +4 -2
  156. package/dist/shims/error-boundary.d.ts +17 -7
  157. package/dist/shims/error-boundary.js +8 -1
  158. package/dist/shims/error.js +37 -11
  159. package/dist/shims/fetch-cache.d.ts +22 -1
  160. package/dist/shims/fetch-cache.js +28 -1
  161. package/dist/shims/hash-scroll.d.ts +1 -0
  162. package/dist/shims/hash-scroll.js +3 -1
  163. package/dist/shims/head.js +6 -1
  164. package/dist/shims/headers.d.ts +16 -2
  165. package/dist/shims/headers.js +37 -1
  166. package/dist/shims/image-config.js +7 -1
  167. package/dist/shims/internal/app-route-detection.d.ts +6 -3
  168. package/dist/shims/internal/app-route-detection.js +10 -6
  169. package/dist/shims/internal/app-router-context.d.ts +5 -0
  170. package/dist/shims/internal/link-status-registry.d.ts +43 -0
  171. package/dist/shims/internal/link-status-registry.js +42 -0
  172. package/dist/shims/internal/route-pattern-for-warning.d.ts +27 -0
  173. package/dist/shims/internal/route-pattern-for-warning.js +40 -0
  174. package/dist/shims/internal/utils.d.ts +1 -0
  175. package/dist/shims/link.js +20 -6
  176. package/dist/shims/metadata.d.ts +6 -2
  177. package/dist/shims/metadata.js +32 -14
  178. package/dist/shims/navigation.d.ts +9 -18
  179. package/dist/shims/navigation.js +96 -23
  180. package/dist/shims/router-state.d.ts +1 -0
  181. package/dist/shims/router-state.js +2 -0
  182. package/dist/shims/router.d.ts +6 -3
  183. package/dist/shims/router.js +156 -22
  184. package/dist/shims/script-nonce-context.d.ts +1 -1
  185. package/dist/shims/script-nonce-context.js +11 -3
  186. package/dist/shims/server.d.ts +17 -1
  187. package/dist/shims/server.js +31 -6
  188. package/dist/shims/slot.js +1 -1
  189. package/dist/shims/unified-request-context.js +1 -0
  190. package/dist/typegen.js +1 -0
  191. package/dist/utils/client-build-manifest.d.ts +8 -1
  192. package/dist/utils/client-build-manifest.js +41 -6
  193. package/dist/utils/client-entry-manifest.d.ts +11 -0
  194. package/dist/utils/client-entry-manifest.js +29 -0
  195. package/dist/utils/client-runtime-metadata.d.ts +45 -0
  196. package/dist/utils/client-runtime-metadata.js +63 -0
  197. package/dist/utils/hash.d.ts +17 -1
  198. package/dist/utils/hash.js +36 -1
  199. package/dist/utils/lazy-chunks.d.ts +27 -1
  200. package/dist/utils/lazy-chunks.js +65 -1
  201. package/dist/utils/manifest-paths.d.ts +20 -2
  202. package/dist/utils/manifest-paths.js +38 -3
  203. package/dist/utils/path.d.ts +2 -1
  204. package/dist/utils/path.js +5 -1
  205. package/package.json +6 -2
@@ -1,3 +1,4 @@
1
+ import { normalizePathSeparators } from "../utils/path.js";
1
2
  import { compareRoutes, decodeRouteSegment, isInvisibleSegment } from "./utils.js";
2
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
3
4
  import { validateRoutePatterns } from "./route-validation.js";
@@ -33,6 +34,10 @@ function createAppRouteGraphSlotId(slotName, ownerTreePath) {
33
34
  function createAppRouteGraphDefaultId(slotId) {
34
35
  return `default:${slotId}`;
35
36
  }
37
+ const SIBLING_INTERCEPT_SLOT_NAME = "__vinext_sibling_intercept";
38
+ function createAppRouteGraphSiblingInterceptSlotId(sourcePattern) {
39
+ return createAppRouteGraphSlotId(SIBLING_INTERCEPT_SLOT_NAME, sourcePattern);
40
+ }
36
41
  function createAppRouteGraphInterceptionId(slotId, sourcePattern, targetPattern) {
37
42
  return `interception:${slotId}:${sourcePattern}->${targetPattern}`;
38
43
  }
@@ -182,6 +187,21 @@ function createStaticSegmentGraph(routes) {
182
187
  slot
183
188
  });
184
189
  }
190
+ for (const ir of route.siblingIntercepts) {
191
+ if (!ir.slotId) continue;
192
+ const id = createAppRouteGraphInterceptionId(ir.slotId, ir.sourceMatchPattern, ir.targetPattern);
193
+ interceptions.set(id, {
194
+ id,
195
+ sourcePattern: ir.sourceMatchPattern,
196
+ sourcePatternParts: splitRouteManifestPatternParts(ir.sourceMatchPattern),
197
+ targetPattern: ir.targetPattern,
198
+ targetPatternParts: splitRouteManifestPatternParts(ir.targetPattern),
199
+ slotId: ir.slotId,
200
+ ownerLayoutId: null,
201
+ interceptingRouteId: routeIdByPattern.get(ir.sourceMatchPattern) ?? null,
202
+ targetRouteId: routeIdByPattern.get(ir.targetPattern) ?? null
203
+ });
204
+ }
185
205
  }
186
206
  return {
187
207
  routes: routeEntries,
@@ -353,9 +373,10 @@ async function buildAppRouteGraph(appDir, matcher) {
353
373
  }
354
374
  const slotSubRoutes = discoverSlotSubRoutes(routes, matcher, ghostParentRoutes);
355
375
  routes.push(...slotSubRoutes);
376
+ discoverSiblingInterceptingRoutes(routes, appDir, matcher);
356
377
  validatePageRouteConflicts(routes, appDir);
357
378
  validateRoutePatterns(routes.map((route) => route.pattern));
358
- validateRoutePatterns([...new Set(routes.flatMap((route) => route.parallelSlots.flatMap((slot) => slot.interceptingRoutes.map((intercept) => intercept.targetPattern))))]);
379
+ validateRoutePatterns([...new Set(routes.flatMap((route) => [...route.parallelSlots.flatMap((slot) => slot.interceptingRoutes.map((intercept) => intercept.targetPattern)), ...route.siblingIntercepts.map((intercept) => intercept.targetPattern)]))]);
359
380
  routes.sort(compareRoutes);
360
381
  return {
361
382
  routes,
@@ -456,6 +477,8 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
456
477
  if (subPathMap.size === 0) continue;
457
478
  const childrenDefault = findFile(parentPageDir, "default", matcher);
458
479
  if (parentRoute.pagePath && !childrenDefault) continue;
480
+ const childrenCatchAll = childrenDefault ? null : findCatchAllPage(parentPageDir, matcher);
481
+ const childrenFallback = childrenDefault ?? childrenCatchAll;
459
482
  for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {
460
483
  const { urlSegments: urlParts, params: subParams, isDynamic: subIsDynamic } = convertedSubRoute;
461
484
  const subUrlPath = urlParts.join("/");
@@ -463,7 +486,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
463
486
  const existingRoute = routesByPattern.get(pattern);
464
487
  if (existingRoute) {
465
488
  if (existingRoute.routePath && !existingRoute.pagePath) throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
466
- applySlotSubPages(existingRoute, slotPages, rawSegments);
489
+ if (urlParts.length > 0) applySlotSubPages(existingRoute, slotPages, rawSegments);
467
490
  continue;
468
491
  }
469
492
  const syntheticParts = [...parentRoute.patternParts, ...urlParts];
@@ -479,7 +502,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
479
502
  const newRoute = {
480
503
  ids: createAppRouteSemanticIds({
481
504
  pattern,
482
- pagePath: childrenDefault,
505
+ pagePath: childrenFallback,
483
506
  routePath: null,
484
507
  routeSegments: [...parentRoute.routeSegments, ...rawSegments],
485
508
  layoutTreePositions: parentRoute.layoutTreePositions,
@@ -487,7 +510,7 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
487
510
  slots: subSlots
488
511
  }),
489
512
  pattern,
490
- pagePath: childrenDefault,
513
+ pagePath: childrenFallback,
491
514
  routePath: null,
492
515
  layouts: parentRoute.layouts,
493
516
  templates: parentRoute.templates,
@@ -507,7 +530,8 @@ function discoverSlotSubRoutes(routes, matcher, ghostParents = []) {
507
530
  isDynamic: parentRoute.isDynamic || subIsDynamic,
508
531
  params: [...parentRoute.params, ...subParams],
509
532
  rootParamNames: parentRoute.rootParamNames,
510
- patternParts: [...parentRoute.patternParts, ...urlParts]
533
+ patternParts: [...parentRoute.patternParts, ...urlParts],
534
+ siblingIntercepts: []
511
535
  };
512
536
  syntheticRoutes.push(newRoute);
513
537
  routesByPattern.set(pattern, newRoute);
@@ -549,6 +573,34 @@ function findSlotSubPages(slotDir, matcher) {
549
573
  return results;
550
574
  }
551
575
  /**
576
+ * Find a sibling catch-all page directly under `dir`, i.e. a `[...slug]` or
577
+ * `[[...slug]]` directory that contains a `page` file. Returns the absolute
578
+ * page path, or null when no catch-all sibling exists.
579
+ *
580
+ * Used as the children fallback for slot-only sub-routes (an explicit `@slot`
581
+ * sub-page with no corresponding children page or `default.tsx`): Next.js
582
+ * serves the children prop from the nearest catch-all, so `/baz` renders
583
+ * `[...catchAll]/page.tsx` for children while `@slot/baz/page.tsx` fills the
584
+ * slot. Optional catch-alls (`[[...slug]]`) qualify because they also match a
585
+ * single extra segment.
586
+ */
587
+ function findCatchAllPage(dir, matcher) {
588
+ let entries;
589
+ try {
590
+ entries = fs.readdirSync(dir, { withFileTypes: true });
591
+ } catch {
592
+ return null;
593
+ }
594
+ for (const entry of entries) {
595
+ if (!entry.isDirectory()) continue;
596
+ const name = entry.name;
597
+ if (!(name.startsWith("[...") && name.endsWith("]") || name.startsWith("[[...") && name.endsWith("]]"))) continue;
598
+ const page = findFile(path.join(dir, name), "page", matcher);
599
+ if (page) return page;
600
+ }
601
+ return null;
602
+ }
603
+ /**
552
604
  * Convert a file path relative to app/ into an AppRoute.
553
605
  */
554
606
  function fileToAppRoute(file, appDir, type, matcher) {
@@ -620,7 +672,8 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
620
672
  isDynamic,
621
673
  params,
622
674
  rootParamNames: computeRootParamNames(segments, layoutTreePositions),
623
- patternParts: urlSegments
675
+ patternParts: urlSegments,
676
+ siblingIntercepts: []
624
677
  };
625
678
  }
626
679
  function dynamicParamNameFromSegment(segment) {
@@ -974,6 +1027,34 @@ function patternsStructurallyEquivalent(a, b) {
974
1027
  return true;
975
1028
  }
976
1029
  /**
1030
+ * Find a page file at the root URL level of a parallel slot directory, including
1031
+ * through transparent route-group subdirectories (e.g. `@slot/(group)/page.tsx`
1032
+ * is equivalent to `@slot/page.tsx` since `(group)` is invisible in the URL).
1033
+ *
1034
+ * Returns the absolute page path, or null if no root-level page is found.
1035
+ *
1036
+ * Only descends into route-group directories (those whose name starts with `(`
1037
+ * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1038
+ * are not transparent and are therefore not searched.
1039
+ */
1040
+ function findSlotRootPage(slotDir, matcher) {
1041
+ const directPage = findFile(slotDir, "page", matcher);
1042
+ if (directPage) return directPage;
1043
+ let entries;
1044
+ try {
1045
+ entries = fs.readdirSync(slotDir, { withFileTypes: true });
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ for (const entry of entries) {
1050
+ if (!entry.isDirectory()) continue;
1051
+ if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1052
+ const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1053
+ if (found) return found;
1054
+ }
1055
+ return null;
1056
+ }
1057
+ /**
977
1058
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
978
1059
  * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.
979
1060
  */
@@ -986,7 +1067,7 @@ function discoverParallelSlots(dir, appDir, matcher) {
986
1067
  if (entry.name === "@children") continue;
987
1068
  const slotName = entry.name.slice(1);
988
1069
  const slotDir = path.join(dir, entry.name);
989
- const pagePath = findFile(slotDir, "page", matcher);
1070
+ const pagePath = findSlotRootPage(slotDir, matcher);
990
1071
  const defaultPath = findFile(slotDir, "default", matcher);
991
1072
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
992
1073
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
@@ -1062,6 +1143,96 @@ function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
1062
1143
  return results;
1063
1144
  }
1064
1145
  /**
1146
+ * Discover sibling-style interception markers — interception marker directories
1147
+ * (e.g. `(..)showcase`, `(..)(..)hoge`) that are NOT wrapped inside an `@slot`
1148
+ * directory. Mutates each matching route's `siblingIntercepts` array.
1149
+ *
1150
+ * Sibling intercepts use the same conventions and target-computation logic as
1151
+ * slot intercepts, but their intercepting page replaces the full page response
1152
+ * (not a slot) during soft navigation.
1153
+ */
1154
+ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1155
+ const routesByDir = /* @__PURE__ */ new Map();
1156
+ for (const route of routes) {
1157
+ const filePath = route.pagePath ?? route.routePath;
1158
+ if (!filePath) continue;
1159
+ const routeDir = normalizePathSeparators(path.dirname(filePath));
1160
+ if (!routesByDir.has(routeDir)) routesByDir.set(routeDir, route);
1161
+ }
1162
+ function walk(dir) {
1163
+ let entries;
1164
+ try {
1165
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1166
+ } catch {
1167
+ return;
1168
+ }
1169
+ for (const entry of entries) {
1170
+ if (!entry.isDirectory()) continue;
1171
+ if (entry.name.startsWith("_")) continue;
1172
+ if (entry.name.startsWith("@")) continue;
1173
+ const childDir = path.join(dir, entry.name);
1174
+ const marker = matchInterceptConvention(entry.name);
1175
+ if (marker) {
1176
+ const restOfName = entry.name.slice(marker.prefix.length);
1177
+ const parentDir = dir;
1178
+ const results = [];
1179
+ collectInterceptingPages(childDir, childDir, marker.convention, restOfName, parentDir, appDir, parentDir, results, matcher);
1180
+ for (const ir of results) {
1181
+ ir.slotId = createAppRouteGraphSiblingInterceptSlotId(ir.sourceMatchPattern);
1182
+ const owner = findOwnerRouteForDir(parentDir, appDir, routes, routesByDir);
1183
+ if (owner) owner.siblingIntercepts.push(ir);
1184
+ }
1185
+ continue;
1186
+ }
1187
+ walk(childDir);
1188
+ }
1189
+ }
1190
+ walk(appDir);
1191
+ }
1192
+ /**
1193
+ * Find the best route to attach a sibling intercept to, given the directory
1194
+ * that contains the interception marker.
1195
+ *
1196
+ * 1. Exact hit: a route whose page/handler lives directly in `dir`.
1197
+ * 2. Subtree hit: shallowest route whose page lives anywhere under `dir`
1198
+ * (handles catch-all routes like `/templates/:catchAll+`).
1199
+ * 3. Ancestor walk: walk up the directory tree toward `appDir` looking for
1200
+ * any of the above. This handles the case where the marker directory has
1201
+ * no sibling pages at all (e.g. `deep/path/(...)target` with no
1202
+ * `deep/path/page.tsx`).
1203
+ *
1204
+ * All comparisons happen in forward-slash space: `appDir` is forward-slash
1205
+ * (normalized once in the config hook), but `dir` and route file paths
1206
+ * descend through native `path.join`/`path.dirname`, which reintroduce
1207
+ * backslashes on Windows. Without normalizing, the `current === appDir`
1208
+ * termination never fires there and the walk overshoots the app root.
1209
+ * `routesByDir` keys must be forward-slash dirnames of the route file paths.
1210
+ *
1211
+ * Exported for tests.
1212
+ */
1213
+ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1214
+ const appRoot = normalizePathSeparators(appDir);
1215
+ let current = normalizePathSeparators(dir);
1216
+ while (true) {
1217
+ const exact = routesByDir.get(current);
1218
+ if (exact) return exact;
1219
+ const currentWithSep = current + "/";
1220
+ let best = null;
1221
+ for (const route of routes) {
1222
+ const filePath = route.pagePath ?? route.routePath;
1223
+ if (!filePath) continue;
1224
+ if (!normalizePathSeparators(filePath).startsWith(currentWithSep)) continue;
1225
+ if (!best || route.patternParts.length < best.patternParts.length) best = route;
1226
+ }
1227
+ if (best) return best;
1228
+ if (current === appRoot) break;
1229
+ const parent = path.dirname(current);
1230
+ if (parent === current) break;
1231
+ current = parent;
1232
+ }
1233
+ return null;
1234
+ }
1235
+ /**
1065
1236
  * Recursively scan a directory tree for page.tsx files that are inside
1066
1237
  * intercepting route directories.
1067
1238
  */
@@ -1314,4 +1485,4 @@ function computeAppRouteStaticSiblings(allRoutes, matchedRoute) {
1314
1485
  return Array.from(siblings);
1315
1486
  }
1316
1487
  //#endregion
1317
- export { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, isInvisibleSegment };
1488
+ export { buildAppRouteGraph, computeAppRouteStaticSiblings, computeRootParamNames, convertSegmentsToRouteParts, findOwnerRouteForDir, isInvisibleSegment };
@@ -69,7 +69,7 @@ function findFileWithExtensions(basePath, matcher) {
69
69
  */
70
70
  function findFileWithExts(dir, name, matcher) {
71
71
  for (const ext of matcher.dottedExtensions) {
72
- const filePath = path.join(dir, name + ext);
72
+ const filePath = path.posix.join(dir, name + ext);
73
73
  if (existsSync(filePath)) return filePath;
74
74
  }
75
75
  return null;
@@ -5,6 +5,7 @@ declare function routePattern(pathname: string): string;
5
5
  declare function fillRoutePatternSegments(pathname: string, params: RoutePatternParams): string | null;
6
6
  declare function matchRoutePattern(urlParts: readonly string[], patternParts: readonly string[]): RoutePatternParams | null;
7
7
  declare function matchRoutePatternPrefix(pathParts: readonly string[], patternParts: readonly string[]): boolean;
8
+ declare function matchRoutePatternWithOptionalDynamicSegments(pathParts: readonly string[], patternParts: readonly string[]): boolean;
8
9
  /**
9
10
  * A single entry from `getStaticPaths().paths`.
10
11
  *
@@ -60,4 +61,4 @@ declare function normalizeStaticPathname(pathname: string): string;
60
61
  */
61
62
  declare function normalizeStaticPathsEntry(entry: StaticPathsEntry, routePattern: string): NormalizedStaticPathsEntry;
62
63
  //#endregion
63
- export { RoutePatternParams, StaticPathsEntry, fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
64
+ export { RoutePatternParams, StaticPathsEntry, fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
@@ -104,6 +104,21 @@ function matchRoutePatternPrefix(pathParts, patternParts) {
104
104
  }
105
105
  return true;
106
106
  }
107
+ function matchRoutePatternWithOptionalDynamicSegments(pathParts, patternParts) {
108
+ function matchFrom(pathIndex, patternIndex) {
109
+ if (patternIndex === patternParts.length) return pathIndex === pathParts.length;
110
+ const patternPart = patternParts[patternIndex];
111
+ if (patternPart.startsWith(":") && (patternPart.endsWith("+") || patternPart.endsWith("*"))) {
112
+ const minLength = patternPart.endsWith("+") ? 1 : 0;
113
+ for (let endIndex = pathIndex + minLength; endIndex <= pathParts.length; endIndex++) if (matchFrom(endIndex, patternIndex + 1)) return true;
114
+ return false;
115
+ }
116
+ if (patternPart.startsWith(":")) return matchFrom(pathIndex, patternIndex + 1) || pathIndex < pathParts.length && matchFrom(pathIndex + 1, patternIndex + 1);
117
+ if (pathIndex >= pathParts.length || pathParts[pathIndex] !== patternPart) return false;
118
+ return matchFrom(pathIndex + 1, patternIndex + 1);
119
+ }
120
+ return matchFrom(0, 0);
121
+ }
107
122
  /**
108
123
  * Strip query string and a single trailing slash from a pathname.
109
124
  *
@@ -147,4 +162,4 @@ function normalizeStaticPathsEntry(entry, routePattern) {
147
162
  return { params };
148
163
  }
149
164
  //#endregion
150
- export { fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
165
+ export { fillRoutePatternSegments, matchRoutePattern, matchRoutePatternPrefix, matchRoutePatternWithOptionalDynamicSegments, normalizeStaticPathname, normalizeStaticPathsEntry, routePattern, routePatternParts };
@@ -7,6 +7,7 @@ import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-medi
7
7
  import { isEdgeApiRuntime } from "./edge-api-runtime.js";
8
8
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT, resolveBodyParserConfig } from "./pages-body-parser-config.js";
9
9
  import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
10
+ import { performOnDemandRevalidate } from "./pages-revalidate.js";
10
11
  import { decode } from "node:querystring";
11
12
  import { Buffer } from "node:buffer";
12
13
  //#region src/server/api-handler.ts
@@ -198,6 +199,9 @@ function enhanceApiObjects(req, res, query, body) {
198
199
  if (typeof statusOrUrl === "string") this.writeHead(307, { Location: statusOrUrl });
199
200
  else this.writeHead(statusOrUrl, { Location: url ?? "" });
200
201
  this.end();
202
+ },
203
+ async revalidate(urlPath, opts) {
204
+ await performOnDemandRevalidate(req, urlPath, opts);
201
205
  }
202
206
  })
203
207
  };