vinext 0.0.41 → 0.0.43
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.
- package/README.md +0 -1
- package/dist/build/client-build-config.d.ts +119 -0
- package/dist/build/client-build-config.js +149 -0
- package/dist/build/client-build-config.js.map +1 -0
- package/dist/build/layout-classification-types.d.ts +62 -0
- package/dist/build/layout-classification-types.js +1 -0
- package/dist/build/layout-classification.d.ts +60 -0
- package/dist/build/layout-classification.js +98 -0
- package/dist/build/layout-classification.js.map +1 -0
- package/dist/build/report.d.ts +15 -1
- package/dist/build/report.js +50 -1
- package/dist/build/report.js.map +1 -1
- package/dist/build/route-classification-manifest.d.ts +53 -0
- package/dist/build/route-classification-manifest.js +145 -0
- package/dist/build/route-classification-manifest.js.map +1 -0
- package/dist/build/run-prerender.js +1 -1
- package/dist/build/ssr-manifest.d.ts +19 -0
- package/dist/build/ssr-manifest.js +71 -0
- package/dist/build/ssr-manifest.js.map +1 -0
- package/dist/check.js +2 -2
- package/dist/check.js.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/client/entry.js +1 -1
- package/dist/config/config-matchers.js +1 -0
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +315 -101
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/index.d.ts +1 -169
- package/dist/index.js +112 -432
- package/dist/index.js.map +1 -1
- package/dist/plugins/fonts.d.ts +49 -1
- package/dist/plugins/fonts.js +96 -3
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/plugins/postcss.d.ts +27 -0
- package/dist/plugins/postcss.js +94 -0
- package/dist/plugins/postcss.js.map +1 -0
- package/dist/plugins/strip-server-exports.d.ts +14 -0
- package/dist/plugins/strip-server-exports.js +73 -0
- package/dist/plugins/strip-server-exports.js.map +1 -0
- package/dist/routing/app-router.d.ts +6 -4
- package/dist/routing/app-router.js +44 -25
- package/dist/routing/app-router.js.map +1 -1
- package/dist/server/app-browser-entry.js +307 -100
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-error.d.ts +8 -0
- package/dist/server/app-browser-error.js +9 -0
- package/dist/server/app-browser-error.js.map +1 -0
- package/dist/server/app-browser-state.d.ts +93 -0
- package/dist/server/app-browser-state.js +132 -0
- package/dist/server/app-browser-state.js.map +1 -0
- package/dist/server/app-elements.d.ts +92 -0
- package/dist/server/app-elements.js +122 -0
- package/dist/server/app-elements.js.map +1 -0
- package/dist/server/app-page-boundary-render.d.ts +2 -1
- package/dist/server/app-page-boundary-render.js +40 -1
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +6 -3
- package/dist/server/app-page-cache.js +14 -8
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +36 -3
- package/dist/server/app-page-execution.js +50 -10
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +10 -4
- package/dist/server/app-page-probe.js +24 -15
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render.d.ts +7 -4
- package/dist/server/app-page-render.js +13 -4
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +52 -4
- package/dist/server/app-page-request.js +86 -16
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.d.ts +1 -0
- package/dist/server/app-page-response.js +1 -0
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +22 -8
- package/dist/server/app-page-route-wiring.js +219 -83
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-render-dependency.d.ts +13 -0
- package/dist/server/app-render-dependency.js +35 -0
- package/dist/server/app-render-dependency.js.map +1 -0
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-policy.js +5 -3
- package/dist/server/app-route-handler-policy.js.map +1 -1
- package/dist/server/app-route-handler-response.js +2 -0
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-route-handler-runtime.d.ts +1 -0
- package/dist/server/app-route-handler-runtime.js +26 -1
- package/dist/server/app-route-handler-runtime.js.map +1 -1
- package/dist/server/app-ssr-entry.js +6 -2
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/dev-server.js +2 -4
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/middleware.js +1 -5
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/prod-server.d.ts +3 -3
- package/dist/server/prod-server.js +1 -1
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/request-pipeline.d.ts +2 -1
- package/dist/server/request-pipeline.js +34 -5
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/shims/cache-runtime.d.ts +1 -0
- package/dist/shims/cache-runtime.js +0 -5
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/cache.d.ts +1 -0
- package/dist/shims/cache.js +1 -8
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/client-hook-error.d.ts +14 -0
- package/dist/shims/client-hook-error.js +19 -0
- package/dist/shims/client-hook-error.js.map +1 -0
- package/dist/shims/constants.d.ts +3 -3
- package/dist/shims/constants.js +3 -3
- package/dist/shims/constants.js.map +1 -1
- package/dist/shims/document.d.ts +6 -6
- package/dist/shims/error-boundary.d.ts +4 -4
- package/dist/shims/error-boundary.js +1 -1
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/form.d.ts +3 -3
- package/dist/shims/head-state.d.ts +1 -0
- package/dist/shims/head-state.js +0 -5
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/headers.d.ts +11 -0
- package/dist/shims/headers.js +13 -10
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/i18n-state.d.ts +1 -0
- package/dist/shims/i18n-state.js +0 -4
- package/dist/shims/i18n-state.js.map +1 -1
- package/dist/shims/internal/app-router-context.d.ts +6 -6
- package/dist/shims/internal/router-context.d.ts +2 -2
- package/dist/shims/layout-segment-context.d.ts +2 -2
- package/dist/shims/link.js +19 -11
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +3 -3
- package/dist/shims/navigation-state.d.ts +2 -0
- package/dist/shims/navigation-state.js +0 -13
- package/dist/shims/navigation-state.js.map +1 -1
- package/dist/shims/navigation.d.ts +56 -9
- package/dist/shims/navigation.js +112 -28
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/navigation.react-server.d.ts +14 -0
- package/dist/shims/navigation.react-server.js +29 -0
- package/dist/shims/navigation.react-server.js.map +1 -0
- package/dist/shims/request-context.d.ts +1 -0
- package/dist/shims/request-context.js +0 -9
- package/dist/shims/request-context.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +1 -1
- package/dist/shims/router-state.d.ts +1 -0
- package/dist/shims/router-state.js +0 -5
- package/dist/shims/router-state.js.map +1 -1
- package/dist/shims/slot.d.ts +11 -7
- package/dist/shims/slot.js +28 -19
- package/dist/shims/slot.js.map +1 -1
- package/dist/shims/unified-request-context.d.ts +2 -0
- package/dist/shims/unified-request-context.js +0 -14
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/utils/mdx-scan.d.ts +10 -0
- package/dist/utils/mdx-scan.js +36 -0
- package/dist/utils/mdx-scan.js.map +1 -0
- package/dist/utils/public-routes.d.ts +5 -0
- package/dist/utils/public-routes.js +50 -0
- package/dist/utils/public-routes.js.map +1 -0
- package/package.json +3 -3
- package/dist/plugins/fix-use-server-closure-collision.d.ts +0 -29
- package/dist/plugins/fix-use-server-closure-collision.js +0 -204
- package/dist/plugins/fix-use-server-closure-collision.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { parseAst } from "vite";
|
|
2
|
+
import MagicString from "magic-string";
|
|
3
|
+
//#region src/plugins/strip-server-exports.ts
|
|
4
|
+
/**
|
|
5
|
+
* Strip server-only data-fetching exports (getServerSideProps,
|
|
6
|
+
* getStaticProps, getStaticPaths) from page modules for the client
|
|
7
|
+
* bundle. Uses Vite's parseAst (Rollup/acorn) for correct handling
|
|
8
|
+
* of all export patterns including function expressions, arrow
|
|
9
|
+
* functions with TS return types, and re-exports.
|
|
10
|
+
*
|
|
11
|
+
* Modeled after Next.js's SWC `next-ssg-transform`.
|
|
12
|
+
*/
|
|
13
|
+
function stripServerExports(code) {
|
|
14
|
+
const SERVER_EXPORTS = new Set([
|
|
15
|
+
"getServerSideProps",
|
|
16
|
+
"getStaticProps",
|
|
17
|
+
"getStaticPaths"
|
|
18
|
+
]);
|
|
19
|
+
if (![...SERVER_EXPORTS].some((name) => code.includes(name))) return null;
|
|
20
|
+
let ast;
|
|
21
|
+
try {
|
|
22
|
+
ast = parseAst(code);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const s = new MagicString(code);
|
|
27
|
+
let changed = false;
|
|
28
|
+
for (const node of ast.body) {
|
|
29
|
+
if (node.type !== "ExportNamedDeclaration") continue;
|
|
30
|
+
if (node.declaration) {
|
|
31
|
+
const decl = node.declaration;
|
|
32
|
+
if (decl.type === "FunctionDeclaration" && decl.id && SERVER_EXPORTS.has(decl.id.name)) {
|
|
33
|
+
s.overwrite(node.start, node.end, `export function ${decl.id.name}() { return { props: {} }; }`);
|
|
34
|
+
changed = true;
|
|
35
|
+
} else if (decl.type === "VariableDeclaration") {
|
|
36
|
+
for (const declarator of decl.declarations) if (declarator.id?.type === "Identifier" && SERVER_EXPORTS.has(declarator.id.name)) {
|
|
37
|
+
s.overwrite(node.start, node.end, `export const ${declarator.id.name} = undefined;`);
|
|
38
|
+
changed = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (node.specifiers && node.specifiers.length > 0 && !node.source) {
|
|
44
|
+
const kept = [];
|
|
45
|
+
const stripped = [];
|
|
46
|
+
for (const spec of node.specifiers) {
|
|
47
|
+
const exportedName = spec.exported?.name ?? spec.exported?.value;
|
|
48
|
+
if (SERVER_EXPORTS.has(exportedName)) stripped.push(exportedName);
|
|
49
|
+
else kept.push(spec);
|
|
50
|
+
}
|
|
51
|
+
if (stripped.length > 0) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (kept.length > 0) {
|
|
54
|
+
const keptStr = kept.map((sp) => {
|
|
55
|
+
const local = sp.local.name;
|
|
56
|
+
const exported = sp.exported?.name ?? sp.exported?.value;
|
|
57
|
+
return local === exported ? local : `${local} as ${exported}`;
|
|
58
|
+
}).join(", ");
|
|
59
|
+
parts.push(`export { ${keptStr} };`);
|
|
60
|
+
}
|
|
61
|
+
for (const name of stripped) parts.push(`export const ${name} = undefined;`);
|
|
62
|
+
s.overwrite(node.start, node.end, parts.join("\n"));
|
|
63
|
+
changed = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!changed) return null;
|
|
68
|
+
return s.toString();
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { stripServerExports };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=strip-server-exports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strip-server-exports.js","names":[],"sources":["../../src/plugins/strip-server-exports.ts"],"sourcesContent":["import { parseAst } from \"vite\";\nimport MagicString from \"magic-string\";\n\ntype ASTNode = ReturnType<typeof parseAst>[\"body\"][number][\"parent\"];\n\n/**\n * Strip server-only data-fetching exports (getServerSideProps,\n * getStaticProps, getStaticPaths) from page modules for the client\n * bundle. Uses Vite's parseAst (Rollup/acorn) for correct handling\n * of all export patterns including function expressions, arrow\n * functions with TS return types, and re-exports.\n *\n * Modeled after Next.js's SWC `next-ssg-transform`.\n */\nexport function stripServerExports(code: string): string | null {\n const SERVER_EXPORTS = new Set([\"getServerSideProps\", \"getStaticProps\", \"getStaticPaths\"]);\n if (![...SERVER_EXPORTS].some((name) => code.includes(name))) return null;\n\n let ast: ReturnType<typeof parseAst>;\n try {\n ast = parseAst(code);\n } catch {\n // If parsing fails (shouldn't happen post-JSX/TS transform), bail out\n return null;\n }\n\n const s = new MagicString(code);\n let changed = false;\n\n for (const node of ast.body) {\n if (node.type !== \"ExportNamedDeclaration\") continue;\n\n // Case 1: export function name() {} / export async function name() {}\n // Case 2: export const/let/var name = ...\n if (node.declaration) {\n const decl = node.declaration;\n if (decl.type === \"FunctionDeclaration\" && decl.id && SERVER_EXPORTS.has(decl.id.name)) {\n s.overwrite(\n node.start,\n node.end,\n `export function ${decl.id.name}() { return { props: {} }; }`,\n );\n changed = true;\n } else if (decl.type === \"VariableDeclaration\") {\n for (const declarator of decl.declarations) {\n if (declarator.id?.type === \"Identifier\" && SERVER_EXPORTS.has(declarator.id.name)) {\n s.overwrite(node.start, node.end, `export const ${declarator.id.name} = undefined;`);\n changed = true;\n }\n }\n }\n continue;\n }\n\n // Case 3: export { getServerSideProps } or export { getServerSideProps as gSSP }\n if (node.specifiers && node.specifiers.length > 0 && !node.source) {\n const kept: Extract<ASTNode, { type: \"ExportSpecifier\" }>[] = [];\n const stripped: string[] = [];\n for (const spec of node.specifiers) {\n // spec.local.name is the binding name, spec.exported.name is the export name\n // oxlint-disable-next-line typescript/no-explicit-any\n const exportedName = (spec.exported as any)?.name ?? (spec.exported as any)?.value;\n if (SERVER_EXPORTS.has(exportedName)) {\n stripped.push(exportedName);\n } else {\n kept.push(spec);\n }\n }\n if (stripped.length > 0) {\n // Build replacement: keep non-server specifiers, add stubs for stripped ones\n const parts: string[] = [];\n if (kept.length > 0) {\n const keptStr = kept\n // oxlint-disable-next-line typescript/no-explicit-any\n .map((sp: any) => {\n const local = sp.local.name;\n const exported = sp.exported?.name ?? sp.exported?.value;\n return local === exported ? local : `${local} as ${exported}`;\n })\n .join(\", \");\n parts.push(`export { ${keptStr} };`);\n }\n for (const name of stripped) {\n parts.push(`export const ${name} = undefined;`);\n }\n s.overwrite(node.start, node.end, parts.join(\"\\n\"));\n changed = true;\n }\n }\n }\n\n if (!changed) return null;\n return s.toString();\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,mBAAmB,MAA6B;CAC9D,MAAM,iBAAiB,IAAI,IAAI;EAAC;EAAsB;EAAkB;EAAiB,CAAC;AAC1F,KAAI,CAAC,CAAC,GAAG,eAAe,CAAC,MAAM,SAAS,KAAK,SAAS,KAAK,CAAC,CAAE,QAAO;CAErE,IAAI;AACJ,KAAI;AACF,QAAM,SAAS,KAAK;SACd;AAEN,SAAO;;CAGT,MAAM,IAAI,IAAI,YAAY,KAAK;CAC/B,IAAI,UAAU;AAEd,MAAK,MAAM,QAAQ,IAAI,MAAM;AAC3B,MAAI,KAAK,SAAS,yBAA0B;AAI5C,MAAI,KAAK,aAAa;GACpB,MAAM,OAAO,KAAK;AAClB,OAAI,KAAK,SAAS,yBAAyB,KAAK,MAAM,eAAe,IAAI,KAAK,GAAG,KAAK,EAAE;AACtF,MAAE,UACA,KAAK,OACL,KAAK,KACL,mBAAmB,KAAK,GAAG,KAAK,8BACjC;AACD,cAAU;cACD,KAAK,SAAS;SAClB,MAAM,cAAc,KAAK,aAC5B,KAAI,WAAW,IAAI,SAAS,gBAAgB,eAAe,IAAI,WAAW,GAAG,KAAK,EAAE;AAClF,OAAE,UAAU,KAAK,OAAO,KAAK,KAAK,gBAAgB,WAAW,GAAG,KAAK,eAAe;AACpF,eAAU;;;AAIhB;;AAIF,MAAI,KAAK,cAAc,KAAK,WAAW,SAAS,KAAK,CAAC,KAAK,QAAQ;GACjE,MAAM,OAAwD,EAAE;GAChE,MAAM,WAAqB,EAAE;AAC7B,QAAK,MAAM,QAAQ,KAAK,YAAY;IAGlC,MAAM,eAAgB,KAAK,UAAkB,QAAS,KAAK,UAAkB;AAC7E,QAAI,eAAe,IAAI,aAAa,CAClC,UAAS,KAAK,aAAa;QAE3B,MAAK,KAAK,KAAK;;AAGnB,OAAI,SAAS,SAAS,GAAG;IAEvB,MAAM,QAAkB,EAAE;AAC1B,QAAI,KAAK,SAAS,GAAG;KACnB,MAAM,UAAU,KAEb,KAAK,OAAY;MAChB,MAAM,QAAQ,GAAG,MAAM;MACvB,MAAM,WAAW,GAAG,UAAU,QAAQ,GAAG,UAAU;AACnD,aAAO,UAAU,WAAW,QAAQ,GAAG,MAAM,MAAM;OACnD,CACD,KAAK,KAAK;AACb,WAAM,KAAK,YAAY,QAAQ,KAAK;;AAEtC,SAAK,MAAM,QAAQ,SACjB,OAAM,KAAK,gBAAgB,KAAK,eAAe;AAEjD,MAAE,UAAU,KAAK,OAAO,KAAK,KAAK,MAAM,KAAK,KAAK,CAAC;AACnD,cAAU;;;;AAKhB,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,EAAE,UAAU"}
|
|
@@ -8,7 +8,8 @@ type InterceptingRoute = {
|
|
|
8
8
|
params: string[];
|
|
9
9
|
};
|
|
10
10
|
type ParallelSlot = {
|
|
11
|
-
/**
|
|
11
|
+
/** Stable slot identity (name + owning directory), used for route serialization keys. */key: string; /** Slot name (e.g. "team" from @team) */
|
|
12
|
+
name: string; /** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */
|
|
12
13
|
ownerDir: string; /** Absolute path to the slot's page component */
|
|
13
14
|
pagePath: string | null; /** Absolute path to the slot's default.tsx fallback */
|
|
14
15
|
defaultPath: string | null; /** Absolute path to the slot's layout component (wraps slot content) */
|
|
@@ -35,8 +36,8 @@ type AppRoute = {
|
|
|
35
36
|
/** URL pattern, e.g. "/" or "/about" or "/blog/:slug" */pattern: string; /** Absolute file path to the page component */
|
|
36
37
|
pagePath: string | null; /** Absolute file path to the route handler (route.ts) */
|
|
37
38
|
routePath: string | null; /** Ordered list of layout files from root to leaf */
|
|
38
|
-
layouts: string[]; /**
|
|
39
|
-
templates:
|
|
39
|
+
layouts: string[]; /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */
|
|
40
|
+
templates: string[]; /** Parallel route slots (from @slot directories at the route's directory level) */
|
|
40
41
|
parallelSlots: ParallelSlot[]; /** Loading component path */
|
|
41
42
|
loadingPath: string | null; /** Error component path (leaf directory only) */
|
|
42
43
|
errorPath: string | null;
|
|
@@ -63,7 +64,8 @@ type AppRoute = {
|
|
|
63
64
|
* Includes route groups and dynamic segments (as template strings like "[id]").
|
|
64
65
|
* Used at render time to compute the child segments for useSelectedLayoutSegments().
|
|
65
66
|
*/
|
|
66
|
-
routeSegments: string[];
|
|
67
|
+
routeSegments: string[]; /** Tree position (directory depth from app/ root) for each template. */
|
|
68
|
+
templateTreePositions?: number[];
|
|
67
69
|
/**
|
|
68
70
|
* Tree position (directory depth from app/ root) for each layout.
|
|
69
71
|
* Used to slice routeSegments and determine which segments are below each layout.
|
|
@@ -28,6 +28,13 @@ function invalidateAppRouteCache() {
|
|
|
28
28
|
cachedAppDir = null;
|
|
29
29
|
cachedPageExtensionsKey = null;
|
|
30
30
|
}
|
|
31
|
+
function hasParallelSlotDirectory(dir) {
|
|
32
|
+
try {
|
|
33
|
+
return fs.readdirSync(dir, { withFileTypes: true }).some((entry) => entry.isDirectory() && entry.name.startsWith("@"));
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
31
38
|
/**
|
|
32
39
|
* Scan the app/ directory and return a list of routes.
|
|
33
40
|
*/
|
|
@@ -45,6 +52,17 @@ async function appRouter(appDir, pageExtensions, matcher) {
|
|
|
45
52
|
const route = fileToAppRoute(file, appDir, "route", matcher);
|
|
46
53
|
if (route) routes.push(route);
|
|
47
54
|
}
|
|
55
|
+
const routePatterns = new Set(routes.map((route) => route.pattern));
|
|
56
|
+
for await (const file of scanWithExtensions("**/layout", appDir, matcher.extensions, excludeDir)) {
|
|
57
|
+
const dir = path.dirname(file);
|
|
58
|
+
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
59
|
+
if (!hasParallelSlotDirectory(routeDir)) continue;
|
|
60
|
+
if (discoverParallelSlots(routeDir, appDir, matcher).length === 0) continue;
|
|
61
|
+
const route = directoryToAppRoute(dir, appDir, matcher, null, null);
|
|
62
|
+
if (!route || routePatterns.has(route.pattern)) continue;
|
|
63
|
+
routes.push(route);
|
|
64
|
+
routePatterns.add(route.pattern);
|
|
65
|
+
}
|
|
48
66
|
const slotSubRoutes = discoverSlotSubRoutes(routes, appDir, matcher);
|
|
49
67
|
routes.push(...slotSubRoutes);
|
|
50
68
|
validateRoutePatterns(routes.map((route) => route.pattern));
|
|
@@ -69,10 +87,9 @@ async function appRouter(appDir, pageExtensions, matcher) {
|
|
|
69
87
|
function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
70
88
|
const syntheticRoutes = [];
|
|
71
89
|
const routesByPattern = new Map(routes.map((r) => [r.pattern, r]));
|
|
72
|
-
const slotKey = (slotName, ownerDir) => `${slotName}\u0000${ownerDir}`;
|
|
73
90
|
const applySlotSubPages = (route, slotPages, rawSegments) => {
|
|
74
91
|
route.parallelSlots = route.parallelSlots.map((slot) => {
|
|
75
|
-
const subPage = slotPages.get(
|
|
92
|
+
const subPage = slotPages.get(slot.key);
|
|
76
93
|
if (subPage !== void 0) return {
|
|
77
94
|
...slot,
|
|
78
95
|
pagePath: subPage,
|
|
@@ -87,7 +104,8 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
87
104
|
const parentPageDir = path.dirname(parentRoute.pagePath);
|
|
88
105
|
const subPathMap = /* @__PURE__ */ new Map();
|
|
89
106
|
for (const slot of parentRoute.parallelSlots) {
|
|
90
|
-
|
|
107
|
+
if (path.dirname(slot.ownerDir) !== parentPageDir) continue;
|
|
108
|
+
const slotDir = slot.ownerDir;
|
|
91
109
|
if (!fs.existsSync(slotDir)) continue;
|
|
92
110
|
const subPages = findSlotSubPages(slotDir, matcher);
|
|
93
111
|
for (const { relativePath, pagePath } of subPages) {
|
|
@@ -105,12 +123,11 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
105
123
|
};
|
|
106
124
|
subPathMap.set(normalizedSubPath, subPathEntry);
|
|
107
125
|
}
|
|
108
|
-
|
|
109
|
-
if (subPathEntry.slotPages.get(slotId)) {
|
|
126
|
+
if (subPathEntry.slotPages.get(slot.key)) {
|
|
110
127
|
const pattern = joinRoutePattern(parentRoute.pattern, normalizedSubPath);
|
|
111
128
|
throw new Error(`You cannot have two routes that resolve to the same path ("${pattern}").`);
|
|
112
129
|
}
|
|
113
|
-
subPathEntry.slotPages.set(
|
|
130
|
+
subPathEntry.slotPages.set(slot.key, pagePath);
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
if (subPathMap.size === 0) continue;
|
|
@@ -127,7 +144,7 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
127
144
|
continue;
|
|
128
145
|
}
|
|
129
146
|
const subSlots = parentRoute.parallelSlots.map((slot) => {
|
|
130
|
-
const subPage = slotPages.get(
|
|
147
|
+
const subPage = slotPages.get(slot.key);
|
|
131
148
|
return {
|
|
132
149
|
...slot,
|
|
133
150
|
pagePath: subPage || null,
|
|
@@ -149,6 +166,7 @@ function discoverSlotSubRoutes(routes, _appDir, matcher) {
|
|
|
149
166
|
forbiddenPath: parentRoute.forbiddenPath,
|
|
150
167
|
unauthorizedPath: parentRoute.unauthorizedPath,
|
|
151
168
|
routeSegments: [...parentRoute.routeSegments, ...rawSegments],
|
|
169
|
+
templateTreePositions: parentRoute.templateTreePositions,
|
|
152
170
|
layoutTreePositions: parentRoute.layoutTreePositions,
|
|
153
171
|
isDynamic: parentRoute.isDynamic || subIsDynamic,
|
|
154
172
|
params: [...parentRoute.params, ...subParams],
|
|
@@ -194,7 +212,9 @@ function findSlotSubPages(slotDir, matcher) {
|
|
|
194
212
|
* Convert a file path relative to app/ into an AppRoute.
|
|
195
213
|
*/
|
|
196
214
|
function fileToAppRoute(file, appDir, type, matcher) {
|
|
197
|
-
|
|
215
|
+
return directoryToAppRoute(path.dirname(file), appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
|
|
216
|
+
}
|
|
217
|
+
function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath) {
|
|
198
218
|
const segments = dir === "." ? [] : dir.split(path.sep);
|
|
199
219
|
const params = [];
|
|
200
220
|
let isDynamic = false;
|
|
@@ -205,7 +225,8 @@ function fileToAppRoute(file, appDir, type, matcher) {
|
|
|
205
225
|
isDynamic = routeIsDynamic;
|
|
206
226
|
const pattern = "/" + urlSegments.join("/");
|
|
207
227
|
const layouts = discoverLayouts(segments, appDir, matcher);
|
|
208
|
-
const templates =
|
|
228
|
+
const templates = discoverTemplates(segments, appDir, matcher);
|
|
229
|
+
const templateTreePositions = computeLayoutTreePositions(appDir, templates);
|
|
209
230
|
const layoutTreePositions = computeLayoutTreePositions(appDir, layouts);
|
|
210
231
|
const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir, matcher);
|
|
211
232
|
const routeDir = dir === "." ? appDir : path.join(appDir, dir);
|
|
@@ -218,8 +239,8 @@ function fileToAppRoute(file, appDir, type, matcher) {
|
|
|
218
239
|
const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);
|
|
219
240
|
return {
|
|
220
241
|
pattern: pattern === "/" ? "/" : pattern,
|
|
221
|
-
pagePath
|
|
222
|
-
routePath
|
|
242
|
+
pagePath,
|
|
243
|
+
routePath,
|
|
223
244
|
layouts,
|
|
224
245
|
templates,
|
|
225
246
|
parallelSlots,
|
|
@@ -231,6 +252,7 @@ function fileToAppRoute(file, appDir, type, matcher) {
|
|
|
231
252
|
forbiddenPath,
|
|
232
253
|
unauthorizedPath,
|
|
233
254
|
routeSegments: segments,
|
|
255
|
+
templateTreePositions,
|
|
234
256
|
layoutTreePositions,
|
|
235
257
|
isDynamic,
|
|
236
258
|
params,
|
|
@@ -266,23 +288,19 @@ function discoverLayouts(segments, appDir, matcher) {
|
|
|
266
288
|
return layouts;
|
|
267
289
|
}
|
|
268
290
|
/**
|
|
269
|
-
* Discover template files
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
* Returns an array of the same length as discoverLayouts() would return,
|
|
273
|
-
* with the template path (or null) at each corresponding layout level.
|
|
274
|
-
*
|
|
275
|
-
* This enables interleaving templates with their corresponding layouts,
|
|
276
|
-
* matching Next.js behavior where each segment's hierarchy is
|
|
277
|
-
* Layout > Template > ErrorBoundary > children.
|
|
291
|
+
* Discover all template files from root to the given directory.
|
|
292
|
+
* Each level of the directory tree may have a template.tsx.
|
|
293
|
+
* Templates are like layouts but re-mount on navigation.
|
|
278
294
|
*/
|
|
279
|
-
function
|
|
295
|
+
function discoverTemplates(segments, appDir, matcher) {
|
|
280
296
|
const templates = [];
|
|
281
|
-
|
|
297
|
+
const rootTemplate = findFile(appDir, "template", matcher);
|
|
298
|
+
if (rootTemplate) templates.push(rootTemplate);
|
|
282
299
|
let currentDir = appDir;
|
|
283
300
|
for (const segment of segments) {
|
|
284
301
|
currentDir = path.join(currentDir, segment);
|
|
285
|
-
|
|
302
|
+
const template = findFile(currentDir, "template", matcher);
|
|
303
|
+
if (template) templates.push(template);
|
|
286
304
|
}
|
|
287
305
|
return templates;
|
|
288
306
|
}
|
|
@@ -374,7 +392,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
|
374
392
|
const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);
|
|
375
393
|
for (const slot of slotsAtLevel) if (isOwnDir) {
|
|
376
394
|
slot.layoutIndex = lvlLayoutIdx;
|
|
377
|
-
slotMap.set(slot.
|
|
395
|
+
slotMap.set(slot.key, slot);
|
|
378
396
|
} else {
|
|
379
397
|
const inheritedSlot = {
|
|
380
398
|
...slot,
|
|
@@ -382,7 +400,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher) {
|
|
|
382
400
|
layoutIndex: lvlLayoutIdx,
|
|
383
401
|
routeSegments: null
|
|
384
402
|
};
|
|
385
|
-
slotMap.set(slot.
|
|
403
|
+
slotMap.set(slot.key, inheritedSlot);
|
|
386
404
|
}
|
|
387
405
|
}
|
|
388
406
|
return Array.from(slotMap.values());
|
|
@@ -404,6 +422,7 @@ function discoverParallelSlots(dir, appDir, matcher) {
|
|
|
404
422
|
const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
|
|
405
423
|
if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;
|
|
406
424
|
slots.push({
|
|
425
|
+
key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\/g, "/")}`,
|
|
407
426
|
name: slotName,
|
|
408
427
|
ownerDir: slotDir,
|
|
409
428
|
pagePath,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-router.js","names":[],"sources":["../../src/routing/app-router.ts"],"sourcesContent":["/**\n * App Router file-system routing.\n *\n * Scans the app/ directory following Next.js App Router conventions:\n * - app/page.tsx -> /\n * - app/about/page.tsx -> /about\n * - app/blog/[slug]/page.tsx -> /blog/:slug\n * - app/[...catchAll]/page.tsx -> /:catchAll+\n * - app/route.ts -> / (API route)\n * - app/(group)/page.tsx -> / (route groups are transparent)\n * - Layouts: app/layout.tsx wraps all children\n * - Loading: app/loading.tsx -> Suspense fallback\n * - Error: app/error.tsx -> ErrorBoundary\n * - Not Found: app/not-found.tsx\n */\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { compareRoutes, decodeRouteSegment, normalizePathnameForRouteMatch } from \"./utils.js\";\nimport {\n createValidFileMatcher,\n scanWithExtensions,\n type ValidFileMatcher,\n} from \"./file-matcher.js\";\nimport { validateRoutePatterns } from \"./route-validation.js\";\nimport { buildRouteTrie, trieMatch, type TrieNode } from \"./route-trie.js\";\n\nexport type InterceptingRoute = {\n /** The interception convention: \".\" | \"..\" | \"../..\" | \"...\" */\n convention: string;\n /** The URL pattern this intercepts (e.g. \"/photos/:id\") */\n targetPattern: string;\n /** Absolute path to the intercepting page component */\n pagePath: string;\n /** Parameter names for dynamic segments */\n params: string[];\n};\n\nexport type ParallelSlot = {\n /** Slot name (e.g. \"team\" from @team) */\n name: string;\n /** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */\n ownerDir: string;\n /** Absolute path to the slot's page component */\n pagePath: string | null;\n /** Absolute path to the slot's default.tsx fallback */\n defaultPath: string | null;\n /** Absolute path to the slot's layout component (wraps slot content) */\n layoutPath: string | null;\n /** Absolute path to the slot's loading component */\n loadingPath: string | null;\n /** Absolute path to the slot's error component */\n errorPath: string | null;\n /** Intercepting routes within this slot */\n interceptingRoutes: InterceptingRoute[];\n /**\n * The layout index (0-based, in route.layouts[]) that this slot belongs to.\n * Slots are passed as props to the layout at their directory level, not\n * necessarily the innermost layout. -1 means \"innermost\" (legacy default).\n */\n layoutIndex: number;\n /**\n * Filesystem segments from the slot's root directory to its active page.\n * Used at render time to compute segments for useSelectedLayoutSegment(slotName).\n * For a page at the slot root (@team/page.tsx), this is [].\n * For a sub-page (@team/members/page.tsx), this is [\"members\"].\n * null when the slot has no active page (showing default.tsx fallback).\n */\n routeSegments: string[] | null;\n};\n\nexport type AppRoute = {\n /** URL pattern, e.g. \"/\" or \"/about\" or \"/blog/:slug\" */\n pattern: string;\n /** Absolute file path to the page component */\n pagePath: string | null;\n /** Absolute file path to the route handler (route.ts) */\n routePath: string | null;\n /** Ordered list of layout files from root to leaf */\n layouts: string[];\n /** Template files aligned with layouts array (null where no template exists at that level) */\n templates: (string | null)[];\n /** Parallel route slots (from @slot directories at the route's directory level) */\n parallelSlots: ParallelSlot[];\n /** Loading component path */\n loadingPath: string | null;\n /** Error component path (leaf directory only) */\n errorPath: string | null;\n /**\n * Per-layout error boundary paths, aligned with the layouts array.\n * Each entry is the error.tsx at the same directory level as the\n * corresponding layout (or null if that level has no error.tsx).\n * Used to interleave ErrorBoundary components with layouts so that\n * ancestor error boundaries catch errors from descendant segments.\n */\n layoutErrorPaths: (string | null)[];\n /** Not-found component path (nearest, walking up from page dir) */\n notFoundPath: string | null;\n /**\n * Not-found component paths per layout level (aligned with layouts array).\n * Each entry is the not-found.tsx at that layout's directory, or null.\n * Used to create per-layout NotFoundBoundary so that notFound() thrown from\n * a layout is caught by the parent layout's boundary (matching Next.js behavior).\n */\n notFoundPaths: (string | null)[];\n /** Forbidden component path (403) */\n forbiddenPath: string | null;\n /** Unauthorized component path (401) */\n unauthorizedPath: string | null;\n /**\n * Filesystem segments from app/ root to the route's directory.\n * Includes route groups and dynamic segments (as template strings like \"[id]\").\n * Used at render time to compute the child segments for useSelectedLayoutSegments().\n */\n routeSegments: string[];\n /**\n * Tree position (directory depth from app/ root) for each layout.\n * Used to slice routeSegments and determine which segments are below each layout.\n * For example, root layout = 0, a layout at app/blog/ = 1, app/blog/(group)/ = 2.\n * Unlike the old layoutSegmentDepths, this counts ALL directory levels including\n * route groups and parallel slots.\n */\n layoutTreePositions: number[];\n /** Whether this is a dynamic route */\n isDynamic: boolean;\n /** Parameter names for dynamic segments */\n params: string[];\n /** Pre-split pattern segments (computed once at scan time, reused per request) */\n patternParts: string[];\n};\n\n// Cache for app routes\nlet cachedRoutes: AppRoute[] | null = null;\nlet cachedAppDir: string | null = null;\nlet cachedPageExtensionsKey: string | null = null;\n\nexport function invalidateAppRouteCache(): void {\n cachedRoutes = null;\n cachedAppDir = null;\n cachedPageExtensionsKey = null;\n}\n\n/**\n * Scan the app/ directory and return a list of routes.\n */\nexport async function appRouter(\n appDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<AppRoute[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const pageExtensionsKey = JSON.stringify(matcher.extensions);\n if (cachedRoutes && cachedAppDir === appDir && cachedPageExtensionsKey === pageExtensionsKey) {\n return cachedRoutes;\n }\n\n // Find all page.tsx and route.ts files, excluding @slot directories\n // (slot pages are not standalone routes — they're rendered as props of their parent layout)\n // and _private folders (Next.js convention for colocated non-route files).\n const routes: AppRoute[] = [];\n\n const excludeDir = (name: string) => name.startsWith(\"@\") || name.startsWith(\"_\");\n\n // Process page files in a single pass\n // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)\n for await (const file of scanWithExtensions(\"**/page\", appDir, matcher.extensions, excludeDir)) {\n const route = fileToAppRoute(file, appDir, \"page\", matcher);\n if (route) routes.push(route);\n }\n\n // Process route handler files (API routes) in a single pass\n for await (const file of scanWithExtensions(\"**/route\", appDir, matcher.extensions, excludeDir)) {\n const route = fileToAppRoute(file, appDir, \"route\", matcher);\n if (route) routes.push(route);\n }\n\n // Discover sub-routes created by nested pages within parallel slots.\n // In Next.js, pages nested inside @slot directories create additional URL routes.\n // For example, @audience/demographics/page.tsx at app/parallel-routes/ creates\n // a route at /parallel-routes/demographics.\n const slotSubRoutes = discoverSlotSubRoutes(routes, appDir, matcher);\n routes.push(...slotSubRoutes);\n\n validateRoutePatterns(routes.map((route) => route.pattern));\n // Deduplicate intercept target patterns: child routes inherit parent slots\n // (including their intercepting routes), so the same target pattern can appear\n // on both the parent and child route. Collect unique patterns only.\n const interceptTargetPatterns = [\n ...new Set(\n routes.flatMap((route) =>\n route.parallelSlots.flatMap((slot) =>\n slot.interceptingRoutes.map((intercept) => intercept.targetPattern),\n ),\n ),\n ),\n ];\n validateRoutePatterns(interceptTargetPatterns);\n\n // Sort: static routes first, then dynamic, then catch-all\n routes.sort(compareRoutes);\n\n cachedRoutes = routes;\n cachedAppDir = appDir;\n cachedPageExtensionsKey = pageExtensionsKey;\n return routes;\n}\n\n/**\n * Discover sub-routes created by nested pages within parallel slots.\n *\n * In Next.js, pages nested inside @slot directories create additional URL routes.\n * For example, given:\n * app/parallel-routes/@audience/demographics/page.tsx\n * This creates a route at /parallel-routes/demographics where:\n * - children slot → parent's default.tsx\n * - @audience slot → @audience/demographics/page.tsx (matched)\n * - other slots → their default.tsx (fallback)\n */\nfunction discoverSlotSubRoutes(\n routes: AppRoute[],\n _appDir: string,\n matcher: ValidFileMatcher,\n): AppRoute[] {\n const syntheticRoutes: AppRoute[] = [];\n\n // O(1) lookup for existing routes by pattern — avoids O(n) routes.find() per sub-path per parent.\n // Updated as new synthetic routes are pushed so that later parents can see earlier synthetic entries.\n const routesByPattern = new Map<string, AppRoute>(routes.map((r) => [r.pattern, r]));\n\n const slotKey = (slotName: string, ownerDir: string): string => `${slotName}\\u0000${ownerDir}`;\n\n const applySlotSubPages = (\n route: AppRoute,\n slotPages: Map<string, string>,\n rawSegments: string[],\n ): void => {\n route.parallelSlots = route.parallelSlots.map((slot) => {\n const subPage = slotPages.get(slotKey(slot.name, slot.ownerDir));\n if (subPage !== undefined) {\n return { ...slot, pagePath: subPage, routeSegments: rawSegments };\n }\n return slot;\n });\n };\n\n for (const parentRoute of routes) {\n if (parentRoute.parallelSlots.length === 0) continue;\n if (!parentRoute.pagePath) continue;\n\n const parentPageDir = path.dirname(parentRoute.pagePath);\n\n // Collect sub-paths from all slots.\n // Map: normalized visible sub-path -> slot pages, raw filesystem segments (for routeSegments),\n // and the pre-computed convertedSubRoute (to avoid a redundant re-conversion in the merge loop).\n const subPathMap = new Map<\n string,\n {\n // Raw filesystem segments (with route groups, @slots, etc.) used for routeSegments so\n // that useSelectedLayoutSegments() sees the correct segment list at runtime.\n rawSegments: string[];\n // Pre-computed URL parts, params, isDynamic from convertSegmentsToRouteParts.\n converted: {\n urlSegments: string[];\n params: string[];\n isDynamic: boolean;\n };\n slotPages: Map<string, string>;\n }\n >();\n\n for (const slot of parentRoute.parallelSlots) {\n const slotDir = path.join(parentPageDir, `@${slot.name}`);\n if (!fs.existsSync(slotDir)) continue;\n\n const subPages = findSlotSubPages(slotDir, matcher);\n for (const { relativePath, pagePath } of subPages) {\n const subSegments = relativePath.split(path.sep);\n const convertedSubRoute = convertSegmentsToRouteParts(subSegments);\n if (!convertedSubRoute) continue;\n\n const { urlSegments } = convertedSubRoute;\n const normalizedSubPath = urlSegments.join(\"/\");\n let subPathEntry = subPathMap.get(normalizedSubPath);\n\n if (!subPathEntry) {\n subPathEntry = {\n rawSegments: subSegments,\n converted: convertedSubRoute,\n slotPages: new Map(),\n };\n subPathMap.set(normalizedSubPath, subPathEntry);\n }\n\n const slotId = slotKey(slot.name, slot.ownerDir);\n const existingSlotPage = subPathEntry.slotPages.get(slotId);\n if (existingSlotPage) {\n const pattern = joinRoutePattern(parentRoute.pattern, normalizedSubPath);\n throw new Error(\n `You cannot have two routes that resolve to the same path (\"${pattern}\").`,\n );\n }\n\n subPathEntry.slotPages.set(slotId, pagePath);\n }\n }\n\n if (subPathMap.size === 0) continue;\n\n // Find the default.tsx for the children slot at the parent directory\n const childrenDefault = findFile(parentPageDir, \"default\", matcher);\n if (!childrenDefault) continue;\n\n for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {\n const {\n urlSegments: urlParts,\n params: subParams,\n isDynamic: subIsDynamic,\n } = convertedSubRoute;\n\n const subUrlPath = urlParts.join(\"/\");\n const pattern = joinRoutePattern(parentRoute.pattern, subUrlPath);\n\n const existingRoute = routesByPattern.get(pattern);\n if (existingRoute) {\n if (existingRoute.routePath && !existingRoute.pagePath) {\n throw new Error(\n `You cannot have two routes that resolve to the same path (\"${pattern}\").`,\n );\n }\n applySlotSubPages(existingRoute, slotPages, rawSegments);\n continue;\n }\n\n // Build parallel slots for this sub-route: matching slots get the sub-page,\n // non-matching slots get null pagePath (rendering falls back to defaultPath)\n const subSlots: ParallelSlot[] = parentRoute.parallelSlots.map((slot) => {\n const subPage = slotPages.get(slotKey(slot.name, slot.ownerDir));\n return {\n ...slot,\n pagePath: subPage || null,\n routeSegments: subPage ? rawSegments : null,\n };\n });\n\n const newRoute: AppRoute = {\n pattern,\n pagePath: childrenDefault, // children slot uses parent's default.tsx as page\n routePath: null,\n layouts: parentRoute.layouts,\n templates: parentRoute.templates,\n parallelSlots: subSlots,\n loadingPath: parentRoute.loadingPath,\n errorPath: parentRoute.errorPath,\n layoutErrorPaths: parentRoute.layoutErrorPaths,\n notFoundPath: parentRoute.notFoundPath,\n notFoundPaths: parentRoute.notFoundPaths,\n forbiddenPath: parentRoute.forbiddenPath,\n unauthorizedPath: parentRoute.unauthorizedPath,\n routeSegments: [...parentRoute.routeSegments, ...rawSegments],\n layoutTreePositions: parentRoute.layoutTreePositions,\n isDynamic: parentRoute.isDynamic || subIsDynamic,\n params: [...parentRoute.params, ...subParams],\n patternParts: [...parentRoute.patternParts, ...urlParts],\n };\n syntheticRoutes.push(newRoute);\n routesByPattern.set(pattern, newRoute);\n }\n }\n\n return syntheticRoutes;\n}\n\n/**\n * Find all page files in subdirectories of a parallel slot directory.\n * Returns relative paths (from the slot dir) and absolute page paths.\n * Skips the root page.tsx (already handled as the slot's main page)\n * and intercepting route directories.\n */\nfunction findSlotSubPages(\n slotDir: string,\n matcher: ValidFileMatcher,\n): Array<{ relativePath: string; pagePath: string }> {\n const results: Array<{ relativePath: string; pagePath: string }> = [];\n\n function scan(dir: string): void {\n if (!fs.existsSync(dir)) return;\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Skip intercepting route directories\n if (matchInterceptConvention(entry.name)) continue;\n // Skip private folders (prefixed with _)\n if (entry.name.startsWith(\"_\")) continue;\n\n const subDir = path.join(dir, entry.name);\n const page = findFile(subDir, \"page\", matcher);\n if (page) {\n const relativePath = path.relative(slotDir, subDir);\n results.push({ relativePath, pagePath: page });\n }\n // Continue scanning deeper for nested sub-pages\n scan(subDir);\n }\n }\n\n scan(slotDir);\n return results;\n}\n\n/**\n * Convert a file path relative to app/ into an AppRoute.\n */\nfunction fileToAppRoute(\n file: string,\n appDir: string,\n type: \"page\" | \"route\",\n matcher: ValidFileMatcher,\n): AppRoute | null {\n // Remove the filename (page.tsx or route.ts)\n const dir = path.dirname(file);\n const segments = dir === \".\" ? [] : dir.split(path.sep);\n\n const params: string[] = [];\n let isDynamic = false;\n\n const convertedRoute = convertSegmentsToRouteParts(segments);\n if (!convertedRoute) return null;\n\n const { urlSegments, params: routeParams, isDynamic: routeIsDynamic } = convertedRoute;\n params.push(...routeParams);\n isDynamic = routeIsDynamic;\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n\n // Discover layouts and layout-aligned templates from root to leaf\n const layouts = discoverLayouts(segments, appDir, matcher);\n const templates = discoverLayoutAlignedTemplates(segments, appDir, matcher);\n\n // Compute the tree position (directory depth) for each layout.\n const layoutTreePositions = computeLayoutTreePositions(appDir, layouts);\n\n // Discover per-layout error boundaries (aligned with layouts array).\n // In Next.js, each segment independently wraps its children with an ErrorBoundary.\n // This array enables interleaving error boundaries with layouts in the rendering.\n const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir, matcher);\n\n // Discover loading, error in the route's directory\n const routeDir = dir === \".\" ? appDir : path.join(appDir, dir);\n const loadingPath = findFile(routeDir, \"loading\", matcher);\n const errorPath = findFile(routeDir, \"error\", matcher);\n\n // Discover not-found/forbidden/unauthorized: walk from route directory up to root (nearest wins).\n const notFoundPath = discoverBoundaryFile(segments, appDir, \"not-found\", matcher);\n const forbiddenPath = discoverBoundaryFile(segments, appDir, \"forbidden\", matcher);\n const unauthorizedPath = discoverBoundaryFile(segments, appDir, \"unauthorized\", matcher);\n\n // Discover per-layout not-found files (one per layout directory).\n // These are used for per-layout NotFoundBoundary to match Next.js behavior where\n // notFound() thrown from a layout is caught by the parent layout's boundary.\n const notFoundPaths = discoverBoundaryFilePerLayout(layouts, \"not-found\", matcher);\n\n // Discover parallel slots (@team, @analytics, etc.).\n // Slots at the route's own directory use page.tsx; slots at ancestor directories\n // (inherited from parent layouts) use default.tsx as fallback.\n const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);\n\n return {\n pattern: pattern === \"/\" ? \"/\" : pattern,\n pagePath: type === \"page\" ? path.join(appDir, file) : null,\n routePath: type === \"route\" ? path.join(appDir, file) : null,\n layouts,\n templates,\n parallelSlots,\n loadingPath,\n errorPath,\n layoutErrorPaths,\n notFoundPath,\n notFoundPaths,\n forbiddenPath,\n unauthorizedPath,\n routeSegments: segments,\n layoutTreePositions,\n isDynamic,\n params,\n patternParts: urlSegments,\n };\n}\n\n/**\n * Compute the tree position (directory depth from app root) for each layout.\n * Root layout = 0, a layout at app/blog/ = 1, app/blog/(group)/ = 2.\n * Counts ALL directory levels including route groups and parallel slots.\n */\nfunction computeLayoutTreePositions(appDir: string, layouts: string[]): number[] {\n return layouts.map((layoutPath) => {\n const layoutDir = path.dirname(layoutPath);\n if (layoutDir === appDir) return 0;\n const relative = path.relative(appDir, layoutDir);\n return relative.split(path.sep).length;\n });\n}\n\n/**\n * Discover all layout files from root to the given directory.\n * Each level of the directory tree may have a layout.tsx.\n */\nfunction discoverLayouts(segments: string[], appDir: string, matcher: ValidFileMatcher): string[] {\n const layouts: string[] = [];\n\n // Check root layout\n const rootLayout = findFile(appDir, \"layout\", matcher);\n if (rootLayout) layouts.push(rootLayout);\n\n // Check each directory level\n let currentDir = appDir;\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n const layout = findFile(currentDir, \"layout\", matcher);\n if (layout) layouts.push(layout);\n }\n\n return layouts;\n}\n\n/**\n * Discover template files aligned with the layouts array.\n * Walks the same directory levels as discoverLayouts and, for each level\n * that contributes a layout entry, checks whether template.tsx also exists.\n * Returns an array of the same length as discoverLayouts() would return,\n * with the template path (or null) at each corresponding layout level.\n *\n * This enables interleaving templates with their corresponding layouts,\n * matching Next.js behavior where each segment's hierarchy is\n * Layout > Template > ErrorBoundary > children.\n */\nfunction discoverLayoutAlignedTemplates(\n segments: string[],\n appDir: string,\n matcher: ValidFileMatcher,\n): (string | null)[] {\n const templates: (string | null)[] = [];\n\n // Root level (only if root has a layout — matching discoverLayouts logic)\n const rootLayout = findFile(appDir, \"layout\", matcher);\n if (rootLayout) {\n templates.push(findFile(appDir, \"template\", matcher));\n }\n\n // Check each directory level\n let currentDir = appDir;\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n const layout = findFile(currentDir, \"layout\", matcher);\n if (layout) {\n templates.push(findFile(currentDir, \"template\", matcher));\n }\n }\n\n return templates;\n}\n\n/**\n * Discover error.tsx files aligned with the layouts array.\n * Walks the same directory levels as discoverLayouts and, for each level\n * that contributes a layout entry, checks whether error.tsx also exists.\n * Returns an array of the same length as discoverLayouts() would return,\n * with the error path (or null) at each corresponding layout level.\n *\n * This enables interleaving ErrorBoundary components with layouts in the\n * rendering tree, matching Next.js behavior where each segment independently\n * wraps its children with an error boundary.\n */\nfunction discoverLayoutAlignedErrors(\n segments: string[],\n appDir: string,\n matcher: ValidFileMatcher,\n): (string | null)[] {\n const errors: (string | null)[] = [];\n\n // Root level (only if root has a layout — matching discoverLayouts logic)\n const rootLayout = findFile(appDir, \"layout\", matcher);\n if (rootLayout) {\n errors.push(findFile(appDir, \"error\", matcher));\n }\n\n // Check each directory level\n let currentDir = appDir;\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n const layout = findFile(currentDir, \"layout\", matcher);\n if (layout) {\n errors.push(findFile(currentDir, \"error\", matcher));\n }\n }\n\n return errors;\n}\n\n/**\n * Discover the nearest boundary file (not-found, forbidden, unauthorized)\n * by walking from the route's directory up to the app root.\n * Returns the first (closest) file found, or null.\n */\nfunction discoverBoundaryFile(\n segments: string[],\n appDir: string,\n fileName: string,\n matcher: ValidFileMatcher,\n): string | null {\n // Build all directory paths from leaf to root\n const dirs: string[] = [];\n let dir = appDir;\n dirs.push(dir);\n for (const segment of segments) {\n dir = path.join(dir, segment);\n dirs.push(dir);\n }\n\n // Walk from leaf (last) to root (first)\n for (let i = dirs.length - 1; i >= 0; i--) {\n const f = findFile(dirs[i], fileName, matcher);\n if (f) return f;\n }\n return null;\n}\n\n/**\n * Discover boundary files (not-found, forbidden, unauthorized) at each layout directory.\n * Returns an array aligned with the layouts array, where each entry is the boundary\n * file at that layout's directory, or null if none exists there.\n *\n * This is used for per-layout error boundaries. In Next.js, each layout level\n * has its own boundary that wraps the layout's children. When notFound() is thrown\n * from a layout, it propagates up to the parent layout's boundary.\n */\nfunction discoverBoundaryFilePerLayout(\n layouts: string[],\n fileName: string,\n matcher: ValidFileMatcher,\n): (string | null)[] {\n return layouts.map((layoutPath) => {\n const layoutDir = path.dirname(layoutPath);\n return findFile(layoutDir, fileName, matcher);\n });\n}\n\n/**\n * Discover parallel slots inherited from ancestor directories.\n *\n * In Next.js, parallel slots belong to the layout that defines them. When a\n * child route is rendered, its parent layout's slots must still be present.\n * If the child doesn't have matching content in a slot, the slot's default.tsx\n * is rendered instead.\n *\n * Walk from appDir through each segment to the route's directory. At each level\n * that has @slot dirs, collect them. Slots at the route's own directory level\n * use page.tsx; slots at ancestor levels use default.tsx only.\n */\nfunction discoverInheritedParallelSlots(\n segments: string[],\n appDir: string,\n routeDir: string,\n matcher: ValidFileMatcher,\n): ParallelSlot[] {\n const slotMap = new Map<string, ParallelSlot>();\n\n // Walk from appDir through each segment, tracking layout indices.\n // layoutIndex tracks which position in the route's layouts[] array corresponds\n // to a given directory. Only directories with a layout.tsx file increment.\n let currentDir = appDir;\n const dirsToCheck: { dir: string; layoutIdx: number }[] = [];\n let layoutIdx = findFile(appDir, \"layout\", matcher) ? 0 : -1;\n dirsToCheck.push({ dir: appDir, layoutIdx: Math.max(layoutIdx, 0) });\n\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n if (findFile(currentDir, \"layout\", matcher)) {\n layoutIdx++;\n }\n dirsToCheck.push({ dir: currentDir, layoutIdx: Math.max(layoutIdx, 0) });\n }\n\n for (const { dir, layoutIdx: lvlLayoutIdx } of dirsToCheck) {\n const isOwnDir = dir === routeDir;\n const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);\n\n for (const slot of slotsAtLevel) {\n if (isOwnDir) {\n // At the route's own directory: use page.tsx (normal behavior)\n slot.layoutIndex = lvlLayoutIdx;\n slotMap.set(slot.name, slot);\n } else {\n // At an ancestor directory: use default.tsx as the page, not page.tsx\n // (the slot's page.tsx is for the parent route, not this child route)\n const inheritedSlot: ParallelSlot = {\n ...slot,\n pagePath: null, // Don't use ancestor's page.tsx\n layoutIndex: lvlLayoutIdx,\n routeSegments: null, // Inherited slot shows default.tsx, not an active page\n // defaultPath, loadingPath, errorPath, interceptingRoutes remain\n };\n // Iteration goes root-to-leaf, so later (closer) ancestors overwrite\n // earlier (farther) ones — the closest ancestor's slot wins.\n slotMap.set(slot.name, inheritedSlot);\n }\n }\n }\n\n return Array.from(slotMap.values());\n}\n\n/**\n * Discover parallel route slots (@team, @analytics, etc.) in a directory.\n * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.\n */\nfunction discoverParallelSlots(\n dir: string,\n appDir: string,\n matcher: ValidFileMatcher,\n): ParallelSlot[] {\n if (!fs.existsSync(dir)) return [];\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n const slots: ParallelSlot[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.startsWith(\"@\")) continue;\n\n const slotName = entry.name.slice(1); // \"@team\" -> \"team\"\n const slotDir = path.join(dir, entry.name);\n\n const pagePath = findFile(slotDir, \"page\", matcher);\n const defaultPath = findFile(slotDir, \"default\", matcher);\n const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);\n\n // Only include slots that have at least a page, default, or intercepting route\n if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;\n\n slots.push({\n name: slotName,\n ownerDir: slotDir,\n pagePath,\n defaultPath,\n layoutPath: findFile(slotDir, \"layout\", matcher),\n loadingPath: findFile(slotDir, \"loading\", matcher),\n errorPath: findFile(slotDir, \"error\", matcher),\n interceptingRoutes,\n layoutIndex: -1, // Will be set by discoverInheritedParallelSlots\n routeSegments: pagePath ? [] : null, // Root page = [], no page = null (default fallback)\n });\n }\n\n return slots;\n}\n\n/**\n * The interception convention prefix patterns.\n * (.) — same level, (..) — one level up, (..)(..)\" — two levels up, (...) — root\n */\nconst INTERCEPT_PATTERNS = [\n { prefix: \"(...)\", convention: \"...\" },\n { prefix: \"(..)(..)\", convention: \"../..\" },\n { prefix: \"(..)\", convention: \"..\" },\n { prefix: \"(.)\", convention: \".\" },\n] as const;\n\n/**\n * Discover intercepting routes inside a parallel slot directory.\n *\n * Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.\n * They intercept navigation to another route and render within the slot instead.\n *\n * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)\n * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)\n * @param appDir - The root app directory\n */\nfunction discoverInterceptingRoutes(\n slotDir: string,\n routeDir: string,\n appDir: string,\n matcher: ValidFileMatcher,\n): InterceptingRoute[] {\n if (!fs.existsSync(slotDir)) return [];\n\n const results: InterceptingRoute[] = [];\n\n // Recursively scan for page files inside intercepting directories\n scanForInterceptingPages(slotDir, routeDir, appDir, results, matcher);\n\n return results;\n}\n\n/**\n * Recursively scan a directory tree for page.tsx files that are inside\n * intercepting route directories.\n */\nfunction scanForInterceptingPages(\n currentDir: string,\n routeDir: string,\n appDir: string,\n results: InterceptingRoute[],\n matcher: ValidFileMatcher,\n): void {\n if (!fs.existsSync(currentDir)) return;\n\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Skip private folders (prefixed with _)\n if (entry.name.startsWith(\"_\")) continue;\n\n // Check if this directory name starts with an interception convention\n const interceptMatch = matchInterceptConvention(entry.name);\n\n if (interceptMatch) {\n // This directory is the start of an intercepting route\n // e.g. \"(.)photos\" means intercept same-level \"photos\" route\n const restOfName = entry.name.slice(interceptMatch.prefix.length);\n const interceptDir = path.join(currentDir, entry.name);\n\n // Find page files within this intercepting directory tree\n collectInterceptingPages(\n interceptDir,\n interceptDir,\n interceptMatch.convention,\n restOfName,\n routeDir,\n appDir,\n results,\n matcher,\n );\n } else {\n // Regular subdirectory — keep scanning for intercepting dirs\n scanForInterceptingPages(\n path.join(currentDir, entry.name),\n routeDir,\n appDir,\n results,\n matcher,\n );\n }\n }\n}\n\n/**\n * Match a directory name against interception convention prefixes.\n */\nfunction matchInterceptConvention(name: string): { prefix: string; convention: string } | null {\n for (const pattern of INTERCEPT_PATTERNS) {\n if (name.startsWith(pattern.prefix)) {\n return pattern;\n }\n }\n return null;\n}\n\n/**\n * Collect page.tsx files inside an intercepting route directory tree\n * and compute their target URL patterns.\n */\nfunction collectInterceptingPages(\n currentDir: string,\n interceptRoot: string,\n convention: string,\n interceptSegment: string,\n routeDir: string,\n appDir: string,\n results: InterceptingRoute[],\n matcher: ValidFileMatcher,\n): void {\n // Check for page.tsx in current directory\n const page = findFile(currentDir, \"page\", matcher);\n if (page) {\n const targetPattern = computeInterceptTarget(\n convention,\n interceptSegment,\n currentDir,\n interceptRoot,\n routeDir,\n appDir,\n );\n if (targetPattern) {\n results.push({\n convention,\n targetPattern: targetPattern.pattern,\n pagePath: page,\n params: targetPattern.params,\n });\n }\n }\n\n // Recurse into subdirectories for nested intercepting routes\n if (!fs.existsSync(currentDir)) return;\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Skip private folders (prefixed with _)\n if (entry.name.startsWith(\"_\")) continue;\n collectInterceptingPages(\n path.join(currentDir, entry.name),\n interceptRoot,\n convention,\n interceptSegment,\n routeDir,\n appDir,\n results,\n matcher,\n );\n }\n}\n\n/**\n * Check whether a path segment is invisible in the URL (route groups, parallel slots, \".\").\n *\n * Used by computeInterceptTarget, convertSegmentsToRouteParts, and\n * hasRemainingVisibleSegments — keep this the single source of truth.\n */\nfunction isInvisibleSegment(segment: string): boolean {\n if (segment === \".\") return true;\n if (segment.startsWith(\"(\") && segment.endsWith(\")\")) return true;\n if (segment.startsWith(\"@\")) return true;\n return false;\n}\n\n/**\n * Compute the target URL pattern for an intercepting route.\n *\n * Interception conventions (..), (..)(..)\" climb by *visible route segments*\n * (not filesystem directories). Route groups like (marketing) and parallel\n * slots like @modal are invisible and must be skipped when counting levels.\n *\n * - (.) same level: resolve relative to routeDir\n * - (..) one level up: climb 1 visible segment\n * - (..)(..) two levels up: climb 2 visible segments\n * - (...) root: resolve from appDir\n */\nfunction computeInterceptTarget(\n convention: string,\n interceptSegment: string,\n currentDir: string,\n interceptRoot: string,\n routeDir: string,\n appDir: string,\n): { pattern: string; params: string[] } | null {\n // Determine the base segments for target resolution.\n // We work on route segments (not filesystem paths) so that route groups\n // and parallel slots are properly skipped when climbing.\n const routeSegments = path.relative(appDir, routeDir).split(path.sep).filter(Boolean);\n\n let baseParts: string[];\n switch (convention) {\n case \".\":\n baseParts = routeSegments;\n break;\n case \"..\":\n case \"../..\": {\n const levelsToClimb = convention === \"..\" ? 1 : 2;\n let climbed = 0;\n let cutIndex = routeSegments.length;\n while (cutIndex > 0 && climbed < levelsToClimb) {\n cutIndex--;\n if (!isInvisibleSegment(routeSegments[cutIndex])) {\n climbed++;\n }\n }\n baseParts = routeSegments.slice(0, cutIndex);\n break;\n }\n case \"...\":\n baseParts = [];\n break;\n default:\n return null;\n }\n\n // Add the intercept segment and any nested path segments\n const nestedParts = path.relative(interceptRoot, currentDir).split(path.sep).filter(Boolean);\n const allSegments = [...baseParts, interceptSegment, ...nestedParts];\n\n const convertedTarget = convertSegmentsToRouteParts(allSegments);\n if (!convertedTarget) return null;\n\n const { urlSegments, params } = convertedTarget;\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n return { pattern: pattern === \"/\" ? \"/\" : pattern, params };\n}\n\n/**\n * Find a file by name (without extension) in a directory.\n * Checks configured pageExtensions.\n */\nfunction findFile(dir: string, name: string, matcher: ValidFileMatcher): string | null {\n for (const ext of matcher.dottedExtensions) {\n const filePath = path.join(dir, name + ext);\n if (fs.existsSync(filePath)) return filePath;\n }\n return null;\n}\n\n/**\n * Convert filesystem path segments to URL route parts, skipping invisible segments\n * (route groups, @slots, \".\") and converting dynamic segment syntax to Express-style\n * patterns (e.g. \"[id]\" → \":id\", \"[...slug]\" → \":slug+\").\n */\nfunction convertSegmentsToRouteParts(\n segments: string[],\n): { urlSegments: string[]; params: string[]; isDynamic: boolean } | null {\n const urlSegments: string[] = [];\n const params: string[] = [];\n let isDynamic = false;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n\n if (isInvisibleSegment(segment)) continue;\n\n // Catch-all segments are only valid in terminal URL position.\n const catchAllMatch = segment.match(/^\\[\\.\\.\\.([\\w-]+)\\]$/);\n if (catchAllMatch) {\n if (hasRemainingVisibleSegments(segments, i + 1)) return null;\n isDynamic = true;\n params.push(catchAllMatch[1]);\n urlSegments.push(`:${catchAllMatch[1]}+`);\n continue;\n }\n\n const optionalCatchAllMatch = segment.match(/^\\[\\[\\.\\.\\.([\\w-]+)\\]\\]$/);\n if (optionalCatchAllMatch) {\n if (hasRemainingVisibleSegments(segments, i + 1)) return null;\n isDynamic = true;\n params.push(optionalCatchAllMatch[1]);\n urlSegments.push(`:${optionalCatchAllMatch[1]}*`);\n continue;\n }\n\n const dynamicMatch = segment.match(/^\\[([\\w-]+)\\]$/);\n if (dynamicMatch) {\n isDynamic = true;\n params.push(dynamicMatch[1]);\n urlSegments.push(`:${dynamicMatch[1]}`);\n continue;\n }\n\n urlSegments.push(decodeRouteSegment(segment));\n }\n\n return { urlSegments, params, isDynamic };\n}\n\nfunction hasRemainingVisibleSegments(segments: string[], startIndex: number): boolean {\n for (let i = startIndex; i < segments.length; i++) {\n if (!isInvisibleSegment(segments[i])) return true;\n }\n return false;\n}\n\n// Trie cache — keyed by route array identity (same array = same trie)\nconst appTrieCache = new WeakMap<AppRoute[], TrieNode<AppRoute>>();\n\nfunction getOrBuildAppTrie(routes: AppRoute[]): TrieNode<AppRoute> {\n let trie = appTrieCache.get(routes);\n if (!trie) {\n trie = buildRouteTrie(routes);\n appTrieCache.set(routes, trie);\n }\n return trie;\n}\n\nfunction joinRoutePattern(basePattern: string, subPath: string): string {\n if (!subPath) return basePattern;\n return basePattern === \"/\" ? `/${subPath}` : `${basePattern}/${subPath}`;\n}\n\n/**\n * Match a URL against App Router routes.\n */\nexport function matchAppRoute(\n url: string,\n routes: AppRoute[],\n): { route: AppRoute; params: Record<string, string | string[]> } | null {\n const pathname = url.split(\"?\")[0];\n let normalizedUrl = pathname === \"/\" ? \"/\" : pathname.replace(/\\/$/, \"\");\n normalizedUrl = normalizePathnameForRouteMatch(normalizedUrl);\n\n // Split URL once, look up via trie\n const urlParts = normalizedUrl.split(\"/\").filter(Boolean);\n const trie = getOrBuildAppTrie(routes);\n return trieMatch(trie, urlParts);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-router.js","names":[],"sources":["../../src/routing/app-router.ts"],"sourcesContent":["/**\n * App Router file-system routing.\n *\n * Scans the app/ directory following Next.js App Router conventions:\n * - app/page.tsx -> /\n * - app/about/page.tsx -> /about\n * - app/blog/[slug]/page.tsx -> /blog/:slug\n * - app/[...catchAll]/page.tsx -> /:catchAll+\n * - app/route.ts -> / (API route)\n * - app/(group)/page.tsx -> / (route groups are transparent)\n * - Layouts: app/layout.tsx wraps all children\n * - Loading: app/loading.tsx -> Suspense fallback\n * - Error: app/error.tsx -> ErrorBoundary\n * - Not Found: app/not-found.tsx\n */\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { compareRoutes, decodeRouteSegment, normalizePathnameForRouteMatch } from \"./utils.js\";\nimport {\n createValidFileMatcher,\n scanWithExtensions,\n type ValidFileMatcher,\n} from \"./file-matcher.js\";\nimport { validateRoutePatterns } from \"./route-validation.js\";\nimport { buildRouteTrie, trieMatch, type TrieNode } from \"./route-trie.js\";\n\nexport type InterceptingRoute = {\n /** The interception convention: \".\" | \"..\" | \"../..\" | \"...\" */\n convention: string;\n /** The URL pattern this intercepts (e.g. \"/photos/:id\") */\n targetPattern: string;\n /** Absolute path to the intercepting page component */\n pagePath: string;\n /** Parameter names for dynamic segments */\n params: string[];\n};\n\nexport type ParallelSlot = {\n /** Stable slot identity (name + owning directory), used for route serialization keys. */\n key: string;\n /** Slot name (e.g. \"team\" from @team) */\n name: string;\n /** Absolute path to the @slot directory that owns this slot. Internal routing metadata. */\n ownerDir: string;\n /** Absolute path to the slot's page component */\n pagePath: string | null;\n /** Absolute path to the slot's default.tsx fallback */\n defaultPath: string | null;\n /** Absolute path to the slot's layout component (wraps slot content) */\n layoutPath: string | null;\n /** Absolute path to the slot's loading component */\n loadingPath: string | null;\n /** Absolute path to the slot's error component */\n errorPath: string | null;\n /** Intercepting routes within this slot */\n interceptingRoutes: InterceptingRoute[];\n /**\n * The layout index (0-based, in route.layouts[]) that this slot belongs to.\n * Slots are passed as props to the layout at their directory level, not\n * necessarily the innermost layout. -1 means \"innermost\" (legacy default).\n */\n layoutIndex: number;\n /**\n * Filesystem segments from the slot's root directory to its active page.\n * Used at render time to compute segments for useSelectedLayoutSegment(slotName).\n * For a page at the slot root (@team/page.tsx), this is [].\n * For a sub-page (@team/members/page.tsx), this is [\"members\"].\n * null when the slot has no active page (showing default.tsx fallback).\n */\n routeSegments: string[] | null;\n};\n\nexport type AppRoute = {\n /** URL pattern, e.g. \"/\" or \"/about\" or \"/blog/:slug\" */\n pattern: string;\n /** Absolute file path to the page component */\n pagePath: string | null;\n /** Absolute file path to the route handler (route.ts) */\n routePath: string | null;\n /** Ordered list of layout files from root to leaf */\n layouts: string[];\n /** Ordered list of all discovered template files from root to leaf (not necessarily aligned 1:1 with layouts) */\n templates: string[];\n /** Parallel route slots (from @slot directories at the route's directory level) */\n parallelSlots: ParallelSlot[];\n /** Loading component path */\n loadingPath: string | null;\n /** Error component path (leaf directory only) */\n errorPath: string | null;\n /**\n * Per-layout error boundary paths, aligned with the layouts array.\n * Each entry is the error.tsx at the same directory level as the\n * corresponding layout (or null if that level has no error.tsx).\n * Used to interleave ErrorBoundary components with layouts so that\n * ancestor error boundaries catch errors from descendant segments.\n */\n layoutErrorPaths: (string | null)[];\n /** Not-found component path (nearest, walking up from page dir) */\n notFoundPath: string | null;\n /**\n * Not-found component paths per layout level (aligned with layouts array).\n * Each entry is the not-found.tsx at that layout's directory, or null.\n * Used to create per-layout NotFoundBoundary so that notFound() thrown from\n * a layout is caught by the parent layout's boundary (matching Next.js behavior).\n */\n notFoundPaths: (string | null)[];\n /** Forbidden component path (403) */\n forbiddenPath: string | null;\n /** Unauthorized component path (401) */\n unauthorizedPath: string | null;\n /**\n * Filesystem segments from app/ root to the route's directory.\n * Includes route groups and dynamic segments (as template strings like \"[id]\").\n * Used at render time to compute the child segments for useSelectedLayoutSegments().\n */\n routeSegments: string[];\n /** Tree position (directory depth from app/ root) for each template. */\n templateTreePositions?: number[];\n /**\n * Tree position (directory depth from app/ root) for each layout.\n * Used to slice routeSegments and determine which segments are below each layout.\n * For example, root layout = 0, a layout at app/blog/ = 1, app/blog/(group)/ = 2.\n * Unlike the old layoutSegmentDepths, this counts ALL directory levels including\n * route groups and parallel slots.\n */\n layoutTreePositions: number[];\n /** Whether this is a dynamic route */\n isDynamic: boolean;\n /** Parameter names for dynamic segments */\n params: string[];\n /** Pre-split pattern segments (computed once at scan time, reused per request) */\n patternParts: string[];\n};\n\n// Cache for app routes\nlet cachedRoutes: AppRoute[] | null = null;\nlet cachedAppDir: string | null = null;\nlet cachedPageExtensionsKey: string | null = null;\n\nexport function invalidateAppRouteCache(): void {\n cachedRoutes = null;\n cachedAppDir = null;\n cachedPageExtensionsKey = null;\n}\n\nfunction hasParallelSlotDirectory(dir: string): boolean {\n try {\n return fs\n .readdirSync(dir, { withFileTypes: true })\n .some((entry) => entry.isDirectory() && entry.name.startsWith(\"@\"));\n } catch {\n return false;\n }\n}\n\n/**\n * Scan the app/ directory and return a list of routes.\n */\nexport async function appRouter(\n appDir: string,\n pageExtensions?: readonly string[],\n matcher?: ValidFileMatcher,\n): Promise<AppRoute[]> {\n matcher ??= createValidFileMatcher(pageExtensions);\n const pageExtensionsKey = JSON.stringify(matcher.extensions);\n if (cachedRoutes && cachedAppDir === appDir && cachedPageExtensionsKey === pageExtensionsKey) {\n return cachedRoutes;\n }\n\n // Find all page.tsx and route.ts files, excluding @slot directories\n // (slot pages are not standalone routes — they're rendered as props of their parent layout)\n // and _private folders (Next.js convention for colocated non-route files).\n const routes: AppRoute[] = [];\n\n const excludeDir = (name: string) => name.startsWith(\"@\") || name.startsWith(\"_\");\n\n // Process page files in a single pass\n // Use function form of exclude for Node < 22.14 compatibility (string arrays require >= 22.14)\n for await (const file of scanWithExtensions(\"**/page\", appDir, matcher.extensions, excludeDir)) {\n const route = fileToAppRoute(file, appDir, \"page\", matcher);\n if (route) routes.push(route);\n }\n\n // Process route handler files (API routes) in a single pass\n for await (const file of scanWithExtensions(\"**/route\", appDir, matcher.extensions, excludeDir)) {\n const route = fileToAppRoute(file, appDir, \"route\", matcher);\n if (route) routes.push(route);\n }\n\n // Layouts with parallel slot pages are valid route entries even when the\n // segment has no children page. Next.js uses this for modal/feed patterns\n // like app/user/[id]/layout + @feed/page + @modal/default.\n const routePatterns = new Set(routes.map((route) => route.pattern));\n for await (const file of scanWithExtensions(\n \"**/layout\",\n appDir,\n matcher.extensions,\n excludeDir,\n )) {\n const dir = path.dirname(file);\n const routeDir = dir === \".\" ? appDir : path.join(appDir, dir);\n if (!hasParallelSlotDirectory(routeDir)) continue;\n if (discoverParallelSlots(routeDir, appDir, matcher).length === 0) continue;\n\n const route = directoryToAppRoute(dir, appDir, matcher, null, null);\n if (!route || routePatterns.has(route.pattern)) continue;\n\n routes.push(route);\n routePatterns.add(route.pattern);\n }\n\n // Discover sub-routes created by nested pages within parallel slots.\n // In Next.js, pages nested inside @slot directories create additional URL routes.\n // For example, @audience/demographics/page.tsx at app/parallel-routes/ creates\n // a route at /parallel-routes/demographics.\n const slotSubRoutes = discoverSlotSubRoutes(routes, appDir, matcher);\n routes.push(...slotSubRoutes);\n\n validateRoutePatterns(routes.map((route) => route.pattern));\n const interceptTargetPatterns = [\n ...new Set(\n routes.flatMap((route) =>\n route.parallelSlots.flatMap((slot) =>\n slot.interceptingRoutes.map((intercept) => intercept.targetPattern),\n ),\n ),\n ),\n ];\n validateRoutePatterns(interceptTargetPatterns);\n\n // Sort: static routes first, then dynamic, then catch-all\n routes.sort(compareRoutes);\n\n cachedRoutes = routes;\n cachedAppDir = appDir;\n cachedPageExtensionsKey = pageExtensionsKey;\n return routes;\n}\n\n/**\n * Discover sub-routes created by nested pages within parallel slots.\n *\n * In Next.js, pages nested inside @slot directories create additional URL routes.\n * For example, given:\n * app/parallel-routes/@audience/demographics/page.tsx\n * This creates a route at /parallel-routes/demographics where:\n * - children slot → parent's default.tsx\n * - @audience slot → @audience/demographics/page.tsx (matched)\n * - other slots → their default.tsx (fallback)\n */\nfunction discoverSlotSubRoutes(\n routes: AppRoute[],\n _appDir: string,\n matcher: ValidFileMatcher,\n): AppRoute[] {\n const syntheticRoutes: AppRoute[] = [];\n\n // O(1) lookup for existing routes by pattern — avoids O(n) routes.find() per sub-path per parent.\n // Updated as new synthetic routes are pushed so that later parents can see earlier synthetic entries.\n const routesByPattern = new Map<string, AppRoute>(routes.map((r) => [r.pattern, r]));\n\n const applySlotSubPages = (\n route: AppRoute,\n slotPages: Map<string, string>,\n rawSegments: string[],\n ): void => {\n route.parallelSlots = route.parallelSlots.map((slot) => {\n const subPage = slotPages.get(slot.key);\n if (subPage !== undefined) {\n return { ...slot, pagePath: subPage, routeSegments: rawSegments };\n }\n return slot;\n });\n };\n\n for (const parentRoute of routes) {\n if (parentRoute.parallelSlots.length === 0) continue;\n if (!parentRoute.pagePath) continue;\n\n const parentPageDir = path.dirname(parentRoute.pagePath);\n\n // Collect sub-paths from all slots.\n // Map: normalized visible sub-path -> slot pages, raw filesystem segments (for routeSegments),\n // and the pre-computed convertedSubRoute (to avoid a redundant re-conversion in the merge loop).\n const subPathMap = new Map<\n string,\n {\n // Raw filesystem segments (with route groups, @slots, etc.) used for routeSegments so\n // that useSelectedLayoutSegments() sees the correct segment list at runtime.\n rawSegments: string[];\n // Pre-computed URL parts, params, isDynamic from convertSegmentsToRouteParts.\n converted: { urlSegments: string[]; params: string[]; isDynamic: boolean };\n slotPages: Map<string, string>;\n }\n >();\n\n for (const slot of parentRoute.parallelSlots) {\n // Only scan sub-pages from slots owned by this route directory.\n // Inherited slots with the same name live in different owner dirs.\n if (path.dirname(slot.ownerDir) !== parentPageDir) {\n continue;\n }\n const slotDir = slot.ownerDir;\n if (!fs.existsSync(slotDir)) continue;\n\n const subPages = findSlotSubPages(slotDir, matcher);\n for (const { relativePath, pagePath } of subPages) {\n const subSegments = relativePath.split(path.sep);\n const convertedSubRoute = convertSegmentsToRouteParts(subSegments);\n if (!convertedSubRoute) continue;\n\n const { urlSegments } = convertedSubRoute;\n const normalizedSubPath = urlSegments.join(\"/\");\n let subPathEntry = subPathMap.get(normalizedSubPath);\n\n if (!subPathEntry) {\n subPathEntry = {\n rawSegments: subSegments,\n converted: convertedSubRoute,\n slotPages: new Map(),\n };\n subPathMap.set(normalizedSubPath, subPathEntry);\n }\n\n const existingSlotPage = subPathEntry.slotPages.get(slot.key);\n if (existingSlotPage) {\n const pattern = joinRoutePattern(parentRoute.pattern, normalizedSubPath);\n throw new Error(\n `You cannot have two routes that resolve to the same path (\"${pattern}\").`,\n );\n }\n\n subPathEntry.slotPages.set(slot.key, pagePath);\n }\n }\n\n if (subPathMap.size === 0) continue;\n\n // Find the default.tsx for the children slot at the parent directory\n const childrenDefault = findFile(parentPageDir, \"default\", matcher);\n if (!childrenDefault) continue;\n\n for (const { rawSegments, converted: convertedSubRoute, slotPages } of subPathMap.values()) {\n const {\n urlSegments: urlParts,\n params: subParams,\n isDynamic: subIsDynamic,\n } = convertedSubRoute;\n\n const subUrlPath = urlParts.join(\"/\");\n const pattern = joinRoutePattern(parentRoute.pattern, subUrlPath);\n\n const existingRoute = routesByPattern.get(pattern);\n if (existingRoute) {\n if (existingRoute.routePath && !existingRoute.pagePath) {\n throw new Error(\n `You cannot have two routes that resolve to the same path (\"${pattern}\").`,\n );\n }\n applySlotSubPages(existingRoute, slotPages, rawSegments);\n continue;\n }\n\n // Build parallel slots for this sub-route: matching slots get the sub-page,\n // non-matching slots get null pagePath (rendering falls back to defaultPath)\n const subSlots: ParallelSlot[] = parentRoute.parallelSlots.map((slot) => {\n const subPage = slotPages.get(slot.key);\n return {\n ...slot,\n pagePath: subPage || null,\n routeSegments: subPage ? rawSegments : null,\n };\n });\n\n const newRoute: AppRoute = {\n pattern,\n pagePath: childrenDefault, // children slot uses parent's default.tsx as page\n routePath: null,\n layouts: parentRoute.layouts,\n templates: parentRoute.templates,\n parallelSlots: subSlots,\n loadingPath: parentRoute.loadingPath,\n errorPath: parentRoute.errorPath,\n layoutErrorPaths: parentRoute.layoutErrorPaths,\n notFoundPath: parentRoute.notFoundPath,\n notFoundPaths: parentRoute.notFoundPaths,\n forbiddenPath: parentRoute.forbiddenPath,\n unauthorizedPath: parentRoute.unauthorizedPath,\n routeSegments: [...parentRoute.routeSegments, ...rawSegments],\n templateTreePositions: parentRoute.templateTreePositions,\n layoutTreePositions: parentRoute.layoutTreePositions,\n isDynamic: parentRoute.isDynamic || subIsDynamic,\n params: [...parentRoute.params, ...subParams],\n patternParts: [...parentRoute.patternParts, ...urlParts],\n };\n syntheticRoutes.push(newRoute);\n routesByPattern.set(pattern, newRoute);\n }\n }\n\n return syntheticRoutes;\n}\n\n/**\n * Find all page files in subdirectories of a parallel slot directory.\n * Returns relative paths (from the slot dir) and absolute page paths.\n * Skips the root page.tsx (already handled as the slot's main page)\n * and intercepting route directories.\n */\nfunction findSlotSubPages(\n slotDir: string,\n matcher: ValidFileMatcher,\n): Array<{ relativePath: string; pagePath: string }> {\n const results: Array<{ relativePath: string; pagePath: string }> = [];\n\n function scan(dir: string): void {\n if (!fs.existsSync(dir)) return;\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Skip intercepting route directories\n if (matchInterceptConvention(entry.name)) continue;\n // Skip private folders (prefixed with _)\n if (entry.name.startsWith(\"_\")) continue;\n\n const subDir = path.join(dir, entry.name);\n const page = findFile(subDir, \"page\", matcher);\n if (page) {\n const relativePath = path.relative(slotDir, subDir);\n results.push({ relativePath, pagePath: page });\n }\n // Continue scanning deeper for nested sub-pages\n scan(subDir);\n }\n }\n\n scan(slotDir);\n return results;\n}\n\n/**\n * Convert a file path relative to app/ into an AppRoute.\n */\nfunction fileToAppRoute(\n file: string,\n appDir: string,\n type: \"page\" | \"route\",\n matcher: ValidFileMatcher,\n): AppRoute | null {\n // Remove the filename (page.tsx or route.ts)\n const dir = path.dirname(file);\n return directoryToAppRoute(\n dir,\n appDir,\n matcher,\n type === \"page\" ? path.join(appDir, file) : null,\n type === \"route\" ? path.join(appDir, file) : null,\n );\n}\n\nfunction directoryToAppRoute(\n dir: string,\n appDir: string,\n matcher: ValidFileMatcher,\n pagePath: string | null,\n routePath: string | null,\n): AppRoute | null {\n const segments = dir === \".\" ? [] : dir.split(path.sep);\n\n const params: string[] = [];\n let isDynamic = false;\n\n const convertedRoute = convertSegmentsToRouteParts(segments);\n if (!convertedRoute) return null;\n\n const { urlSegments, params: routeParams, isDynamic: routeIsDynamic } = convertedRoute;\n params.push(...routeParams);\n isDynamic = routeIsDynamic;\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n\n // Discover layouts and templates from root to leaf\n const layouts = discoverLayouts(segments, appDir, matcher);\n const templates = discoverTemplates(segments, appDir, matcher);\n const templateTreePositions = computeLayoutTreePositions(appDir, templates);\n\n // Compute the tree position (directory depth) for each layout.\n const layoutTreePositions = computeLayoutTreePositions(appDir, layouts);\n\n // Discover per-layout error boundaries (aligned with layouts array).\n // In Next.js, each segment independently wraps its children with an ErrorBoundary.\n // This array enables interleaving error boundaries with layouts in the rendering.\n const layoutErrorPaths = discoverLayoutAlignedErrors(segments, appDir, matcher);\n\n // Discover loading, error in the route's directory\n const routeDir = dir === \".\" ? appDir : path.join(appDir, dir);\n const loadingPath = findFile(routeDir, \"loading\", matcher);\n const errorPath = findFile(routeDir, \"error\", matcher);\n\n // Discover not-found/forbidden/unauthorized: walk from route directory up to root (nearest wins).\n const notFoundPath = discoverBoundaryFile(segments, appDir, \"not-found\", matcher);\n const forbiddenPath = discoverBoundaryFile(segments, appDir, \"forbidden\", matcher);\n const unauthorizedPath = discoverBoundaryFile(segments, appDir, \"unauthorized\", matcher);\n\n // Discover per-layout not-found files (one per layout directory).\n // These are used for per-layout NotFoundBoundary to match Next.js behavior where\n // notFound() thrown from a layout is caught by the parent layout's boundary.\n const notFoundPaths = discoverBoundaryFilePerLayout(layouts, \"not-found\", matcher);\n\n // Discover parallel slots (@team, @analytics, etc.).\n // Slots at the route's own directory use page.tsx; slots at ancestor directories\n // (inherited from parent layouts) use default.tsx as fallback.\n const parallelSlots = discoverInheritedParallelSlots(segments, appDir, routeDir, matcher);\n\n return {\n pattern: pattern === \"/\" ? \"/\" : pattern,\n pagePath,\n routePath,\n layouts,\n templates,\n parallelSlots,\n loadingPath,\n errorPath,\n layoutErrorPaths,\n notFoundPath,\n notFoundPaths,\n forbiddenPath,\n unauthorizedPath,\n routeSegments: segments,\n templateTreePositions,\n layoutTreePositions,\n isDynamic,\n params,\n patternParts: urlSegments,\n };\n}\n\n/**\n * Compute the tree position (directory depth from app root) for each layout.\n * Root layout = 0, a layout at app/blog/ = 1, app/blog/(group)/ = 2.\n * Counts ALL directory levels including route groups and parallel slots.\n */\nfunction computeLayoutTreePositions(appDir: string, layouts: string[]): number[] {\n return layouts.map((layoutPath) => {\n const layoutDir = path.dirname(layoutPath);\n if (layoutDir === appDir) return 0;\n const relative = path.relative(appDir, layoutDir);\n return relative.split(path.sep).length;\n });\n}\n\n/**\n * Discover all layout files from root to the given directory.\n * Each level of the directory tree may have a layout.tsx.\n */\nfunction discoverLayouts(segments: string[], appDir: string, matcher: ValidFileMatcher): string[] {\n const layouts: string[] = [];\n\n // Check root layout\n const rootLayout = findFile(appDir, \"layout\", matcher);\n if (rootLayout) layouts.push(rootLayout);\n\n // Check each directory level\n let currentDir = appDir;\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n const layout = findFile(currentDir, \"layout\", matcher);\n if (layout) layouts.push(layout);\n }\n\n return layouts;\n}\n\n/**\n * Discover all template files from root to the given directory.\n * Each level of the directory tree may have a template.tsx.\n * Templates are like layouts but re-mount on navigation.\n */\nfunction discoverTemplates(\n segments: string[],\n appDir: string,\n matcher: ValidFileMatcher,\n): string[] {\n const templates: string[] = [];\n\n // Check root template\n const rootTemplate = findFile(appDir, \"template\", matcher);\n if (rootTemplate) templates.push(rootTemplate);\n\n // Check each directory level\n let currentDir = appDir;\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n const template = findFile(currentDir, \"template\", matcher);\n if (template) templates.push(template);\n }\n\n return templates;\n}\n\n/**\n * Discover error.tsx files aligned with the layouts array.\n * Walks the same directory levels as discoverLayouts and, for each level\n * that contributes a layout entry, checks whether error.tsx also exists.\n * Returns an array of the same length as discoverLayouts() would return,\n * with the error path (or null) at each corresponding layout level.\n *\n * This enables interleaving ErrorBoundary components with layouts in the\n * rendering tree, matching Next.js behavior where each segment independently\n * wraps its children with an error boundary.\n */\nfunction discoverLayoutAlignedErrors(\n segments: string[],\n appDir: string,\n matcher: ValidFileMatcher,\n): (string | null)[] {\n const errors: (string | null)[] = [];\n\n // Root level (only if root has a layout — matching discoverLayouts logic)\n const rootLayout = findFile(appDir, \"layout\", matcher);\n if (rootLayout) {\n errors.push(findFile(appDir, \"error\", matcher));\n }\n\n // Check each directory level\n let currentDir = appDir;\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n const layout = findFile(currentDir, \"layout\", matcher);\n if (layout) {\n errors.push(findFile(currentDir, \"error\", matcher));\n }\n }\n\n return errors;\n}\n\n/**\n * Discover the nearest boundary file (not-found, forbidden, unauthorized)\n * by walking from the route's directory up to the app root.\n * Returns the first (closest) file found, or null.\n */\nfunction discoverBoundaryFile(\n segments: string[],\n appDir: string,\n fileName: string,\n matcher: ValidFileMatcher,\n): string | null {\n // Build all directory paths from leaf to root\n const dirs: string[] = [];\n let dir = appDir;\n dirs.push(dir);\n for (const segment of segments) {\n dir = path.join(dir, segment);\n dirs.push(dir);\n }\n\n // Walk from leaf (last) to root (first)\n for (let i = dirs.length - 1; i >= 0; i--) {\n const f = findFile(dirs[i], fileName, matcher);\n if (f) return f;\n }\n return null;\n}\n\n/**\n * Discover boundary files (not-found, forbidden, unauthorized) at each layout directory.\n * Returns an array aligned with the layouts array, where each entry is the boundary\n * file at that layout's directory, or null if none exists there.\n *\n * This is used for per-layout error boundaries. In Next.js, each layout level\n * has its own boundary that wraps the layout's children. When notFound() is thrown\n * from a layout, it propagates up to the parent layout's boundary.\n */\nfunction discoverBoundaryFilePerLayout(\n layouts: string[],\n fileName: string,\n matcher: ValidFileMatcher,\n): (string | null)[] {\n return layouts.map((layoutPath) => {\n const layoutDir = path.dirname(layoutPath);\n return findFile(layoutDir, fileName, matcher);\n });\n}\n\n/**\n * Discover parallel slots inherited from ancestor directories.\n *\n * In Next.js, parallel slots belong to the layout that defines them. When a\n * child route is rendered, its parent layout's slots must still be present.\n * If the child doesn't have matching content in a slot, the slot's default.tsx\n * is rendered instead.\n *\n * Walk from appDir through each segment to the route's directory. At each level\n * that has @slot dirs, collect them. Slots at the route's own directory level\n * use page.tsx; slots at ancestor levels use default.tsx only.\n */\nfunction discoverInheritedParallelSlots(\n segments: string[],\n appDir: string,\n routeDir: string,\n matcher: ValidFileMatcher,\n): ParallelSlot[] {\n const slotMap = new Map<string, ParallelSlot>();\n\n // Walk from appDir through each segment, tracking layout indices.\n // layoutIndex tracks which position in the route's layouts[] array corresponds\n // to a given directory. Only directories with a layout.tsx file increment.\n let currentDir = appDir;\n const dirsToCheck: { dir: string; layoutIdx: number }[] = [];\n let layoutIdx = findFile(appDir, \"layout\", matcher) ? 0 : -1;\n dirsToCheck.push({ dir: appDir, layoutIdx: Math.max(layoutIdx, 0) });\n\n for (const segment of segments) {\n currentDir = path.join(currentDir, segment);\n if (findFile(currentDir, \"layout\", matcher)) {\n layoutIdx++;\n }\n dirsToCheck.push({ dir: currentDir, layoutIdx: Math.max(layoutIdx, 0) });\n }\n\n for (const { dir, layoutIdx: lvlLayoutIdx } of dirsToCheck) {\n const isOwnDir = dir === routeDir;\n const slotsAtLevel = discoverParallelSlots(dir, appDir, matcher);\n\n for (const slot of slotsAtLevel) {\n if (isOwnDir) {\n // At the route's own directory: use page.tsx (normal behavior)\n slot.layoutIndex = lvlLayoutIdx;\n slotMap.set(slot.key, slot);\n } else {\n // At an ancestor directory: use default.tsx as the page, not page.tsx\n // (the slot's page.tsx is for the parent route, not this child route)\n const inheritedSlot: ParallelSlot = {\n ...slot,\n pagePath: null, // Don't use ancestor's page.tsx\n layoutIndex: lvlLayoutIdx,\n routeSegments: null,\n // defaultPath, loadingPath, errorPath, interceptingRoutes remain\n };\n slotMap.set(slot.key, inheritedSlot);\n }\n }\n }\n\n return Array.from(slotMap.values());\n}\n\n/**\n * Discover parallel route slots (@team, @analytics, etc.) in a directory.\n * Returns a ParallelSlot for each @-prefixed subdirectory that has a page or default component.\n */\nfunction discoverParallelSlots(\n dir: string,\n appDir: string,\n matcher: ValidFileMatcher,\n): ParallelSlot[] {\n if (!fs.existsSync(dir)) return [];\n\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n const slots: ParallelSlot[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.startsWith(\"@\")) continue;\n\n const slotName = entry.name.slice(1); // \"@team\" -> \"team\"\n const slotDir = path.join(dir, entry.name);\n\n const pagePath = findFile(slotDir, \"page\", matcher);\n const defaultPath = findFile(slotDir, \"default\", matcher);\n const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);\n\n // Only include slots that have at least a page, default, or intercepting route\n if (!pagePath && !defaultPath && interceptingRoutes.length === 0) continue;\n\n slots.push({\n key: `${slotName}@${path.relative(appDir, slotDir).replace(/\\\\/g, \"/\")}`,\n name: slotName,\n ownerDir: slotDir,\n pagePath,\n defaultPath,\n layoutPath: findFile(slotDir, \"layout\", matcher),\n loadingPath: findFile(slotDir, \"loading\", matcher),\n errorPath: findFile(slotDir, \"error\", matcher),\n interceptingRoutes,\n layoutIndex: -1, // Will be set by discoverInheritedParallelSlots\n routeSegments: pagePath ? [] : null,\n });\n }\n\n return slots;\n}\n\n/**\n * The interception convention prefix patterns.\n * (.) — same level, (..) — one level up, (..)(..)\" — two levels up, (...) — root\n */\nconst INTERCEPT_PATTERNS = [\n { prefix: \"(...)\", convention: \"...\" },\n { prefix: \"(..)(..)\", convention: \"../..\" },\n { prefix: \"(..)\", convention: \"..\" },\n { prefix: \"(.)\", convention: \".\" },\n] as const;\n\n/**\n * Discover intercepting routes inside a parallel slot directory.\n *\n * Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.\n * They intercept navigation to another route and render within the slot instead.\n *\n * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)\n * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)\n * @param appDir - The root app directory\n */\nfunction discoverInterceptingRoutes(\n slotDir: string,\n routeDir: string,\n appDir: string,\n matcher: ValidFileMatcher,\n): InterceptingRoute[] {\n if (!fs.existsSync(slotDir)) return [];\n\n const results: InterceptingRoute[] = [];\n\n // Recursively scan for page files inside intercepting directories\n scanForInterceptingPages(slotDir, routeDir, appDir, results, matcher);\n\n return results;\n}\n\n/**\n * Recursively scan a directory tree for page.tsx files that are inside\n * intercepting route directories.\n */\nfunction scanForInterceptingPages(\n currentDir: string,\n routeDir: string,\n appDir: string,\n results: InterceptingRoute[],\n matcher: ValidFileMatcher,\n): void {\n if (!fs.existsSync(currentDir)) return;\n\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Skip private folders (prefixed with _)\n if (entry.name.startsWith(\"_\")) continue;\n\n // Check if this directory name starts with an interception convention\n const interceptMatch = matchInterceptConvention(entry.name);\n\n if (interceptMatch) {\n // This directory is the start of an intercepting route\n // e.g. \"(.)photos\" means intercept same-level \"photos\" route\n const restOfName = entry.name.slice(interceptMatch.prefix.length);\n const interceptDir = path.join(currentDir, entry.name);\n\n // Find page files within this intercepting directory tree\n collectInterceptingPages(\n interceptDir,\n interceptDir,\n interceptMatch.convention,\n restOfName,\n routeDir,\n appDir,\n results,\n matcher,\n );\n } else {\n // Regular subdirectory — keep scanning for intercepting dirs\n scanForInterceptingPages(\n path.join(currentDir, entry.name),\n routeDir,\n appDir,\n results,\n matcher,\n );\n }\n }\n}\n\n/**\n * Match a directory name against interception convention prefixes.\n */\nfunction matchInterceptConvention(name: string): { prefix: string; convention: string } | null {\n for (const pattern of INTERCEPT_PATTERNS) {\n if (name.startsWith(pattern.prefix)) {\n return pattern;\n }\n }\n return null;\n}\n\n/**\n * Collect page.tsx files inside an intercepting route directory tree\n * and compute their target URL patterns.\n */\nfunction collectInterceptingPages(\n currentDir: string,\n interceptRoot: string,\n convention: string,\n interceptSegment: string,\n routeDir: string,\n appDir: string,\n results: InterceptingRoute[],\n matcher: ValidFileMatcher,\n): void {\n // Check for page.tsx in current directory\n const page = findFile(currentDir, \"page\", matcher);\n if (page) {\n const targetPattern = computeInterceptTarget(\n convention,\n interceptSegment,\n currentDir,\n interceptRoot,\n routeDir,\n appDir,\n );\n if (targetPattern) {\n results.push({\n convention,\n targetPattern: targetPattern.pattern,\n pagePath: page,\n params: targetPattern.params,\n });\n }\n }\n\n // Recurse into subdirectories for nested intercepting routes\n if (!fs.existsSync(currentDir)) return;\n const entries = fs.readdirSync(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n // Skip private folders (prefixed with _)\n if (entry.name.startsWith(\"_\")) continue;\n collectInterceptingPages(\n path.join(currentDir, entry.name),\n interceptRoot,\n convention,\n interceptSegment,\n routeDir,\n appDir,\n results,\n matcher,\n );\n }\n}\n\n/**\n * Check whether a path segment is invisible in the URL (route groups, parallel slots, \".\").\n *\n * Used by computeInterceptTarget, convertSegmentsToRouteParts, and\n * hasRemainingVisibleSegments — keep this the single source of truth.\n */\nfunction isInvisibleSegment(segment: string): boolean {\n if (segment === \".\") return true;\n if (segment.startsWith(\"(\") && segment.endsWith(\")\")) return true;\n if (segment.startsWith(\"@\")) return true;\n return false;\n}\n\n/**\n * Compute the target URL pattern for an intercepting route.\n *\n * Interception conventions (..), (..)(..)\" climb by *visible route segments*\n * (not filesystem directories). Route groups like (marketing) and parallel\n * slots like @modal are invisible and must be skipped when counting levels.\n *\n * - (.) same level: resolve relative to routeDir\n * - (..) one level up: climb 1 visible segment\n * - (..)(..) two levels up: climb 2 visible segments\n * - (...) root: resolve from appDir\n */\nfunction computeInterceptTarget(\n convention: string,\n interceptSegment: string,\n currentDir: string,\n interceptRoot: string,\n routeDir: string,\n appDir: string,\n): { pattern: string; params: string[] } | null {\n // Determine the base segments for target resolution.\n // We work on route segments (not filesystem paths) so that route groups\n // and parallel slots are properly skipped when climbing.\n const routeSegments = path.relative(appDir, routeDir).split(path.sep).filter(Boolean);\n\n let baseParts: string[];\n switch (convention) {\n case \".\":\n baseParts = routeSegments;\n break;\n case \"..\":\n case \"../..\": {\n const levelsToClimb = convention === \"..\" ? 1 : 2;\n let climbed = 0;\n let cutIndex = routeSegments.length;\n while (cutIndex > 0 && climbed < levelsToClimb) {\n cutIndex--;\n if (!isInvisibleSegment(routeSegments[cutIndex])) {\n climbed++;\n }\n }\n baseParts = routeSegments.slice(0, cutIndex);\n break;\n }\n case \"...\":\n baseParts = [];\n break;\n default:\n return null;\n }\n\n // Add the intercept segment and any nested path segments\n const nestedParts = path.relative(interceptRoot, currentDir).split(path.sep).filter(Boolean);\n const allSegments = [...baseParts, interceptSegment, ...nestedParts];\n\n const convertedTarget = convertSegmentsToRouteParts(allSegments);\n if (!convertedTarget) return null;\n\n const { urlSegments, params } = convertedTarget;\n\n const pattern = \"/\" + urlSegments.join(\"/\");\n return { pattern: pattern === \"/\" ? \"/\" : pattern, params };\n}\n\n/**\n * Find a file by name (without extension) in a directory.\n * Checks configured pageExtensions.\n */\nfunction findFile(dir: string, name: string, matcher: ValidFileMatcher): string | null {\n for (const ext of matcher.dottedExtensions) {\n const filePath = path.join(dir, name + ext);\n if (fs.existsSync(filePath)) return filePath;\n }\n return null;\n}\n\n/**\n * Convert filesystem path segments to URL route parts, skipping invisible segments\n * (route groups, @slots, \".\") and converting dynamic segment syntax to Express-style\n * patterns (e.g. \"[id]\" → \":id\", \"[...slug]\" → \":slug+\").\n */\nfunction convertSegmentsToRouteParts(\n segments: string[],\n): { urlSegments: string[]; params: string[]; isDynamic: boolean } | null {\n const urlSegments: string[] = [];\n const params: string[] = [];\n let isDynamic = false;\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n\n if (isInvisibleSegment(segment)) continue;\n\n // Catch-all segments are only valid in terminal URL position.\n const catchAllMatch = segment.match(/^\\[\\.\\.\\.([\\w-]+)\\]$/);\n if (catchAllMatch) {\n if (hasRemainingVisibleSegments(segments, i + 1)) return null;\n isDynamic = true;\n params.push(catchAllMatch[1]);\n urlSegments.push(`:${catchAllMatch[1]}+`);\n continue;\n }\n\n const optionalCatchAllMatch = segment.match(/^\\[\\[\\.\\.\\.([\\w-]+)\\]\\]$/);\n if (optionalCatchAllMatch) {\n if (hasRemainingVisibleSegments(segments, i + 1)) return null;\n isDynamic = true;\n params.push(optionalCatchAllMatch[1]);\n urlSegments.push(`:${optionalCatchAllMatch[1]}*`);\n continue;\n }\n\n const dynamicMatch = segment.match(/^\\[([\\w-]+)\\]$/);\n if (dynamicMatch) {\n isDynamic = true;\n params.push(dynamicMatch[1]);\n urlSegments.push(`:${dynamicMatch[1]}`);\n continue;\n }\n\n urlSegments.push(decodeRouteSegment(segment));\n }\n\n return { urlSegments, params, isDynamic };\n}\n\nfunction hasRemainingVisibleSegments(segments: string[], startIndex: number): boolean {\n for (let i = startIndex; i < segments.length; i++) {\n if (!isInvisibleSegment(segments[i])) return true;\n }\n return false;\n}\n\n// Trie cache — keyed by route array identity (same array = same trie)\nconst appTrieCache = new WeakMap<AppRoute[], TrieNode<AppRoute>>();\n\nfunction getOrBuildAppTrie(routes: AppRoute[]): TrieNode<AppRoute> {\n let trie = appTrieCache.get(routes);\n if (!trie) {\n trie = buildRouteTrie(routes);\n appTrieCache.set(routes, trie);\n }\n return trie;\n}\n\nfunction joinRoutePattern(basePattern: string, subPath: string): string {\n if (!subPath) return basePattern;\n return basePattern === \"/\" ? `/${subPath}` : `${basePattern}/${subPath}`;\n}\n\n/**\n * Match a URL against App Router routes.\n */\nexport function matchAppRoute(\n url: string,\n routes: AppRoute[],\n): { route: AppRoute; params: Record<string, string | string[]> } | null {\n const pathname = url.split(\"?\")[0];\n let normalizedUrl = pathname === \"/\" ? \"/\" : pathname.replace(/\\/$/, \"\");\n normalizedUrl = normalizePathnameForRouteMatch(normalizedUrl);\n\n // Split URL once, look up via trie\n const urlParts = normalizedUrl.split(\"/\").filter(Boolean);\n const trie = getOrBuildAppTrie(routes);\n return trieMatch(trie, urlParts);\n}\n"],"mappings":"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"}
|