katman-mcp 0.1.0
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/LICENSE +13 -0
- package/README.md +160 -0
- package/dist/cli.js +71 -0
- package/dist/context.js +43 -0
- package/dist/crawlers.js +86 -0
- package/dist/index.js +25 -0
- package/dist/license.js +448 -0
- package/dist/meta.js +28 -0
- package/dist/net.js +128 -0
- package/dist/server.js +17 -0
- package/dist/state.js +81 -0
- package/dist/tools/account/account.js +64 -0
- package/dist/tools/account/cli.js +21 -0
- package/dist/tools/account/index.js +48 -0
- package/dist/tools/account/sync.js +142 -0
- package/dist/tools/account/upsell.js +290 -0
- package/dist/tools/crawl/audit/analyze.js +719 -0
- package/dist/tools/crawl/audit/bots.js +114 -0
- package/dist/tools/crawl/audit/checks.js +126 -0
- package/dist/tools/crawl/audit/html.js +382 -0
- package/dist/tools/crawl/audit/index.js +364 -0
- package/dist/tools/crawl/audit/model.js +1 -0
- package/dist/tools/crawl/audit/report.js +199 -0
- package/dist/tools/crawl/audit/robots.js +199 -0
- package/dist/tools/crawl/audit/sitemap.js +147 -0
- package/dist/tools/crawl/cli.js +106 -0
- package/dist/tools/crawl/common/advice.js +61 -0
- package/dist/tools/crawl/common/findings.js +57 -0
- package/dist/tools/crawl/common/routes.js +80 -0
- package/dist/tools/crawl/common/text.js +89 -0
- package/dist/tools/crawl/common/urls.js +64 -0
- package/dist/tools/crawl/index.js +226 -0
- package/dist/tools/crawl/indexnow.js +271 -0
- package/dist/tools/crawl/keywords.js +178 -0
- package/dist/tools/crawl/scan/framework.js +645 -0
- package/dist/tools/crawl/scan/index.js +175 -0
- package/dist/tools/crawl/scan/project.js +180 -0
- package/dist/tools/crawl/scan/routes.js +354 -0
- package/dist/tools/crawl/scan/signals.js +414 -0
- package/dist/tools/method/cli.js +42 -0
- package/dist/tools/method/core/brief.js +302 -0
- package/dist/tools/method/core/extract.js +435 -0
- package/dist/tools/method/core/generate.js +504 -0
- package/dist/tools/method/core/lint.js +556 -0
- package/dist/tools/method/core/plan.js +405 -0
- package/dist/tools/method/core/prompt.js +150 -0
- package/dist/tools/method/core/questions.js +196 -0
- package/dist/tools/method/core/report.js +182 -0
- package/dist/tools/method/core/research-run.js +148 -0
- package/dist/tools/method/core/research.js +448 -0
- package/dist/tools/method/core/snippets.js +292 -0
- package/dist/tools/method/core/start.js +277 -0
- package/dist/tools/method/data/anatomy.js +141 -0
- package/dist/tools/method/data/appendices.js +202 -0
- package/dist/tools/method/data/cloudflare.js +192 -0
- package/dist/tools/method/data/evidence.js +137 -0
- package/dist/tools/method/data/geo-jobs.js +128 -0
- package/dist/tools/method/data/index.js +19 -0
- package/dist/tools/method/data/landing.js +67 -0
- package/dist/tools/method/data/lessons.js +127 -0
- package/dist/tools/method/data/measurement.js +142 -0
- package/dist/tools/method/data/method.js +141 -0
- package/dist/tools/method/data/offsite.js +148 -0
- package/dist/tools/method/data/pages.js +167 -0
- package/dist/tools/method/data/prompts.js +633 -0
- package/dist/tools/method/data/publish.js +113 -0
- package/dist/tools/method/data/render.js +117 -0
- package/dist/tools/method/data/research.js +131 -0
- package/dist/tools/method/data/roadmap.js +105 -0
- package/dist/tools/method/data/traps.js +74 -0
- package/dist/tools/method/data/verify.js +88 -0
- package/dist/tools/method/data/viral-list.js +39 -0
- package/dist/tools/method/index.js +209 -0
- package/dist/tools/method/lib/calls.js +53 -0
- package/dist/tools/method/lib/framework.js +525 -0
- package/dist/tools/method/lib/l10n.js +17 -0
- package/dist/tools/method/lib/routes.js +118 -0
- package/dist/tools/method/lib/state.js +454 -0
- package/dist/tools/method/lib/text.js +165 -0
- package/dist/tools/method/mcp/prompts.js +79 -0
- package/dist/tools/method/mcp/resources.js +50 -0
- package/dist/tools/visibility/analyze.js +463 -0
- package/dist/tools/visibility/cli.js +86 -0
- package/dist/tools/visibility/estimate.js +105 -0
- package/dist/tools/visibility/index.js +108 -0
- package/dist/tools/visibility/judge.js +156 -0
- package/dist/tools/visibility/manual.js +275 -0
- package/dist/tools/visibility/models.js +277 -0
- package/dist/tools/visibility/monitoring.js +165 -0
- package/dist/tools/visibility/openrouter.js +449 -0
- package/dist/tools/visibility/render.js +295 -0
- package/dist/tools/visibility/run.js +684 -0
- package/dist/tools/visibility/summary.js +164 -0
- package/dist/types.js +3 -0
- package/dist/util.js +92 -0
- package/package.json +54 -0
- package/server.json +34 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import { classifyPath } from "../common/routes.js";
|
|
2
|
+
import { escapeRe } from "../common/text.js";
|
|
3
|
+
import { codeFiles, lineOf, list, text } from "./project.js";
|
|
4
|
+
const FILE_LIKE = /\.(txt|xml|json|webmanifest|ico|png|svg|jpg|jpeg|webp|rss|atom|js|css|map|pdf)$/i;
|
|
5
|
+
function add(out, path, file, fileRoutes, line = 1) {
|
|
6
|
+
let p = path.trim();
|
|
7
|
+
if (!p)
|
|
8
|
+
p = "/";
|
|
9
|
+
if (!p.startsWith("/"))
|
|
10
|
+
p = "/" + p;
|
|
11
|
+
p = p.replace(/\/{2,}/g, "/");
|
|
12
|
+
if (p.length > 1)
|
|
13
|
+
p = p.replace(/\/$/, "");
|
|
14
|
+
if (/[`${}]/.test(p) && /\$\{/.test(p))
|
|
15
|
+
return; // template literal
|
|
16
|
+
const last = p.split("/").pop() ?? "";
|
|
17
|
+
if (FILE_LIKE.test(last) && !/[[:$*]/.test(last)) {
|
|
18
|
+
if (!fileRoutes.has(last.toLowerCase()))
|
|
19
|
+
fileRoutes.set(last.toLowerCase(), { file: file ?? "", line, match: p });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (out.has(p))
|
|
23
|
+
return;
|
|
24
|
+
out.set(p, { path: p, file, kind: classifyPath(p), dynamic: /[[:$*]|\(\.\.\.|\?$/.test(p) });
|
|
25
|
+
}
|
|
26
|
+
function segsToPath(segs) {
|
|
27
|
+
return "/" + segs.filter(Boolean).join("/");
|
|
28
|
+
}
|
|
29
|
+
function nextApp(rel) {
|
|
30
|
+
const m = rel.match(/(?:^|\/)app\/(.*?)\/?(?:page)\.(?:tsx|ts|jsx|js|mdx|md)$/);
|
|
31
|
+
if (!m)
|
|
32
|
+
return null;
|
|
33
|
+
const segs = m[1].split("/").filter(Boolean);
|
|
34
|
+
if (segs.some((s) => s.startsWith("_") || /^\(\.{1,3}\)/.test(s)))
|
|
35
|
+
return null;
|
|
36
|
+
return segsToPath(segs.filter((s) => !/^\(.*\)$/.test(s) && !s.startsWith("@")));
|
|
37
|
+
}
|
|
38
|
+
function stripIndex(segs) {
|
|
39
|
+
const out = [...segs];
|
|
40
|
+
if (out.length && /^index$/i.test(out[out.length - 1]))
|
|
41
|
+
out.pop();
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
function fileSegs(rel, base) {
|
|
45
|
+
const m = rel.match(base);
|
|
46
|
+
if (!m)
|
|
47
|
+
return null;
|
|
48
|
+
return m[1].replace(/\.[a-z0-9]+$/i, "").split("/").filter(Boolean);
|
|
49
|
+
}
|
|
50
|
+
export function remixFlat(name) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
let cur = "";
|
|
53
|
+
let inBr = false;
|
|
54
|
+
for (const ch of name) {
|
|
55
|
+
if (ch === "[") {
|
|
56
|
+
inBr = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (ch === "]") {
|
|
60
|
+
inBr = false;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ch === "." && !inBr) {
|
|
64
|
+
parts.push(cur);
|
|
65
|
+
cur = "";
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
cur += ch;
|
|
69
|
+
}
|
|
70
|
+
parts.push(cur);
|
|
71
|
+
const out = [];
|
|
72
|
+
let dynamic = false;
|
|
73
|
+
for (let s of parts) {
|
|
74
|
+
if (!s || s === "_index" || s === "route")
|
|
75
|
+
continue;
|
|
76
|
+
if (s.startsWith("_"))
|
|
77
|
+
continue;
|
|
78
|
+
if (s.endsWith("_"))
|
|
79
|
+
s = s.slice(0, -1);
|
|
80
|
+
if (s === "$") {
|
|
81
|
+
out.push("*");
|
|
82
|
+
dynamic = true;
|
|
83
|
+
}
|
|
84
|
+
else if (/^\(\$.+\)$/.test(s)) {
|
|
85
|
+
out.push(`:${s.slice(2, -1)}?`);
|
|
86
|
+
dynamic = true;
|
|
87
|
+
}
|
|
88
|
+
else if (/^\(.+\)$/.test(s))
|
|
89
|
+
out.push(`${s.slice(1, -1)}?`);
|
|
90
|
+
else if (s.startsWith("$")) {
|
|
91
|
+
out.push(`:${s.slice(1)}`);
|
|
92
|
+
dynamic = true;
|
|
93
|
+
}
|
|
94
|
+
else
|
|
95
|
+
out.push(s);
|
|
96
|
+
}
|
|
97
|
+
return { path: segsToPath(out), dynamic };
|
|
98
|
+
}
|
|
99
|
+
const ROUTER_LIB = /react-router|@tanstack\/react-router|@tanstack\/solid-router|vue-router|@angular\/router|@solidjs\/router|wouter|preact-router|svelte-spa-router|createBrowserRouter|RouterModule|provideRouter|<Routes\b/;
|
|
100
|
+
export function scanRoutes(p, det) {
|
|
101
|
+
const out = new Map();
|
|
102
|
+
const fileRoutes = new Map();
|
|
103
|
+
const hashRouting = [];
|
|
104
|
+
const catchAll = [];
|
|
105
|
+
const pre = det.appDir ? `${det.appDir}/` : "";
|
|
106
|
+
const at = (s) => new RegExp(`^${escapeRe(pre)}${s}`);
|
|
107
|
+
const fw = det.framework.name;
|
|
108
|
+
const code = codeFiles(p);
|
|
109
|
+
if (fw === "next") {
|
|
110
|
+
for (const f of list(p, at(String.raw `(src/)?app/(.+/)?page\.(tsx|ts|jsx|js|mdx|md)$`))) {
|
|
111
|
+
const r = nextApp(f);
|
|
112
|
+
if (r !== null)
|
|
113
|
+
add(out, r, f, fileRoutes);
|
|
114
|
+
}
|
|
115
|
+
for (const f of list(p, at(String.raw `(src/)?app/(.+/)?route\.(ts|js)$`))) {
|
|
116
|
+
const m = f.match(/(?:^|\/)app\/(.*?)\/route\.(ts|js)$/);
|
|
117
|
+
if (m)
|
|
118
|
+
add(out, segsToPath(m[1].split("/").filter((s) => !/^\(.*\)$/.test(s))), f, fileRoutes);
|
|
119
|
+
}
|
|
120
|
+
for (const f of list(p, at(String.raw `(src/)?pages/.+\.(tsx|ts|jsx|js|mdx|md)$`))) {
|
|
121
|
+
const segs = fileSegs(f, /(?:^|\/)pages\/(.+)$/);
|
|
122
|
+
if (!segs || /^_(app|document|error)$/.test(segs[segs.length - 1]) || /^(404|500)$/.test(segs.join("/")))
|
|
123
|
+
continue;
|
|
124
|
+
add(out, segsToPath(stripIndex(segs)), f, fileRoutes);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else if (fw === "astro") {
|
|
128
|
+
for (const f of list(p, at(String.raw `src/pages/.+\.(astro|md|mdx|html|markdown|ts|js)$`))) {
|
|
129
|
+
const segs = fileSegs(f, /src\/pages\/(.+)$/);
|
|
130
|
+
if (!segs || segs.some((s) => s.startsWith("_")))
|
|
131
|
+
continue;
|
|
132
|
+
const endpoint = /\.(ts|js)$/.test(f);
|
|
133
|
+
const last = f.split("/").pop().replace(/\.(ts|js)$/, "");
|
|
134
|
+
if (endpoint && FILE_LIKE.test(last)) {
|
|
135
|
+
fileRoutes.set(last.toLowerCase(), { file: f, line: 1, match: last });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (/^404$/.test(segs.join("/")))
|
|
139
|
+
continue;
|
|
140
|
+
add(out, segsToPath(stripIndex(segs)), f, fileRoutes);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (fw === "nuxt") {
|
|
144
|
+
for (const f of list(p, at(String.raw `(app/)?pages/.+\.vue$`))) {
|
|
145
|
+
const segs = fileSegs(f, /(?:^|\/)pages\/(.+)$/);
|
|
146
|
+
if (segs)
|
|
147
|
+
add(out, segsToPath(stripIndex(segs)), f, fileRoutes);
|
|
148
|
+
}
|
|
149
|
+
for (const f of list(p, at(String.raw `server/(api|routes)/.+\.(ts|js)$`))) {
|
|
150
|
+
const m = f.match(/server\/(api|routes)\/(.+)\.(ts|js)$/);
|
|
151
|
+
if (m)
|
|
152
|
+
add(out, (m[1] === "api" ? "/api/" : "/") + m[2].replace(/(^|\/)index$/, "").replace(/\.(get|post|put|delete)$/, ""), f, fileRoutes);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else if (fw === "sveltekit") {
|
|
156
|
+
for (const f of list(p, at(String.raw `src/routes/(.*/)?\+(page|server)\.(svelte|md|svx|ts|js)$`))) {
|
|
157
|
+
const m = f.match(/src\/routes\/(.*?)\/?\+(page|server)\./);
|
|
158
|
+
if (!m)
|
|
159
|
+
continue;
|
|
160
|
+
const segs = m[1].split("/").filter((s) => s && !/^\(.*\)$/.test(s));
|
|
161
|
+
add(out, segsToPath(segs), f, fileRoutes);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (fw === "remix" || fw === "react-router") {
|
|
165
|
+
const cfgRel = list(p, at(String.raw `app/routes\.(ts|js|mts)$`))[0];
|
|
166
|
+
const cfg = cfgRel ? text(p, cfgRel) : "";
|
|
167
|
+
if (cfg && !/flatRoutes\s*\(/.test(cfg)) {
|
|
168
|
+
for (const m of cfg.matchAll(/\broute\(\s*["'`]([^"'`]*)["'`]\s*,\s*["'`]([^"'`]+)["'`]/g))
|
|
169
|
+
add(out, m[1], `${pre}app/${m[2]}`, fileRoutes);
|
|
170
|
+
for (const m of cfg.matchAll(/\bindex\(\s*["'`]([^"'`]+)["'`]/g))
|
|
171
|
+
add(out, "/", `${pre}app/${m[1]}`, fileRoutes);
|
|
172
|
+
}
|
|
173
|
+
for (const f of list(p, at(String.raw `app/routes/[^/]+\.(tsx|ts|jsx|js|mdx|md)$`))) {
|
|
174
|
+
const name = f.split("/").pop().replace(/\.(tsx|ts|jsx|js|mdx|md)$/, "");
|
|
175
|
+
const r = remixFlat(name);
|
|
176
|
+
if (!/export\s+default/.test(text(p, f)) && /\.(ts|js)$/.test(f)) {
|
|
177
|
+
const last = r.path.split("/").pop() ?? "";
|
|
178
|
+
if (FILE_LIKE.test(last))
|
|
179
|
+
fileRoutes.set(last.toLowerCase(), { file: f, line: 1, match: r.path });
|
|
180
|
+
else
|
|
181
|
+
add(out, r.path, f, fileRoutes);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
add(out, r.path, f, fileRoutes);
|
|
185
|
+
}
|
|
186
|
+
for (const f of list(p, at(String.raw `app/routes/[^/]+/route\.(tsx|ts|jsx|js)$`))) {
|
|
187
|
+
const name = f.split("/").slice(-2, -1)[0];
|
|
188
|
+
add(out, remixFlat(name).path, f, fileRoutes);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (fw === "gatsby") {
|
|
192
|
+
for (const f of list(p, at(String.raw `src/pages/.+\.(tsx|ts|jsx|js)$`))) {
|
|
193
|
+
const segs = fileSegs(f, /src\/pages\/(.+)$/);
|
|
194
|
+
if (!segs || /^404$/.test(segs.join("/")))
|
|
195
|
+
continue;
|
|
196
|
+
add(out, segsToPath(stripIndex(segs)), f, fileRoutes);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (fw === "hugo") {
|
|
200
|
+
for (const f of list(p, /^content\/.+\.(md|markdown|html)$/)) {
|
|
201
|
+
const segs = fileSegs(f, /^content\/(.+)$/);
|
|
202
|
+
const last = segs[segs.length - 1];
|
|
203
|
+
if (last === "_index" || last === "index")
|
|
204
|
+
segs.pop();
|
|
205
|
+
add(out, segsToPath(segs), f, fileRoutes);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else if (fw === "jekyll") {
|
|
209
|
+
for (const f of list(p, /^[^_/][^/]*\.(md|markdown|html)$/)) {
|
|
210
|
+
if (/^(README|CHANGELOG|LICENSE|404)\./i.test(f))
|
|
211
|
+
continue;
|
|
212
|
+
add(out, segsToPath(stripIndex([f.replace(/\.(md|markdown|html)$/, "")])), f, fileRoutes);
|
|
213
|
+
}
|
|
214
|
+
for (const f of list(p, /^_posts\/.+\.(md|markdown|html)$/)) {
|
|
215
|
+
const slug = f.split("/").pop().replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/\.(md|markdown|html)$/, "");
|
|
216
|
+
add(out, `/${slug}`, f, fileRoutes);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (fw === "eleventy" || fw === "static-html" || fw === "vitepress" || fw === "docusaurus") {
|
|
220
|
+
const input = fw === "eleventy" && p.dirs.has("src") ? "src/" : fw === "vitepress" ? "docs/" : fw === "docusaurus" ? "" : pre;
|
|
221
|
+
const re = fw === "static-html" ? /\.html?$/ : /\.(md|njk|liquid|html|11ty\.js|mdx)$/;
|
|
222
|
+
for (const f of p.files.keys()) {
|
|
223
|
+
if (!f.startsWith(input) || !re.test(f))
|
|
224
|
+
continue;
|
|
225
|
+
if (/(^|\/)(_includes|_data|_layouts|node_modules|\.vitepress)\//.test(f) || /(^|\/)(README|CHANGELOG|LICENSE)\.md$/i.test(f))
|
|
226
|
+
continue;
|
|
227
|
+
if (fw === "docusaurus" && !/^(docs|blog|src\/pages)\//.test(f))
|
|
228
|
+
continue;
|
|
229
|
+
const segs = f.slice(input.length).replace(/\.(html?|md|njk|liquid|11ty\.js|mdx)$/, "").split("/");
|
|
230
|
+
if (/^404$/.test(segs.join("/")))
|
|
231
|
+
continue;
|
|
232
|
+
add(out, segsToPath(stripIndex(segs)), f, fileRoutes);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else if (fw === "tanstack-start") {
|
|
236
|
+
for (const f of code) {
|
|
237
|
+
for (const m of f.text.matchAll(/createFileRoute\(\s*["'`]([^"'`]+)["'`]\s*\)/g))
|
|
238
|
+
add(out, tanstackPath(m[1]), f.rel, fileRoutes, lineOf(f.text, m.index ?? 0));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// SPA routers (also used inside other frameworks' client code)
|
|
242
|
+
if (!out.size || ["vite-react", "vite-vue", "vite-svelte", "vite-solid", "vite", "cra", "angular", "static-html", "unknown", "tanstack-start"].includes(fw)) {
|
|
243
|
+
spaRoutes(code, out, fileRoutes, catchAll, fw);
|
|
244
|
+
}
|
|
245
|
+
// Hand-written servers (Workers / Express / Hono) and path tables
|
|
246
|
+
if (fw === "custom-server" || fw === "unknown" || !out.size)
|
|
247
|
+
serverRoutes(code, out, fileRoutes);
|
|
248
|
+
// Prebuilt HTML served from the static folder by a custom server (e.g. public/pricing.html → /pricing)
|
|
249
|
+
if (fw === "custom-server" && det.staticDir) {
|
|
250
|
+
const pretty = det.hosting.provider === "cloudflare" || det.hosting.provider === "vercel" || det.hosting.provider === "netlify";
|
|
251
|
+
for (const f of p.files.keys()) {
|
|
252
|
+
if (!f.startsWith(det.staticDir + "/") || !/\.html?$/.test(f))
|
|
253
|
+
continue;
|
|
254
|
+
const rel = f.slice(det.staticDir.length + 1);
|
|
255
|
+
if (/(^|\/)(404|500)\.html?$/.test(rel))
|
|
256
|
+
continue;
|
|
257
|
+
const segs = rel.split("/");
|
|
258
|
+
const last = segs.pop();
|
|
259
|
+
if (!/^index\.html?$/.test(last))
|
|
260
|
+
segs.push(pretty ? last.replace(/\.html?$/, "") : last);
|
|
261
|
+
add(out, segsToPath(segs), f, fileRoutes);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Machine files served from code in any framework (robots.txt / sitemap.xml / llms.txt routes)
|
|
265
|
+
collectFileRoutes(code, fileRoutes);
|
|
266
|
+
// Hash routing and catch-all routes
|
|
267
|
+
for (const f of code) {
|
|
268
|
+
const t = f.text;
|
|
269
|
+
for (const m of t.matchAll(/\b(HashRouter|createHashRouter|createWebHashHistory|useHash\s*:\s*true|withHashLocation|hashHistory|createHashHistory)\b/g)) {
|
|
270
|
+
hashRouting.push({ file: f.rel, line: lineOf(t, m.index ?? 0), match: m[1] });
|
|
271
|
+
}
|
|
272
|
+
if (/from\s+["']svelte-spa-router["']/.test(t))
|
|
273
|
+
hashRouting.push({ file: f.rel, line: 1, match: "svelte-spa-router (hash-based)" });
|
|
274
|
+
}
|
|
275
|
+
return { routes: [...out.values()], hashRouting, catchAll, fileRoutes };
|
|
276
|
+
}
|
|
277
|
+
function tanstackPath(p) {
|
|
278
|
+
const segs = p.split("/").filter((s) => s && !s.startsWith("_") && !/^\(.*\)$/.test(s));
|
|
279
|
+
return segsToPath(segs);
|
|
280
|
+
}
|
|
281
|
+
function spaRoutes(code, out, fileRoutes, catchAll, fw) {
|
|
282
|
+
for (const f of code) {
|
|
283
|
+
const t = f.text;
|
|
284
|
+
const isRouterFile = ROUTER_LIB.test(t);
|
|
285
|
+
for (const m of t.matchAll(/<Route\b[^>]*?\bpath\s*=\s*(?:\{\s*)?["'`]([^"'`]*)["'`]/g)) {
|
|
286
|
+
const line = lineOf(t, m.index ?? 0);
|
|
287
|
+
if (m[1] === "*" || m[1] === "/*")
|
|
288
|
+
catchAll.push({ file: f.rel, line, match: `path="${m[1]}"` });
|
|
289
|
+
else
|
|
290
|
+
add(out, m[1], f.rel, fileRoutes, line);
|
|
291
|
+
}
|
|
292
|
+
if (isRouterFile) {
|
|
293
|
+
for (const m of t.matchAll(/\bpath\s*:\s*["'`]([^"'`]*)["'`]/g)) {
|
|
294
|
+
const line = lineOf(t, m.index ?? 0);
|
|
295
|
+
const v = m[1];
|
|
296
|
+
if (v === "*" || v === "**" || v === "/*" || /pathMatch\(\.\*\)/.test(v) || v === "/:catchAll(.*)")
|
|
297
|
+
catchAll.push({ file: f.rel, line, match: `path: "${v}"` });
|
|
298
|
+
else if (fw === "angular" || !v.startsWith("/"))
|
|
299
|
+
add(out, v, f.rel, fileRoutes, line);
|
|
300
|
+
else
|
|
301
|
+
add(out, v, f.rel, fileRoutes, line);
|
|
302
|
+
}
|
|
303
|
+
for (const m of t.matchAll(/createFileRoute\(\s*["'`]([^"'`]+)["'`]\s*\)/g))
|
|
304
|
+
add(out, tanstackPath(m[1]), f.rel, fileRoutes, lineOf(t, m.index ?? 0));
|
|
305
|
+
}
|
|
306
|
+
if (/from\s+["']svelte-spa-router["']/.test(t)) {
|
|
307
|
+
for (const m of t.matchAll(/["'`](\/[^"'`]*)["'`]\s*:\s*[A-Za-z_]\w*/g)) {
|
|
308
|
+
if (m[1] === "*")
|
|
309
|
+
catchAll.push({ file: f.rel, line: lineOf(t, m.index ?? 0), match: "*" });
|
|
310
|
+
else
|
|
311
|
+
add(out, m[1], f.rel, fileRoutes, lineOf(t, m.index ?? 0));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const SERVER_CALL = /\b(?:app|router|r|route|routes|server|api|hono|fastify|site|web)\.(?:get|all|on|route)\(\s*(?:["'`](?:GET|HEAD)["'`]\s*,\s*)?["'`](\/[^"'`\s]*)["'`]/g;
|
|
317
|
+
const PATHNAME_EQ = /\b(?:pathname|url\.pathname)\s*===?\s*["'`](\/[^"'`\s]*)["'`]/g;
|
|
318
|
+
const CASE_PATH = /case\s+["'`](\/[^"'`\s]*)["'`]\s*:/g;
|
|
319
|
+
const PATH_TABLE_FILE = /(^|\/)(paths?|routes?|urls?|pages|site-?map|nav|navigation|links|menu)\.(m?[jt]sx?|cjs)$/i;
|
|
320
|
+
const NOT_ROUTES_FILE = /(^|\/)(__mocks__|mocks?|fakes?|stubs?|fixtures?|seeds?|samples?)(\/|[._-])|(^|\/)[^/]*(mock|fake|stub|dev[-_.]?server)[^/]*\.[a-z]+$/i;
|
|
321
|
+
function serverRoutes(code, out, fileRoutes) {
|
|
322
|
+
for (const f of code) {
|
|
323
|
+
if (!/\.(m?[jt]sx?|cjs)$/.test(f.rel) || NOT_ROUTES_FILE.test(f.rel))
|
|
324
|
+
continue;
|
|
325
|
+
const t = f.text;
|
|
326
|
+
for (const re of [SERVER_CALL, PATHNAME_EQ, CASE_PATH]) {
|
|
327
|
+
for (const m of t.matchAll(re))
|
|
328
|
+
add(out, m[1], f.rel, fileRoutes, lineOf(t, m.index ?? 0));
|
|
329
|
+
}
|
|
330
|
+
if (PATH_TABLE_FILE.test(f.rel)) {
|
|
331
|
+
for (const m of t.matchAll(/["'`](\/|\/[a-z0-9][a-z0-9\-/]*)["'`]/g)) {
|
|
332
|
+
if (/\/(static|assets|img|images|fonts|css|js)\//.test(m[1]))
|
|
333
|
+
continue;
|
|
334
|
+
add(out, m[1], f.rel, fileRoutes, lineOf(t, m.index ?? 0));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function collectFileRoutes(code, fileRoutes) {
|
|
340
|
+
const names = ["robots.txt", "sitemap.xml", "sitemap-index.xml", "sitemap_index.xml", "llms.txt", "llms-full.txt"];
|
|
341
|
+
for (const f of code) {
|
|
342
|
+
if (!/\.(m?[jt]sx?|cjs)$/.test(f.rel))
|
|
343
|
+
continue;
|
|
344
|
+
const t = f.text;
|
|
345
|
+
for (const n of names) {
|
|
346
|
+
if (fileRoutes.has(n))
|
|
347
|
+
continue;
|
|
348
|
+
const re = new RegExp(`(?:\\.(?:get|all)\\(|pathname\\s*===?\\s*|case\\s+|path\\s*:\\s*)["'\`]/${escapeRe(n)}["'\`]`);
|
|
349
|
+
const m = t.match(re);
|
|
350
|
+
if (m)
|
|
351
|
+
fileRoutes.set(n, { file: f.rel, line: lineOf(t, m.index ?? 0), match: `/${n}` });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|