@rangojs/router 0.0.0-experimental.002d056c
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/AGENTS.md +9 -0
- package/README.md +899 -0
- package/dist/bin/rango.js +1606 -0
- package/dist/vite/index.js +5153 -0
- package/package.json +177 -0
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/cache-guide/SKILL.md +262 -0
- package/skills/caching/SKILL.md +253 -0
- package/skills/composability/SKILL.md +172 -0
- package/skills/debug-manifest/SKILL.md +112 -0
- package/skills/document-cache/SKILL.md +182 -0
- package/skills/fonts/SKILL.md +167 -0
- package/skills/hooks/SKILL.md +704 -0
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +313 -0
- package/skills/layout/SKILL.md +310 -0
- package/skills/links/SKILL.md +239 -0
- package/skills/loader/SKILL.md +596 -0
- package/skills/middleware/SKILL.md +339 -0
- package/skills/mime-routes/SKILL.md +128 -0
- package/skills/parallel/SKILL.md +305 -0
- package/skills/prerender/SKILL.md +643 -0
- package/skills/rango/SKILL.md +118 -0
- package/skills/response-routes/SKILL.md +411 -0
- package/skills/route/SKILL.md +385 -0
- package/skills/router-setup/SKILL.md +439 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +79 -0
- package/skills/typesafety/SKILL.md +623 -0
- package/skills/use-cache/SKILL.md +324 -0
- package/src/__internal.ts +273 -0
- package/src/bin/rango.ts +321 -0
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/action-response-classifier.ts +99 -0
- package/src/browser/event-controller.ts +899 -0
- package/src/browser/history-state.ts +80 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/link-interceptor.ts +141 -0
- package/src/browser/logging.ts +55 -0
- package/src/browser/merge-segment-loaders.ts +134 -0
- package/src/browser/navigation-bridge.ts +638 -0
- package/src/browser/navigation-client.ts +261 -0
- package/src/browser/navigation-store.ts +806 -0
- package/src/browser/navigation-transaction.ts +297 -0
- package/src/browser/network-error-handler.ts +61 -0
- package/src/browser/partial-update.ts +582 -0
- package/src/browser/prefetch/cache.ts +206 -0
- package/src/browser/prefetch/fetch.ts +145 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +128 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +368 -0
- package/src/browser/react/NavigationProvider.tsx +413 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +59 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +162 -0
- package/src/browser/react/location-state.ts +107 -0
- package/src/browser/react/mount-context.ts +37 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +218 -0
- package/src/browser/react/use-client-cache.ts +58 -0
- package/src/browser/react/use-handle.ts +162 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +135 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +99 -0
- package/src/browser/react/use-params.ts +65 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-router.ts +63 -0
- package/src/browser/react/use-search-params.ts +56 -0
- package/src/browser/react/use-segments.ts +171 -0
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +464 -0
- package/src/browser/scroll-restoration.ts +397 -0
- package/src/browser/segment-reconciler.ts +216 -0
- package/src/browser/segment-structure-assert.ts +83 -0
- package/src/browser/server-action-bridge.ts +667 -0
- package/src/browser/shallow.ts +40 -0
- package/src/browser/types.ts +547 -0
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +438 -0
- package/src/build/generate-route-types.ts +36 -0
- package/src/build/index.ts +35 -0
- package/src/build/route-trie.ts +265 -0
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +98 -0
- package/src/build/route-types/codegen.ts +102 -0
- package/src/build/route-types/include-resolution.ts +411 -0
- package/src/build/route-types/param-extraction.ts +48 -0
- package/src/build/route-types/per-module-writer.ts +128 -0
- package/src/build/route-types/router-processing.ts +479 -0
- package/src/build/route-types/scan-filter.ts +78 -0
- package/src/build/runtime-discovery.ts +231 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +338 -0
- package/src/cache/cache-scope.ts +382 -0
- package/src/cache/cf/cf-cache-store.ts +982 -0
- package/src/cache/cf/index.ts +29 -0
- package/src/cache/document-cache.ts +369 -0
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +41 -0
- package/src/cache/index.ts +44 -0
- package/src/cache/memory-segment-store.ts +328 -0
- package/src/cache/profile-registry.ts +73 -0
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +256 -0
- package/src/cache/taint.ts +98 -0
- package/src/cache/types.ts +342 -0
- package/src/client.rsc.tsx +85 -0
- package/src/client.tsx +601 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +27 -0
- package/src/context-var.ts +86 -0
- package/src/debug.ts +243 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +365 -0
- package/src/handle.ts +135 -0
- package/src/handles/MetaTags.tsx +246 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +7 -0
- package/src/handles/meta.ts +264 -0
- package/src/host/cookie-handler.ts +165 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +53 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +352 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +146 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +222 -0
- package/src/index.rsc.ts +233 -0
- package/src/index.ts +277 -0
- package/src/internal-debug.ts +11 -0
- package/src/loader.rsc.ts +89 -0
- package/src/loader.ts +64 -0
- package/src/network-error-thrower.tsx +23 -0
- package/src/outlet-context.ts +15 -0
- package/src/outlet-provider.tsx +45 -0
- package/src/prerender/param-hash.ts +37 -0
- package/src/prerender/store.ts +185 -0
- package/src/prerender.ts +463 -0
- package/src/reverse.ts +330 -0
- package/src/root-error-boundary.tsx +289 -0
- package/src/route-content-wrapper.tsx +196 -0
- package/src/route-definition/dsl-helpers.ts +934 -0
- package/src/route-definition/helper-factories.ts +200 -0
- package/src/route-definition/helpers-types.ts +430 -0
- package/src/route-definition/index.ts +52 -0
- package/src/route-definition/redirect.ts +93 -0
- package/src/route-definition.ts +1 -0
- package/src/route-map-builder.ts +281 -0
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +259 -0
- package/src/router/content-negotiation.ts +116 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/find-match.ts +160 -0
- package/src/router/handler-context.ts +451 -0
- package/src/router/intercept-resolution.ts +397 -0
- package/src/router/lazy-includes.ts +236 -0
- package/src/router/loader-resolution.ts +420 -0
- package/src/router/logging.ts +251 -0
- package/src/router/manifest.ts +269 -0
- package/src/router/match-api.ts +620 -0
- package/src/router/match-context.ts +266 -0
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +223 -0
- package/src/router/match-middleware/cache-lookup.ts +634 -0
- package/src/router/match-middleware/cache-store.ts +295 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +306 -0
- package/src/router/match-middleware/segment-resolution.ts +193 -0
- package/src/router/match-pipelines.ts +179 -0
- package/src/router/match-result.ts +219 -0
- package/src/router/metrics.ts +282 -0
- package/src/router/middleware-cookies.ts +55 -0
- package/src/router/middleware-types.ts +222 -0
- package/src/router/middleware.ts +749 -0
- package/src/router/pattern-matching.ts +563 -0
- package/src/router/prerender-match.ts +402 -0
- package/src/router/preview-match.ts +170 -0
- package/src/router/revalidation.ts +289 -0
- package/src/router/router-context.ts +320 -0
- package/src/router/router-interfaces.ts +452 -0
- package/src/router/router-options.ts +592 -0
- package/src/router/router-registry.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +570 -0
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +198 -0
- package/src/router/segment-resolution/revalidation.ts +1242 -0
- package/src/router/segment-resolution/static-store.ts +67 -0
- package/src/router/segment-resolution.ts +21 -0
- package/src/router/segment-wrappers.ts +291 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +239 -0
- package/src/router/types.ts +170 -0
- package/src/router.ts +1006 -0
- package/src/rsc/handler-context.ts +45 -0
- package/src/rsc/handler.ts +1089 -0
- package/src/rsc/helpers.ts +198 -0
- package/src/rsc/index.ts +36 -0
- package/src/rsc/loader-fetch.ts +209 -0
- package/src/rsc/manifest-init.ts +86 -0
- package/src/rsc/nonce.ts +32 -0
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +379 -0
- package/src/rsc/response-error.ts +37 -0
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +237 -0
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +348 -0
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +263 -0
- package/src/search-params.ts +230 -0
- package/src/segment-system.tsx +454 -0
- package/src/server/context.ts +591 -0
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +37 -0
- package/src/server/handle-store.ts +308 -0
- package/src/server/loader-registry.ts +133 -0
- package/src/server/request-context.ts +920 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +51 -0
- package/src/ssr/index.tsx +365 -0
- package/src/static-handler.ts +114 -0
- package/src/theme/ThemeProvider.tsx +297 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +62 -0
- package/src/theme/index.ts +48 -0
- package/src/theme/theme-context.ts +44 -0
- package/src/theme/theme-script.ts +155 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types/boundaries.ts +158 -0
- package/src/types/cache-types.ts +198 -0
- package/src/types/error-types.ts +192 -0
- package/src/types/global-namespace.ts +100 -0
- package/src/types/handler-context.ts +687 -0
- package/src/types/index.ts +88 -0
- package/src/types/loader-types.ts +183 -0
- package/src/types/route-config.ts +170 -0
- package/src/types/route-entry.ts +109 -0
- package/src/types/segments.ts +148 -0
- package/src/types.ts +1 -0
- package/src/urls/include-helper.ts +197 -0
- package/src/urls/index.ts +53 -0
- package/src/urls/path-helper-types.ts +339 -0
- package/src/urls/path-helper.ts +329 -0
- package/src/urls/pattern-types.ts +95 -0
- package/src/urls/response-types.ts +106 -0
- package/src/urls/type-extraction.ts +372 -0
- package/src/urls/urls-function.ts +98 -0
- package/src/urls.ts +1 -0
- package/src/use-loader.tsx +354 -0
- package/src/vite/discovery/bundle-postprocess.ts +184 -0
- package/src/vite/discovery/discover-routers.ts +344 -0
- package/src/vite/discovery/prerender-collection.ts +385 -0
- package/src/vite/discovery/route-types-writer.ts +258 -0
- package/src/vite/discovery/self-gen-tracking.ts +47 -0
- package/src/vite/discovery/state.ts +108 -0
- package/src/vite/discovery/virtual-module-codegen.ts +203 -0
- package/src/vite/index.ts +16 -0
- package/src/vite/plugin-types.ts +48 -0
- package/src/vite/plugins/cjs-to-esm.ts +93 -0
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +105 -0
- package/src/vite/plugins/expose-action-id.ts +363 -0
- package/src/vite/plugins/expose-id-utils.ts +287 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +569 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +323 -0
- package/src/vite/plugins/version-injector.ts +83 -0
- package/src/vite/plugins/version-plugin.ts +266 -0
- package/src/vite/plugins/version.d.ts +12 -0
- package/src/vite/plugins/virtual-entries.ts +123 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +445 -0
- package/src/vite/router-discovery.ts +777 -0
- package/src/vite/utils/ast-handler-extract.ts +517 -0
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +137 -0
- package/src/vite/utils/manifest-utils.ts +70 -0
- package/src/vite/utils/package-resolution.ts +121 -0
- package/src/vite/utils/prerender-utils.ts +189 -0
- package/src/vite/utils/shared-utils.ts +169 -0
package/src/bin/rango.ts
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { resolve, dirname } from "node:path";
|
|
2
|
+
import { readFileSync, statSync, existsSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
findTsFiles,
|
|
5
|
+
writePerModuleRouteTypesForFile,
|
|
6
|
+
writeCombinedRouteTypes,
|
|
7
|
+
detectUnresolvableIncludes,
|
|
8
|
+
detectUnresolvableIncludesForUrlsFile,
|
|
9
|
+
findNestedRouterConflict,
|
|
10
|
+
formatNestedRouterConflictError,
|
|
11
|
+
type UnresolvableInclude,
|
|
12
|
+
} from "../build/generate-route-types.ts";
|
|
13
|
+
|
|
14
|
+
const [command, ...rawArgs] = process.argv.slice(2);
|
|
15
|
+
|
|
16
|
+
if (command === "generate") {
|
|
17
|
+
// Parse flags
|
|
18
|
+
let mode: "default" | "runtime" | "static" = "default";
|
|
19
|
+
let configFile: string | undefined;
|
|
20
|
+
const positionalArgs: string[] = [];
|
|
21
|
+
|
|
22
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
23
|
+
const arg = rawArgs[i];
|
|
24
|
+
if (arg === "--runtime") {
|
|
25
|
+
mode = "runtime";
|
|
26
|
+
} else if (arg === "--static") {
|
|
27
|
+
mode = "static";
|
|
28
|
+
} else if (arg === "--config") {
|
|
29
|
+
configFile = rawArgs[++i];
|
|
30
|
+
if (!configFile) {
|
|
31
|
+
console.error("[rango] --config requires a path argument");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
} else if (arg.startsWith("--")) {
|
|
35
|
+
console.error(`[rango] Unknown flag: ${arg}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
} else {
|
|
38
|
+
positionalArgs.push(arg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (positionalArgs.length === 0) {
|
|
43
|
+
console.error(
|
|
44
|
+
"[rango] Usage: rango generate <file|dir> [file2 ...] [--runtime|--static] [--config <path>]",
|
|
45
|
+
);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (configFile && mode !== "runtime") {
|
|
50
|
+
console.warn("[rango] --config is only used with --runtime, ignoring");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (mode === "runtime") {
|
|
54
|
+
// Runtime discovery: dynamically import to avoid loading Vite for static-only usage
|
|
55
|
+
runRuntimeDiscovery(positionalArgs, configFile).catch((err) => {
|
|
56
|
+
console.error(`[rango] Runtime discovery failed: ${err.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
runStaticGeneration(positionalArgs, mode);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
if (
|
|
64
|
+
command &&
|
|
65
|
+
command !== "help" &&
|
|
66
|
+
command !== "--help" &&
|
|
67
|
+
command !== "-h"
|
|
68
|
+
) {
|
|
69
|
+
console.error(`[rango] Unknown command: ${command}\n`);
|
|
70
|
+
}
|
|
71
|
+
console.log(`Usage: rango generate <file|dir> [file2 ...] [--runtime|--static] [--config <path>]
|
|
72
|
+
|
|
73
|
+
Auto-detects file type (createRouter, urls) and generates
|
|
74
|
+
the appropriate .gen.ts route type files.
|
|
75
|
+
|
|
76
|
+
Modes:
|
|
77
|
+
(default) Static parser with error on unresolvable includes
|
|
78
|
+
--runtime Vite-based runtime discovery (100% coverage)
|
|
79
|
+
Requires vite and @vitejs/plugin-rsc
|
|
80
|
+
--static Static parser, accept partial output with warnings
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
--config <path> Path to vite.config.ts (--runtime only, auto-detected if omitted)
|
|
84
|
+
|
|
85
|
+
Examples:
|
|
86
|
+
rango generate src/router.tsx
|
|
87
|
+
rango generate src/router.tsx --runtime
|
|
88
|
+
rango generate src/ --static`);
|
|
89
|
+
process.exit(
|
|
90
|
+
command && command !== "help" && command !== "--help" && command !== "-h"
|
|
91
|
+
? 1
|
|
92
|
+
: 0,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Walk up from a file path to find the project root (directory containing
|
|
98
|
+
* package.json or vite.config.ts).
|
|
99
|
+
*/
|
|
100
|
+
function findProjectRoot(fromPath: string): string {
|
|
101
|
+
let dir = dirname(resolve(fromPath));
|
|
102
|
+
while (dir !== dirname(dir)) {
|
|
103
|
+
if (
|
|
104
|
+
existsSync(resolve(dir, "package.json")) ||
|
|
105
|
+
existsSync(resolve(dir, "vite.config.ts")) ||
|
|
106
|
+
existsSync(resolve(dir, "vite.config.js"))
|
|
107
|
+
) {
|
|
108
|
+
return dir;
|
|
109
|
+
}
|
|
110
|
+
dir = dirname(dir);
|
|
111
|
+
}
|
|
112
|
+
// Fallback to cwd if no project root found
|
|
113
|
+
return process.cwd();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function runStaticGeneration(args: string[], mode: "default" | "static") {
|
|
117
|
+
// Expand args: files are used directly, directories are scanned
|
|
118
|
+
const files: string[] = [];
|
|
119
|
+
for (const arg of args) {
|
|
120
|
+
const resolved = resolve(arg);
|
|
121
|
+
try {
|
|
122
|
+
if (statSync(resolved).isDirectory()) {
|
|
123
|
+
files.push(...findTsFiles(resolved));
|
|
124
|
+
} else {
|
|
125
|
+
files.push(resolved);
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
console.warn(`[rango] Skipping ${arg}: not found`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (files.length === 0) {
|
|
133
|
+
console.log("[rango] No files to process");
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Phase 1: Classify files
|
|
138
|
+
const routerFiles: string[] = [];
|
|
139
|
+
const urlsFiles: string[] = [];
|
|
140
|
+
|
|
141
|
+
for (const filePath of files) {
|
|
142
|
+
try {
|
|
143
|
+
const source = readFileSync(filePath, "utf-8");
|
|
144
|
+
if (/\bcreateRouter\s*[<(]/.test(source)) {
|
|
145
|
+
routerFiles.push(filePath);
|
|
146
|
+
}
|
|
147
|
+
if (source.includes("urls(")) {
|
|
148
|
+
urlsFiles.push(filePath);
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.warn(
|
|
152
|
+
`[rango] Failed to process ${filePath}: ${(err as Error).message}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Phase 2: Collect diagnostics from all files BEFORE writing anything
|
|
158
|
+
const allDiagnostics: Array<UnresolvableInclude & { routerFile: string }> =
|
|
159
|
+
[];
|
|
160
|
+
|
|
161
|
+
for (const routerFile of routerFiles) {
|
|
162
|
+
const diagnostics = detectUnresolvableIncludes(routerFile);
|
|
163
|
+
for (const d of diagnostics) {
|
|
164
|
+
allDiagnostics.push({ ...d, routerFile });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Also check standalone urls files not covered by router-level detection
|
|
169
|
+
const routerFileSet = new Set(routerFiles);
|
|
170
|
+
for (const urlsFile of urlsFiles) {
|
|
171
|
+
if (routerFileSet.has(urlsFile)) continue;
|
|
172
|
+
const diagnostics = detectUnresolvableIncludesForUrlsFile(urlsFile);
|
|
173
|
+
for (const d of diagnostics) {
|
|
174
|
+
allDiagnostics.push({ ...d, routerFile: urlsFile });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Deduplicate diagnostics (router and urls detection may find the same issue)
|
|
179
|
+
const seen = new Set<string>();
|
|
180
|
+
const uniqueDiagnostics = allDiagnostics.filter((d) => {
|
|
181
|
+
const key = `${d.sourceFile}:${d.pathPrefix}:${d.reason}`;
|
|
182
|
+
if (seen.has(key)) return false;
|
|
183
|
+
seen.add(key);
|
|
184
|
+
return true;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (uniqueDiagnostics.length > 0 && mode === "default") {
|
|
188
|
+
// Hard error: no files written
|
|
189
|
+
console.error("\n[rango] Unresolvable includes detected:\n");
|
|
190
|
+
formatDiagnostics(uniqueDiagnostics);
|
|
191
|
+
console.error(
|
|
192
|
+
"\nThe static parser cannot resolve these includes because they use " +
|
|
193
|
+
"factory functions or dynamic expressions.\n\n" +
|
|
194
|
+
"Options:\n" +
|
|
195
|
+
" rango generate <path> --runtime Use Vite-based discovery (requires vite)\n" +
|
|
196
|
+
" rango generate <path> --static Accept partial output (missing routes above)\n",
|
|
197
|
+
);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (uniqueDiagnostics.length > 0 && mode === "static") {
|
|
202
|
+
// Warning: partial output accepted
|
|
203
|
+
console.warn(
|
|
204
|
+
"\n[rango] Warning: partial output (unresolvable includes):\n",
|
|
205
|
+
);
|
|
206
|
+
formatDiagnostics(uniqueDiagnostics);
|
|
207
|
+
console.warn("");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const nestedRouterConflict = findNestedRouterConflict(routerFiles);
|
|
211
|
+
if (nestedRouterConflict) {
|
|
212
|
+
console.error(
|
|
213
|
+
`\n${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}\n`,
|
|
214
|
+
);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Phase 3: Write all outputs (only reached if diagnostics pass or --static)
|
|
219
|
+
for (const urlsFile of urlsFiles) {
|
|
220
|
+
writePerModuleRouteTypesForFile(urlsFile);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const routerFile of routerFiles) {
|
|
224
|
+
const projectRoot = findProjectRoot(routerFile);
|
|
225
|
+
writeCombinedRouteTypes(projectRoot, [routerFile]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(
|
|
229
|
+
`[rango] Processed ${files.length} file(s)${routerFiles.length ? ` (${routerFiles.length} router)` : ""}`,
|
|
230
|
+
);
|
|
231
|
+
process.exit(0);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function runRuntimeDiscovery(args: string[], configFile?: string) {
|
|
235
|
+
// Resolve the entry: find the router file from the arguments
|
|
236
|
+
const files: string[] = [];
|
|
237
|
+
for (const arg of args) {
|
|
238
|
+
const resolved = resolve(arg);
|
|
239
|
+
try {
|
|
240
|
+
if (statSync(resolved).isDirectory()) {
|
|
241
|
+
files.push(...findTsFiles(resolved));
|
|
242
|
+
} else {
|
|
243
|
+
files.push(resolved);
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
console.warn(`[rango] Skipping ${arg}: not found`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Find router files among the inputs
|
|
251
|
+
const routerEntries: string[] = [];
|
|
252
|
+
for (const filePath of files) {
|
|
253
|
+
try {
|
|
254
|
+
const source = readFileSync(filePath, "utf-8");
|
|
255
|
+
if (/\bcreateRouter\s*[<(]/.test(source)) {
|
|
256
|
+
routerEntries.push(filePath);
|
|
257
|
+
}
|
|
258
|
+
// Also generate per-module types for urls files
|
|
259
|
+
if (source.includes("urls(")) {
|
|
260
|
+
writePerModuleRouteTypesForFile(filePath);
|
|
261
|
+
}
|
|
262
|
+
} catch {
|
|
263
|
+
// Skip unreadable files
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (routerEntries.length === 0) {
|
|
268
|
+
console.error("[rango] No router files found in the provided paths");
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const nestedRouterConflict = findNestedRouterConflict(routerEntries);
|
|
273
|
+
if (nestedRouterConflict) {
|
|
274
|
+
console.error(
|
|
275
|
+
`\n${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}\n`,
|
|
276
|
+
);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let discoverAndWriteRouteTypes: typeof import("../build/runtime-discovery.ts").discoverAndWriteRouteTypes;
|
|
281
|
+
try {
|
|
282
|
+
const mod = await import("../build/runtime-discovery.ts");
|
|
283
|
+
discoverAndWriteRouteTypes = mod.discoverAndWriteRouteTypes;
|
|
284
|
+
} catch (err: any) {
|
|
285
|
+
if (
|
|
286
|
+
err.code === "ERR_MODULE_NOT_FOUND" ||
|
|
287
|
+
err.code === "MODULE_NOT_FOUND"
|
|
288
|
+
) {
|
|
289
|
+
console.error(
|
|
290
|
+
"[rango] Runtime discovery requires 'vite' and '@vitejs/plugin-rsc'.\n" +
|
|
291
|
+
"Install them with: pnpm add -D vite @vitejs/plugin-rsc",
|
|
292
|
+
);
|
|
293
|
+
} else {
|
|
294
|
+
console.error(`[rango] Failed to load runtime discovery: ${err.message}`);
|
|
295
|
+
}
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
for (const entry of routerEntries) {
|
|
300
|
+
const projectRoot = findProjectRoot(entry);
|
|
301
|
+
const result = await discoverAndWriteRouteTypes({
|
|
302
|
+
root: projectRoot,
|
|
303
|
+
configFile,
|
|
304
|
+
entry,
|
|
305
|
+
});
|
|
306
|
+
console.log(
|
|
307
|
+
`[rango] Runtime discovery: ${result.routerCount} router(s), ${result.routeCount} route(s)`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatDiagnostics(
|
|
313
|
+
diagnostics: Array<UnresolvableInclude & { routerFile: string }>,
|
|
314
|
+
) {
|
|
315
|
+
for (const d of diagnostics) {
|
|
316
|
+
const prefix = d.namePrefix ? `${d.namePrefix}.*` : `${d.pathPrefix}*`;
|
|
317
|
+
console.error(` ${prefix}`);
|
|
318
|
+
console.error(` Reason: ${d.reason} -- ${d.detail}`);
|
|
319
|
+
console.error(` Source: ${d.sourceFile}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyActionResponse,
|
|
3
|
+
type ActionScenario,
|
|
4
|
+
} from "./action-response-classifier.js";
|
|
5
|
+
import type { ActionEntry } from "./event-controller.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Plain data inputs for classifying a post-reconciliation action outcome.
|
|
9
|
+
* No browser objects or controller references — all values are snapshots.
|
|
10
|
+
*/
|
|
11
|
+
export interface ActionOutcomeInput {
|
|
12
|
+
/** This action's unique instance ID */
|
|
13
|
+
handleId: string;
|
|
14
|
+
/** All in-flight action entries (snapshot from event controller) */
|
|
15
|
+
inflightActions: Map<string, ActionEntry>;
|
|
16
|
+
/** Whether any concurrent actions occurred (controller-level shared flag) */
|
|
17
|
+
hadAnyConcurrentActions: boolean;
|
|
18
|
+
/** Segments revalidated by concurrent actions (from tracking set) */
|
|
19
|
+
revalidatedSegments: Set<string>;
|
|
20
|
+
/** window.location.pathname captured at action start */
|
|
21
|
+
actionStartPathname: string;
|
|
22
|
+
/** window.location.pathname at classification time */
|
|
23
|
+
currentPathname: string;
|
|
24
|
+
/** window.history.state?.key captured at action start */
|
|
25
|
+
actionStartLocationKey: string | undefined;
|
|
26
|
+
/** window.history.state?.key at classification time */
|
|
27
|
+
currentLocationKey: string | undefined;
|
|
28
|
+
/** Number of segments after reconciliation */
|
|
29
|
+
reconciledSegmentCount: number;
|
|
30
|
+
/** Number of matched segment IDs from server */
|
|
31
|
+
matchedCount: number;
|
|
32
|
+
/** Current intercept source URL (null when not on intercept route) */
|
|
33
|
+
currentInterceptSource: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compute consolidation segments from concurrent action state.
|
|
38
|
+
*
|
|
39
|
+
* Returns segment IDs that need re-fetching when concurrent actions
|
|
40
|
+
* have each revalidated different parts of the tree, or null if
|
|
41
|
+
* consolidation is not needed.
|
|
42
|
+
*/
|
|
43
|
+
function computeConsolidationSegments(
|
|
44
|
+
input: ActionOutcomeInput,
|
|
45
|
+
): string[] | null {
|
|
46
|
+
if (!input.hadAnyConcurrentActions) return null;
|
|
47
|
+
if (input.revalidatedSegments.size === 0) return null;
|
|
48
|
+
|
|
49
|
+
// Can't consolidate while any action is still waiting for a server response
|
|
50
|
+
const stillFetchingCount = [...input.inflightActions.values()].filter(
|
|
51
|
+
(a) => a.phase === "fetching",
|
|
52
|
+
).length;
|
|
53
|
+
if (stillFetchingCount > 0) return null;
|
|
54
|
+
|
|
55
|
+
return Array.from(input.revalidatedSegments);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Count other actions still in "fetching" phase (excluding this handle).
|
|
60
|
+
*/
|
|
61
|
+
function countOtherFetchingActions(input: ActionOutcomeInput): number {
|
|
62
|
+
let count = 0;
|
|
63
|
+
for (const [, a] of input.inflightActions) {
|
|
64
|
+
if (a.phase === "fetching" && a.id !== input.handleId) {
|
|
65
|
+
count++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return count;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Classify a post-reconciliation action outcome into one of 5 scenarios.
|
|
73
|
+
*
|
|
74
|
+
* This is the single entry point for post-action decision logic.
|
|
75
|
+
* It gathers consolidation and concurrency data from the plain inputs,
|
|
76
|
+
* then delegates to the pure classifyActionResponse function.
|
|
77
|
+
*
|
|
78
|
+
* The server-action-bridge calls this after reconciliation to decide
|
|
79
|
+
* whether to render, skip, consolidate, or refetch.
|
|
80
|
+
*/
|
|
81
|
+
export function classifyActionOutcome(
|
|
82
|
+
input: ActionOutcomeInput,
|
|
83
|
+
): ActionScenario {
|
|
84
|
+
return classifyActionResponse({
|
|
85
|
+
actionStartPathname: input.actionStartPathname,
|
|
86
|
+
currentPathname: input.currentPathname,
|
|
87
|
+
actionStartLocationKey: input.actionStartLocationKey,
|
|
88
|
+
currentLocationKey: input.currentLocationKey,
|
|
89
|
+
reconciledSegmentCount: input.reconciledSegmentCount,
|
|
90
|
+
matchedCount: input.matchedCount,
|
|
91
|
+
currentInterceptSource: input.currentInterceptSource,
|
|
92
|
+
consolidationSegments: computeConsolidationSegments(input),
|
|
93
|
+
otherFetchingActionCount: countOtherFetchingActions(input),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type { ActionScenario };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminated union of post-reconciliation action response scenarios.
|
|
3
|
+
*
|
|
4
|
+
* Error and full-update-unsupported are handled inline in the bridge
|
|
5
|
+
* before reconciliation. This classifier only runs for partial responses
|
|
6
|
+
* that have been successfully reconciled.
|
|
7
|
+
*/
|
|
8
|
+
export type ActionScenario =
|
|
9
|
+
| {
|
|
10
|
+
type: "navigated-away";
|
|
11
|
+
historyKeyChanged: boolean;
|
|
12
|
+
onInterceptRoute: boolean;
|
|
13
|
+
}
|
|
14
|
+
| { type: "hmr-missing" }
|
|
15
|
+
| { type: "consolidation-needed"; segmentIds: string[] }
|
|
16
|
+
| { type: "concurrent-skip"; otherFetchingCount: number }
|
|
17
|
+
| { type: "normal" };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Pure data inputs for classifying a partial action response.
|
|
21
|
+
* All values come from the bridge but no browser APIs or side effects.
|
|
22
|
+
*/
|
|
23
|
+
export interface ClassifierInput {
|
|
24
|
+
/** window.location.pathname captured at action start */
|
|
25
|
+
actionStartPathname: string;
|
|
26
|
+
/** window.location.pathname at classification time */
|
|
27
|
+
currentPathname: string;
|
|
28
|
+
/** window.history.state?.key captured at action start */
|
|
29
|
+
actionStartLocationKey: string | undefined;
|
|
30
|
+
/** window.history.state?.key at classification time */
|
|
31
|
+
currentLocationKey: string | undefined;
|
|
32
|
+
/** Number of segments after reconciliation */
|
|
33
|
+
reconciledSegmentCount: number;
|
|
34
|
+
/** Number of matched segment IDs from server */
|
|
35
|
+
matchedCount: number;
|
|
36
|
+
/** Segment IDs needing consolidation (from concurrent action tracking) */
|
|
37
|
+
consolidationSegments: string[] | null;
|
|
38
|
+
/** Number of other actions still in "fetching" phase */
|
|
39
|
+
otherFetchingActionCount: number;
|
|
40
|
+
/** Current intercept source URL (null when not on intercept route) */
|
|
41
|
+
currentInterceptSource: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Classify a partial action response into one of 5 post-reconciliation
|
|
46
|
+
* scenarios.
|
|
47
|
+
*
|
|
48
|
+
* Called after error and full-update cases are handled inline by the bridge.
|
|
49
|
+
* The classification order matches the priority chain:
|
|
50
|
+
* 1. User navigated away during action
|
|
51
|
+
* 2. HMR missing segments (fewer reconciled than matched)
|
|
52
|
+
* 3. Consolidation needed (concurrent actions finished)
|
|
53
|
+
* 4. Concurrent skip (other actions still fetching)
|
|
54
|
+
* 5. Normal (single action, no issues)
|
|
55
|
+
*
|
|
56
|
+
* This is a pure function with no side effects - the bridge handles
|
|
57
|
+
* all UI updates, store mutations, and network requests based on the
|
|
58
|
+
* returned scenario.
|
|
59
|
+
*/
|
|
60
|
+
export function classifyActionResponse(input: ClassifierInput): ActionScenario {
|
|
61
|
+
// Check if user navigated away during the action
|
|
62
|
+
const userNavigatedAway =
|
|
63
|
+
input.currentPathname !== input.actionStartPathname ||
|
|
64
|
+
input.currentLocationKey !== input.actionStartLocationKey;
|
|
65
|
+
|
|
66
|
+
if (userNavigatedAway) {
|
|
67
|
+
const historyKeyChanged =
|
|
68
|
+
input.currentLocationKey !== input.actionStartLocationKey;
|
|
69
|
+
return {
|
|
70
|
+
type: "navigated-away",
|
|
71
|
+
historyKeyChanged,
|
|
72
|
+
onInterceptRoute: input.currentInterceptSource !== null,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// HMR resilience: segments missing after reconciliation
|
|
77
|
+
if (input.reconciledSegmentCount < input.matchedCount) {
|
|
78
|
+
return { type: "hmr-missing" };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Consolidation needed for concurrent actions
|
|
82
|
+
if (input.consolidationSegments && input.consolidationSegments.length > 0) {
|
|
83
|
+
return {
|
|
84
|
+
type: "consolidation-needed",
|
|
85
|
+
segmentIds: input.consolidationSegments,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Other actions still fetching - skip UI update
|
|
90
|
+
if (input.otherFetchingActionCount > 0) {
|
|
91
|
+
return {
|
|
92
|
+
type: "concurrent-skip",
|
|
93
|
+
otherFetchingCount: input.otherFetchingActionCount,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Normal single-action completion
|
|
98
|
+
return { type: "normal" };
|
|
99
|
+
}
|