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