@rangojs/router 0.0.0-experimental.10
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/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -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 +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +114 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @rangojs/router
|
|
2
|
+
|
|
3
|
+
Run `/rango` first to understand the API. Skills are in `node_modules/@rangojs/router/skills/`.
|
|
4
|
+
|
|
5
|
+
## Tree-Structure-Critical Files (DO NOT MODIFY without understanding)
|
|
6
|
+
|
|
7
|
+
The following files control the React tree structure. Changing the tree structure
|
|
8
|
+
(element types, nesting depth, or keys at any position) between SSR, navigation,
|
|
9
|
+
and action renders will cause React to remount components, destroying client state
|
|
10
|
+
like `useActionState`, refs, and local state. This is extremely hard to debug.
|
|
11
|
+
|
|
12
|
+
**Protected files:**
|
|
13
|
+
|
|
14
|
+
- `src/segment-system.tsx` - `renderSegments()` builds the React tree from segments.
|
|
15
|
+
The `loading` property determines tree structure:
|
|
16
|
+
- `undefined` / `null` -> OutletProvider directly (no boundary)
|
|
17
|
+
- `false` -> LoaderBoundary + OutletProvider (boundary, no RouteContentWrapper)
|
|
18
|
+
- truthy (ReactNode) -> LoaderBoundary + OutletProvider + RouteContentWrapper
|
|
19
|
+
|
|
20
|
+
- `src/route-content-wrapper.tsx` - `LoaderBoundary` and `RouteContentWrapper`.
|
|
21
|
+
These add structural depth (Suspense boundaries) to the React tree.
|
|
22
|
+
|
|
23
|
+
- `src/browser/server-action-bridge.ts` - Merges server action segments with
|
|
24
|
+
cached segments. Must preserve cached `loading` values to prevent tree drift.
|
|
25
|
+
|
|
26
|
+
- `src/browser/partial-update.ts` - Merges navigation segments with cached segments.
|
|
27
|
+
|
|
28
|
+
**Rules:**
|
|
29
|
+
|
|
30
|
+
1. Never change the conditional logic in `renderSegments()` that decides between
|
|
31
|
+
LoaderBoundary/RouteContentWrapper/OutletProvider without verifying all three
|
|
32
|
+
render paths (SSR, navigation, action) produce identical tree structures.
|
|
33
|
+
|
|
34
|
+
2. Never add or remove wrapper elements (Suspense, div, Fragment) around segment
|
|
35
|
+
content without checking that the same wrappers exist in ALL render paths.
|
|
36
|
+
|
|
37
|
+
3. When merging segments (action bridge, partial update), always preserve the
|
|
38
|
+
cached `loading` value if it differs from the server value. The server may
|
|
39
|
+
return different `loading` values based on `isSSR` context.
|
|
40
|
+
|
|
41
|
+
4. Run `pnpm --filter @rangojs/router exec playwright test loader-behavior` after
|
|
42
|
+
any changes to these files. The skipSSR action tests specifically catch tree
|
|
43
|
+
structure regressions.
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @rangojs/router
|
|
2
|
+
|
|
3
|
+
> **Warning:** This package is experimental and under active development. APIs may change without notice.
|
|
4
|
+
|
|
5
|
+
Type-safe RSC router with partial rendering support for Vite.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @rangojs/router@experimental
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Status
|
|
14
|
+
|
|
15
|
+
This package is in early experimental stages. It is not recommended for production use.
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
MIT
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin/rango.ts
|
|
4
|
+
import { resolve as resolve2 } from "node:path";
|
|
5
|
+
|
|
6
|
+
// src/build/generate-route-types.ts
|
|
7
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, unlinkSync } from "node:fs";
|
|
8
|
+
import { join, dirname, resolve, relative, basename as pathBasename } from "node:path";
|
|
9
|
+
import picomatch from "picomatch";
|
|
10
|
+
function extractRoutesFromSource(code) {
|
|
11
|
+
const routes = [];
|
|
12
|
+
const regex = /\bpath(?:\.(?:json|text|html|xml|image|stream|any))?\s*\(/g;
|
|
13
|
+
let match;
|
|
14
|
+
while ((match = regex.exec(code)) !== null) {
|
|
15
|
+
const result = parsePathCall(code, match.index + match[0].length);
|
|
16
|
+
if (result) routes.push(result);
|
|
17
|
+
}
|
|
18
|
+
return routes;
|
|
19
|
+
}
|
|
20
|
+
function generatePerModuleTypesSource(routes) {
|
|
21
|
+
const valid = routes.filter(({ name }) => {
|
|
22
|
+
if (!name || /["'\\`\n\r]/.test(name)) {
|
|
23
|
+
console.warn(`[rsc-router] Skipping route with invalid name: ${JSON.stringify(name)}`);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
});
|
|
28
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const { name, pattern } of valid) {
|
|
30
|
+
deduped.set(name, pattern);
|
|
31
|
+
}
|
|
32
|
+
const sorted = [...deduped.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
33
|
+
const body = sorted.map(([name, pattern]) => {
|
|
34
|
+
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
|
|
35
|
+
return ` ${key}: "${pattern}",`;
|
|
36
|
+
}).join("\n");
|
|
37
|
+
return `// Auto-generated by @rangojs/router - do not edit
|
|
38
|
+
export const routes = {
|
|
39
|
+
${body}
|
|
40
|
+
} as const;
|
|
41
|
+
export type routes = typeof routes;
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
function isWhitespace(ch) {
|
|
45
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r";
|
|
46
|
+
}
|
|
47
|
+
function readString(code, pos) {
|
|
48
|
+
const quote = code[pos];
|
|
49
|
+
if (quote !== '"' && quote !== "'") return null;
|
|
50
|
+
let value = "";
|
|
51
|
+
pos++;
|
|
52
|
+
while (pos < code.length) {
|
|
53
|
+
if (code[pos] === "\\") {
|
|
54
|
+
pos++;
|
|
55
|
+
if (pos < code.length) {
|
|
56
|
+
value += code[pos];
|
|
57
|
+
pos++;
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (code[pos] === quote) {
|
|
62
|
+
return { value, end: pos + 1 };
|
|
63
|
+
}
|
|
64
|
+
value += code[pos];
|
|
65
|
+
pos++;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function skipStringLiteral(code, pos) {
|
|
70
|
+
const quote = code[pos];
|
|
71
|
+
if (quote === "`") {
|
|
72
|
+
pos++;
|
|
73
|
+
while (pos < code.length) {
|
|
74
|
+
if (code[pos] === "\\") {
|
|
75
|
+
pos += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (code[pos] === "`") return pos + 1;
|
|
79
|
+
if (code[pos] === "$" && pos + 1 < code.length && code[pos + 1] === "{") {
|
|
80
|
+
pos += 2;
|
|
81
|
+
let braceDepth = 1;
|
|
82
|
+
while (pos < code.length && braceDepth > 0) {
|
|
83
|
+
if (code[pos] === "{") braceDepth++;
|
|
84
|
+
else if (code[pos] === "}") braceDepth--;
|
|
85
|
+
else if (code[pos] === "\\") pos++;
|
|
86
|
+
else if (code[pos] === '"' || code[pos] === "'" || code[pos] === "`") {
|
|
87
|
+
pos = skipStringLiteral(code, pos);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (braceDepth > 0) pos++;
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
pos++;
|
|
95
|
+
}
|
|
96
|
+
return pos;
|
|
97
|
+
}
|
|
98
|
+
pos++;
|
|
99
|
+
while (pos < code.length) {
|
|
100
|
+
if (code[pos] === "\\") {
|
|
101
|
+
pos += 2;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (code[pos] === quote) return pos + 1;
|
|
105
|
+
pos++;
|
|
106
|
+
}
|
|
107
|
+
return pos;
|
|
108
|
+
}
|
|
109
|
+
function matchesNameColon(code, pos) {
|
|
110
|
+
if (code.slice(pos, pos + 4) !== "name") return false;
|
|
111
|
+
if (pos > 0 && /\w/.test(code[pos - 1])) return false;
|
|
112
|
+
const afterName = pos + 4;
|
|
113
|
+
if (afterName < code.length && /\w/.test(code[afterName])) return false;
|
|
114
|
+
let checkPos = afterName;
|
|
115
|
+
while (checkPos < code.length && isWhitespace(code[checkPos])) checkPos++;
|
|
116
|
+
return code[checkPos] === ":";
|
|
117
|
+
}
|
|
118
|
+
function extractNameValue(code, pos) {
|
|
119
|
+
pos += 4;
|
|
120
|
+
while (pos < code.length && isWhitespace(code[pos])) pos++;
|
|
121
|
+
pos++;
|
|
122
|
+
while (pos < code.length && isWhitespace(code[pos])) pos++;
|
|
123
|
+
return readString(code, pos);
|
|
124
|
+
}
|
|
125
|
+
function parsePathCall(code, pos) {
|
|
126
|
+
while (pos < code.length && isWhitespace(code[pos])) pos++;
|
|
127
|
+
const patternStr = readString(code, pos);
|
|
128
|
+
if (!patternStr) return null;
|
|
129
|
+
const pattern = patternStr.value;
|
|
130
|
+
pos = patternStr.end;
|
|
131
|
+
let depth = 1;
|
|
132
|
+
let name = null;
|
|
133
|
+
while (pos < code.length && depth > 0) {
|
|
134
|
+
const ch = code[pos];
|
|
135
|
+
if (isWhitespace(ch)) {
|
|
136
|
+
pos++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "/") {
|
|
140
|
+
pos += 2;
|
|
141
|
+
while (pos < code.length && code[pos] !== "\n") pos++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch === "/" && pos + 1 < code.length && code[pos + 1] === "*") {
|
|
145
|
+
pos += 2;
|
|
146
|
+
while (pos < code.length - 1 && !(code[pos] === "*" && code[pos + 1] === "/"))
|
|
147
|
+
pos++;
|
|
148
|
+
pos += 2;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (depth === 2 && ch === "n" && matchesNameColon(code, pos)) {
|
|
152
|
+
const nameResult = extractNameValue(code, pos);
|
|
153
|
+
if (nameResult) {
|
|
154
|
+
name = nameResult.value;
|
|
155
|
+
pos = nameResult.end;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (ch === '"' || ch === "`" || ch === "'" && (pos === 0 || !/\w/.test(code[pos - 1]))) {
|
|
160
|
+
pos = skipStringLiteral(code, pos);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (ch === "(" || ch === "{" || ch === "[") depth++;
|
|
164
|
+
else if (ch === ")" || ch === "}" || ch === "]") depth--;
|
|
165
|
+
pos++;
|
|
166
|
+
}
|
|
167
|
+
if (name === null) return null;
|
|
168
|
+
return { name, pattern };
|
|
169
|
+
}
|
|
170
|
+
function findTsFiles(dir, filter) {
|
|
171
|
+
const results = [];
|
|
172
|
+
let entries;
|
|
173
|
+
try {
|
|
174
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.warn(`[rsc-router] Failed to scan directory ${dir}: ${err.message}`);
|
|
177
|
+
return results;
|
|
178
|
+
}
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const fullPath = join(dir, entry.name);
|
|
181
|
+
if (entry.isDirectory()) {
|
|
182
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
183
|
+
results.push(...findTsFiles(fullPath, filter));
|
|
184
|
+
} else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.includes(".gen.")) {
|
|
185
|
+
if (filter && !filter(fullPath)) continue;
|
|
186
|
+
results.push(fullPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
191
|
+
function writePerModuleRouteTypesForFile(filePath) {
|
|
192
|
+
try {
|
|
193
|
+
const source = readFileSync(filePath, "utf-8");
|
|
194
|
+
if (!source.includes("urls(")) return;
|
|
195
|
+
const routes = extractRoutesFromSource(source);
|
|
196
|
+
if (routes.length === 0) return;
|
|
197
|
+
const genPath = filePath.replace(/\.(tsx?)$/, ".gen.ts");
|
|
198
|
+
const genSource = generatePerModuleTypesSource(routes);
|
|
199
|
+
const existing = existsSync(genPath) ? readFileSync(genPath, "utf-8") : null;
|
|
200
|
+
if (existing !== genSource) {
|
|
201
|
+
writeFileSync(genPath, genSource);
|
|
202
|
+
console.log(`[rsc-router] Generated route types -> ${genPath}`);
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
console.warn(`[rsc-router] Failed to generate route types for ${filePath}: ${err.message}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/bin/rango.ts
|
|
210
|
+
var [command, ...args] = process.argv.slice(2);
|
|
211
|
+
if (command === "extract-names") {
|
|
212
|
+
const dir = args[0] ?? "./src";
|
|
213
|
+
const resolvedDir = resolve2(dir);
|
|
214
|
+
console.log(`[rango] Scanning ${resolvedDir} for url modules...`);
|
|
215
|
+
const files = findTsFiles(resolvedDir);
|
|
216
|
+
for (const filePath of files) {
|
|
217
|
+
writePerModuleRouteTypesForFile(filePath);
|
|
218
|
+
}
|
|
219
|
+
console.log(`[rango] Scanned ${files.length} file(s)`);
|
|
220
|
+
process.exit(0);
|
|
221
|
+
} else {
|
|
222
|
+
console.log(`Usage: rango <command>
|
|
223
|
+
|
|
224
|
+
Commands:
|
|
225
|
+
extract-names [dir] Extract route names from url modules (default: ./src)`);
|
|
226
|
+
process.exit(command ? 1 : 0);
|
|
227
|
+
}
|