codesight 1.0.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/README.md +213 -0
- package/dist/detectors/components.d.ts +2 -0
- package/dist/detectors/components.js +237 -0
- package/dist/detectors/config.d.ts +2 -0
- package/dist/detectors/config.js +142 -0
- package/dist/detectors/contracts.d.ts +6 -0
- package/dist/detectors/contracts.js +118 -0
- package/dist/detectors/graph.d.ts +2 -0
- package/dist/detectors/graph.js +113 -0
- package/dist/detectors/libs.d.ts +2 -0
- package/dist/detectors/libs.js +206 -0
- package/dist/detectors/middleware.d.ts +2 -0
- package/dist/detectors/middleware.js +116 -0
- package/dist/detectors/routes.d.ts +2 -0
- package/dist/detectors/routes.js +356 -0
- package/dist/detectors/schema.d.ts +2 -0
- package/dist/detectors/schema.js +283 -0
- package/dist/detectors/tokens.d.ts +6 -0
- package/dist/detectors/tokens.js +48 -0
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +268 -0
- package/dist/generators/ai-config.d.ts +2 -0
- package/dist/generators/ai-config.js +137 -0
- package/dist/generators/html-report.d.ts +2 -0
- package/dist/generators/html-report.js +200 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +304 -0
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +171 -0
- package/dist/scanner.d.ts +4 -0
- package/dist/scanner.js +329 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.js +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# codesight
|
|
2
|
+
|
|
3
|
+
See your codebase clearly. One command gives your AI assistant complete project understanding.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx codesight
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
**Works with Claude Code, Cursor, GitHub Copilot, OpenAI Codex, Windsurf, Cline, and any AI coding tool.**
|
|
10
|
+
|
|
11
|
+
## The Problem
|
|
12
|
+
|
|
13
|
+
Every AI coding conversation starts the same way: your assistant burns thousands of tokens exploring files, grepping for patterns, and reading configs just to understand your project. On a 65-route Hono API with 18 Drizzle models, that costs **~68,000 tokens per conversation** in exploration alone.
|
|
14
|
+
|
|
15
|
+
codesight pre-generates that understanding into ~4,000 tokens of structured context. **Your AI starts every conversation already knowing your routes, schema, components, dependencies, and architecture.**
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx codesight # Scan and generate context
|
|
21
|
+
npx codesight --init # Also generate CLAUDE.md, .cursorrules, codex.md, AGENTS.md
|
|
22
|
+
npx codesight --open # Also open interactive HTML report in browser
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## What It Generates
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
.codesight/
|
|
29
|
+
CODESIGHT.md # Combined AI context map (point your assistant here)
|
|
30
|
+
routes.md # API routes with methods, params, request/response types, tags
|
|
31
|
+
schema.md # Database models with fields, types, PKs, FKs, relations
|
|
32
|
+
components.md # UI components with props (shadcn/radix filtered out)
|
|
33
|
+
libs.md # Library exports with function signatures
|
|
34
|
+
config.md # Env vars (required vs default), config files, key deps
|
|
35
|
+
middleware.md # Auth, rate limiting, CORS, validation, logging
|
|
36
|
+
graph.md # Dependency graph: most-imported files + import map
|
|
37
|
+
report.html # Interactive visual report (with --html or --open)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Real Output Example
|
|
41
|
+
|
|
42
|
+
From a production SaaS monorepo (Hono + Drizzle + React):
|
|
43
|
+
|
|
44
|
+
```markdown
|
|
45
|
+
# savemrr — AI Context Map
|
|
46
|
+
|
|
47
|
+
> **Stack:** hono | drizzle | react | typescript
|
|
48
|
+
> **Monorepo:** @savemrr/api, @savemrr/dashboard, @savemrr/widget, @savemrr/shared
|
|
49
|
+
|
|
50
|
+
> 65 routes | 18 models | 16 components | 36 lib files | 22 env vars | 5 middleware | 53 import links
|
|
51
|
+
> **Token savings:** this file is ~4,041 tokens. Without it, AI exploration would cost ~68,640 tokens.
|
|
52
|
+
> **Saves ~64,599 tokens per conversation.**
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
# Routes
|
|
57
|
+
|
|
58
|
+
- `POST` `/magic-link` [auth, db, cache, email]
|
|
59
|
+
- `POST` `/verify` [auth, db, cache, email]
|
|
60
|
+
- `GET` `/me` [auth, db, cache, email]
|
|
61
|
+
- `GET` `/overview` [auth, db, queue, payment, ai]
|
|
62
|
+
- `GET` `/shield` [auth, db, queue, payment, ai]
|
|
63
|
+
- `GET` `/rescue` [auth, db, queue, payment, ai]
|
|
64
|
+
- `POST` `/rescue/:id/send-now` params(id) [auth, db, queue, payment, ai]
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
# Schema
|
|
68
|
+
|
|
69
|
+
### users
|
|
70
|
+
- id: uuid (pk, default)
|
|
71
|
+
- email: text (unique, required)
|
|
72
|
+
- tier: text (required, default)
|
|
73
|
+
- polarSubId: text (fk)
|
|
74
|
+
- settings: jsonb (required, default)
|
|
75
|
+
|
|
76
|
+
### stripe_connections
|
|
77
|
+
- id: uuid (pk, default)
|
|
78
|
+
- userId: uuid (fk)
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
# Dependency Graph
|
|
82
|
+
|
|
83
|
+
## Most Imported Files (change these carefully)
|
|
84
|
+
- `packages/shared/src/index.ts` — imported by **12** files
|
|
85
|
+
- `apps/api/src/lib/db.ts` — imported by **8** files
|
|
86
|
+
- `apps/api/src/lib/auth.ts` — imported by **6** files
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What It Detects
|
|
90
|
+
|
|
91
|
+
| Category | Supported |
|
|
92
|
+
|---|---|
|
|
93
|
+
| **Routes** | Hono, Express, Fastify, Next.js (App + Pages), Koa, FastAPI, Flask, Django, Go (net/http, Gin, Fiber) |
|
|
94
|
+
| **Schema** | Drizzle, Prisma, TypeORM, SQLAlchemy |
|
|
95
|
+
| **Components** | React, Vue, Svelte (filters out shadcn/radix primitives) |
|
|
96
|
+
| **Libraries** | TypeScript/JavaScript, Python, Go exports with function signatures |
|
|
97
|
+
| **Config** | Environment variables (required vs defaults), config files, notable dependencies |
|
|
98
|
+
| **Middleware** | Auth, rate limiting, CORS, validation, logging, error handlers |
|
|
99
|
+
| **Dependencies** | Import graph with hot file detection (most imported = highest blast radius) |
|
|
100
|
+
| **Contracts** | URL params, request types, response types extracted from route handlers |
|
|
101
|
+
| **Monorepos** | pnpm, npm, yarn workspaces with cross-workspace detection |
|
|
102
|
+
| **Languages** | TypeScript, JavaScript, Python, Go |
|
|
103
|
+
|
|
104
|
+
## Auto-Generate AI Config Files
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx codesight --init
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Generates instruction files for every major AI coding tool:
|
|
111
|
+
|
|
112
|
+
| File | Tool |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `CLAUDE.md` | Claude Code |
|
|
115
|
+
| `.cursorrules` | Cursor |
|
|
116
|
+
| `.github/copilot-instructions.md` | GitHub Copilot |
|
|
117
|
+
| `codex.md` | OpenAI Codex CLI |
|
|
118
|
+
| `AGENTS.md` | OpenAI Codex |
|
|
119
|
+
|
|
120
|
+
Each file includes your project's stack, key architecture facts, hot files, and required env vars so your AI assistant knows the project from the first message.
|
|
121
|
+
|
|
122
|
+
## Interactive HTML Report
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx codesight --open
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Generates a visual dashboard with:
|
|
129
|
+
- Token savings hero metric
|
|
130
|
+
- Route table with methods, contracts, and tags
|
|
131
|
+
- Schema cards with fields and relations
|
|
132
|
+
- Dependency hot files with impact bars
|
|
133
|
+
- Environment variables (required vs defaults)
|
|
134
|
+
- Full middleware map
|
|
135
|
+
|
|
136
|
+
Screenshots of this report are what go viral on Twitter.
|
|
137
|
+
|
|
138
|
+
## MCP Server
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx codesight --mcp
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Runs as a Model Context Protocol server that Claude Code and Cursor can connect to. Add to your MCP config:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"codesight": {
|
|
150
|
+
"command": "npx",
|
|
151
|
+
"args": ["codesight", "--mcp"]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Your AI assistant can then call the `codesight_scan` tool to get full project context on demand.
|
|
158
|
+
|
|
159
|
+
## Watch Mode
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npx codesight --watch
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Re-scans automatically when files change. Keeps context fresh during development.
|
|
166
|
+
|
|
167
|
+
## Git Pre-Commit Hook
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
npx codesight --hook
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Installs a git pre-commit hook that regenerates context on every commit. Context stays up to date automatically.
|
|
174
|
+
|
|
175
|
+
## All Options
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npx codesight # Scan current directory
|
|
179
|
+
npx codesight ./my-project # Scan specific directory
|
|
180
|
+
npx codesight --init # Generate AI config files
|
|
181
|
+
npx codesight --open # Open interactive HTML report
|
|
182
|
+
npx codesight --html # Generate HTML report (no open)
|
|
183
|
+
npx codesight --mcp # Start MCP server
|
|
184
|
+
npx codesight --watch # Watch mode
|
|
185
|
+
npx codesight --hook # Install git pre-commit hook
|
|
186
|
+
npx codesight --json # Output JSON
|
|
187
|
+
npx codesight -o .ai-context # Custom output directory
|
|
188
|
+
npx codesight -d 5 # Limit directory depth
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Why Not Repomix?
|
|
192
|
+
|
|
193
|
+
Repomix dumps your entire codebase into one file. codesight maps your architecture.
|
|
194
|
+
|
|
195
|
+
| | Repomix | codesight |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| **Approach** | Raw code dump | Structured intelligence |
|
|
198
|
+
| **Output** | One giant text blob | Semantic context files |
|
|
199
|
+
| **AI gets** | "Here is all the code" | "Here is the architecture" |
|
|
200
|
+
| **Token cost** | Huge (entire codebase) | Tiny (~4K tokens for full map) |
|
|
201
|
+
| **Route discovery** | Read every file | Instant from routes.md |
|
|
202
|
+
| **Schema understanding** | Read every migration | Instant from schema.md |
|
|
203
|
+
| **Change blast radius** | Unknown | Visible in dependency graph |
|
|
204
|
+
| **AI config generation** | No | CLAUDE.md, .cursorrules, codex.md, AGENTS.md |
|
|
205
|
+
| **Runtime deps** | 26 | **Zero** |
|
|
206
|
+
|
|
207
|
+
## Zero Dependencies
|
|
208
|
+
|
|
209
|
+
codesight has zero runtime dependencies. Just Node.js built-ins. Fast, portable, no supply chain risk.
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { relative, basename, extname } from "node:path";
|
|
2
|
+
import { readFileSafe } from "../scanner.js";
|
|
3
|
+
// shadcn/ui + radix primitives to filter out
|
|
4
|
+
const UI_PRIMITIVES = new Set([
|
|
5
|
+
"accordion",
|
|
6
|
+
"alert",
|
|
7
|
+
"alert-dialog",
|
|
8
|
+
"aspect-ratio",
|
|
9
|
+
"avatar",
|
|
10
|
+
"badge",
|
|
11
|
+
"breadcrumb",
|
|
12
|
+
"button",
|
|
13
|
+
"calendar",
|
|
14
|
+
"card",
|
|
15
|
+
"carousel",
|
|
16
|
+
"chart",
|
|
17
|
+
"checkbox",
|
|
18
|
+
"collapsible",
|
|
19
|
+
"command",
|
|
20
|
+
"context-menu",
|
|
21
|
+
"data-table",
|
|
22
|
+
"date-picker",
|
|
23
|
+
"dialog",
|
|
24
|
+
"drawer",
|
|
25
|
+
"dropdown-menu",
|
|
26
|
+
"form",
|
|
27
|
+
"hover-card",
|
|
28
|
+
"input",
|
|
29
|
+
"input-otp",
|
|
30
|
+
"label",
|
|
31
|
+
"menubar",
|
|
32
|
+
"navigation-menu",
|
|
33
|
+
"pagination",
|
|
34
|
+
"popover",
|
|
35
|
+
"progress",
|
|
36
|
+
"radio-group",
|
|
37
|
+
"resizable",
|
|
38
|
+
"scroll-area",
|
|
39
|
+
"select",
|
|
40
|
+
"separator",
|
|
41
|
+
"sheet",
|
|
42
|
+
"sidebar",
|
|
43
|
+
"skeleton",
|
|
44
|
+
"slider",
|
|
45
|
+
"sonner",
|
|
46
|
+
"switch",
|
|
47
|
+
"table",
|
|
48
|
+
"tabs",
|
|
49
|
+
"textarea",
|
|
50
|
+
"toast",
|
|
51
|
+
"toaster",
|
|
52
|
+
"toggle",
|
|
53
|
+
"toggle-group",
|
|
54
|
+
"tooltip",
|
|
55
|
+
]);
|
|
56
|
+
function isUIPrimitive(filePath) {
|
|
57
|
+
const name = basename(filePath, extname(filePath)).toLowerCase();
|
|
58
|
+
return (UI_PRIMITIVES.has(name) ||
|
|
59
|
+
filePath.includes("/ui/") ||
|
|
60
|
+
filePath.includes("/components/ui/") ||
|
|
61
|
+
filePath.includes("@radix-ui") ||
|
|
62
|
+
filePath.includes("@shadcn"));
|
|
63
|
+
}
|
|
64
|
+
export async function detectComponents(files, project) {
|
|
65
|
+
switch (project.componentFramework) {
|
|
66
|
+
case "react":
|
|
67
|
+
return detectReactComponents(files, project);
|
|
68
|
+
case "vue":
|
|
69
|
+
return detectVueComponents(files, project);
|
|
70
|
+
case "svelte":
|
|
71
|
+
return detectSvelteComponents(files, project);
|
|
72
|
+
default:
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// --- React ---
|
|
77
|
+
async function detectReactComponents(files, project) {
|
|
78
|
+
const componentFiles = files.filter((f) => (f.endsWith(".tsx") || f.endsWith(".jsx")) &&
|
|
79
|
+
!f.includes("node_modules") &&
|
|
80
|
+
!f.endsWith(".test.tsx") &&
|
|
81
|
+
!f.endsWith(".test.jsx") &&
|
|
82
|
+
!f.endsWith(".spec.tsx") &&
|
|
83
|
+
!f.endsWith(".spec.jsx") &&
|
|
84
|
+
!f.endsWith(".stories.tsx") &&
|
|
85
|
+
!f.endsWith(".stories.jsx"));
|
|
86
|
+
const components = [];
|
|
87
|
+
for (const file of componentFiles) {
|
|
88
|
+
if (isUIPrimitive(file))
|
|
89
|
+
continue;
|
|
90
|
+
const content = await readFileSafe(file);
|
|
91
|
+
if (!content)
|
|
92
|
+
continue;
|
|
93
|
+
const rel = relative(project.root, file);
|
|
94
|
+
// Detect component name from export
|
|
95
|
+
let name = "";
|
|
96
|
+
// export default function ComponentName
|
|
97
|
+
const defaultFnMatch = content.match(/export\s+default\s+function\s+(\w+)/);
|
|
98
|
+
if (defaultFnMatch) {
|
|
99
|
+
name = defaultFnMatch[1];
|
|
100
|
+
}
|
|
101
|
+
// export function ComponentName (starts with uppercase)
|
|
102
|
+
if (!name) {
|
|
103
|
+
const namedFnMatch = content.match(/export\s+(?:async\s+)?function\s+([A-Z]\w+)/);
|
|
104
|
+
if (namedFnMatch)
|
|
105
|
+
name = namedFnMatch[1];
|
|
106
|
+
}
|
|
107
|
+
// const ComponentName = ... (with export)
|
|
108
|
+
if (!name) {
|
|
109
|
+
const constMatch = content.match(/export\s+const\s+([A-Z]\w+)\s*(?::\s*\w+)?\s*=/);
|
|
110
|
+
if (constMatch)
|
|
111
|
+
name = constMatch[1];
|
|
112
|
+
}
|
|
113
|
+
// Fallback: any function starting with uppercase that returns JSX
|
|
114
|
+
if (!name) {
|
|
115
|
+
const anyComponent = content.match(/(?:function|const)\s+([A-Z]\w+)/);
|
|
116
|
+
if (anyComponent && (content.includes("<") || content.includes("jsx"))) {
|
|
117
|
+
name = anyComponent[1];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!name)
|
|
121
|
+
continue;
|
|
122
|
+
// Detect props
|
|
123
|
+
const props = [];
|
|
124
|
+
// interface/type Props { ... }
|
|
125
|
+
const propsPattern = /(?:interface|type)\s+(?:\w*Props\w*)\s*(?:=\s*)?\{([^}]*)}/;
|
|
126
|
+
const propsMatch = content.match(propsPattern);
|
|
127
|
+
if (propsMatch) {
|
|
128
|
+
const propsBody = propsMatch[1];
|
|
129
|
+
for (const line of propsBody.split("\n")) {
|
|
130
|
+
const propMatch = line.match(/^\s*(\w+)\s*[?]?\s*:/);
|
|
131
|
+
if (propMatch && propMatch[1] !== "children") {
|
|
132
|
+
props.push(propMatch[1]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Destructured props: function Component({ prop1, prop2 }: Props)
|
|
137
|
+
if (props.length === 0) {
|
|
138
|
+
const destructuredMatch = content.match(new RegExp(`function\\s+${name}\\s*\\(\\s*\\{([^}]*)\\}`));
|
|
139
|
+
if (destructuredMatch) {
|
|
140
|
+
for (const prop of destructuredMatch[1].split(",")) {
|
|
141
|
+
const trimmed = prop.trim().split(/[=:]/)[0].trim();
|
|
142
|
+
if (trimmed && trimmed !== "children" && !trimmed.startsWith("...")) {
|
|
143
|
+
props.push(trimmed);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const isClient = content.slice(0, 50).includes("use client");
|
|
149
|
+
const isServer = content.slice(0, 50).includes("use server");
|
|
150
|
+
components.push({
|
|
151
|
+
name,
|
|
152
|
+
file: rel,
|
|
153
|
+
props: props.slice(0, 10), // limit to 10 props
|
|
154
|
+
isClient,
|
|
155
|
+
isServer,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return components;
|
|
159
|
+
}
|
|
160
|
+
// --- Vue ---
|
|
161
|
+
async function detectVueComponents(files, project) {
|
|
162
|
+
const vueFiles = files.filter((f) => f.endsWith(".vue") && !f.includes("node_modules"));
|
|
163
|
+
const components = [];
|
|
164
|
+
for (const file of vueFiles) {
|
|
165
|
+
if (isUIPrimitive(file))
|
|
166
|
+
continue;
|
|
167
|
+
const content = await readFileSafe(file);
|
|
168
|
+
const rel = relative(project.root, file);
|
|
169
|
+
const name = basename(file, ".vue");
|
|
170
|
+
const props = [];
|
|
171
|
+
// defineProps<{ ... }>() or defineProps({ ... })
|
|
172
|
+
const definePropsMatch = content.match(/defineProps\s*[<(]\s*\{([^}]*)\}/);
|
|
173
|
+
if (definePropsMatch) {
|
|
174
|
+
for (const line of definePropsMatch[1].split("\n")) {
|
|
175
|
+
const propMatch = line.match(/^\s*(\w+)\s*[?]?\s*:/);
|
|
176
|
+
if (propMatch)
|
|
177
|
+
props.push(propMatch[1]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// props: { ... } in Options API
|
|
181
|
+
if (props.length === 0) {
|
|
182
|
+
const optionsPropsMatch = content.match(/props\s*:\s*\{([^}]*)\}/);
|
|
183
|
+
if (optionsPropsMatch) {
|
|
184
|
+
for (const line of optionsPropsMatch[1].split("\n")) {
|
|
185
|
+
const propMatch = line.match(/^\s*(\w+)\s*[?]?\s*:/);
|
|
186
|
+
if (propMatch)
|
|
187
|
+
props.push(propMatch[1]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
components.push({
|
|
192
|
+
name,
|
|
193
|
+
file: rel,
|
|
194
|
+
props: props.slice(0, 10),
|
|
195
|
+
isClient: true,
|
|
196
|
+
isServer: false,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return components;
|
|
200
|
+
}
|
|
201
|
+
// --- Svelte ---
|
|
202
|
+
async function detectSvelteComponents(files, project) {
|
|
203
|
+
const svelteFiles = files.filter((f) => f.endsWith(".svelte") && !f.includes("node_modules"));
|
|
204
|
+
const components = [];
|
|
205
|
+
for (const file of svelteFiles) {
|
|
206
|
+
if (isUIPrimitive(file))
|
|
207
|
+
continue;
|
|
208
|
+
const content = await readFileSafe(file);
|
|
209
|
+
const rel = relative(project.root, file);
|
|
210
|
+
const name = basename(file, ".svelte");
|
|
211
|
+
const props = [];
|
|
212
|
+
// export let propName (Svelte 4)
|
|
213
|
+
const exportLetPattern = /export\s+let\s+(\w+)/g;
|
|
214
|
+
let match;
|
|
215
|
+
while ((match = exportLetPattern.exec(content)) !== null) {
|
|
216
|
+
props.push(match[1]);
|
|
217
|
+
}
|
|
218
|
+
// $props() (Svelte 5)
|
|
219
|
+
const propsMatch = content.match(/let\s+\{([^}]*)\}\s*=\s*\$props\(\)/);
|
|
220
|
+
if (propsMatch) {
|
|
221
|
+
for (const prop of propsMatch[1].split(",")) {
|
|
222
|
+
const trimmed = prop.trim().split(/[=:]/)[0].trim();
|
|
223
|
+
if (trimmed && !trimmed.startsWith("...")) {
|
|
224
|
+
props.push(trimmed);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
components.push({
|
|
229
|
+
name,
|
|
230
|
+
file: rel,
|
|
231
|
+
props: props.slice(0, 10),
|
|
232
|
+
isClient: true,
|
|
233
|
+
isServer: false,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return components;
|
|
237
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { relative, join, basename } from "node:path";
|
|
3
|
+
import { readFileSafe } from "../scanner.js";
|
|
4
|
+
const CONFIG_FILES = [
|
|
5
|
+
"tsconfig.json",
|
|
6
|
+
"next.config.js",
|
|
7
|
+
"next.config.mjs",
|
|
8
|
+
"next.config.ts",
|
|
9
|
+
"vite.config.ts",
|
|
10
|
+
"vite.config.js",
|
|
11
|
+
"tailwind.config.ts",
|
|
12
|
+
"tailwind.config.js",
|
|
13
|
+
"drizzle.config.ts",
|
|
14
|
+
"wrangler.toml",
|
|
15
|
+
"docker-compose.yml",
|
|
16
|
+
"docker-compose.yaml",
|
|
17
|
+
"Dockerfile",
|
|
18
|
+
".env.example",
|
|
19
|
+
"pyproject.toml",
|
|
20
|
+
"go.mod",
|
|
21
|
+
"Cargo.toml",
|
|
22
|
+
"railway.json",
|
|
23
|
+
"vercel.json",
|
|
24
|
+
"fly.toml",
|
|
25
|
+
"render.yaml",
|
|
26
|
+
];
|
|
27
|
+
export async function detectConfig(files, project) {
|
|
28
|
+
// Find config files
|
|
29
|
+
const configFiles = files
|
|
30
|
+
.filter((f) => {
|
|
31
|
+
const name = basename(f);
|
|
32
|
+
return CONFIG_FILES.includes(name);
|
|
33
|
+
})
|
|
34
|
+
.map((f) => relative(project.root, f));
|
|
35
|
+
// Also check root for config files that might not have code extensions
|
|
36
|
+
for (const cf of CONFIG_FILES) {
|
|
37
|
+
const content = await readFileSafe(join(project.root, cf));
|
|
38
|
+
if (content) {
|
|
39
|
+
const rel = cf;
|
|
40
|
+
if (!configFiles.includes(rel))
|
|
41
|
+
configFiles.push(rel);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Detect env vars
|
|
45
|
+
const envVars = await detectEnvVars(files, project);
|
|
46
|
+
// Detect dependencies
|
|
47
|
+
let dependencies = {};
|
|
48
|
+
let devDependencies = {};
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(await readFile(join(project.root, "package.json"), "utf-8"));
|
|
51
|
+
dependencies = pkg.dependencies || {};
|
|
52
|
+
devDependencies = pkg.devDependencies || {};
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
return {
|
|
56
|
+
envVars,
|
|
57
|
+
configFiles: configFiles.sort(),
|
|
58
|
+
dependencies,
|
|
59
|
+
devDependencies,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function detectEnvVars(files, project) {
|
|
63
|
+
const envMap = new Map();
|
|
64
|
+
// Parse .env.example and .env files for declarations
|
|
65
|
+
const envFiles = files.filter((f) => basename(f) === ".env" ||
|
|
66
|
+
basename(f) === ".env.example" ||
|
|
67
|
+
basename(f) === ".env.local");
|
|
68
|
+
for (const file of envFiles) {
|
|
69
|
+
const content = await readFileSafe(file);
|
|
70
|
+
for (const line of content.split("\n")) {
|
|
71
|
+
const trimmed = line.trim();
|
|
72
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
73
|
+
continue;
|
|
74
|
+
const match = trimmed.match(/^([A-Z_][A-Z0-9_]*)\s*=/);
|
|
75
|
+
if (match) {
|
|
76
|
+
const name = match[1];
|
|
77
|
+
const hasDefault = trimmed.includes("=") && trimmed.split("=")[1].trim().length > 0;
|
|
78
|
+
envMap.set(name, {
|
|
79
|
+
name,
|
|
80
|
+
source: relative(project.root, file),
|
|
81
|
+
hasDefault,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Scan code for process.env.VAR_NAME or os.environ["VAR_NAME"] or os.Getenv("VAR_NAME")
|
|
87
|
+
const codeFiles = files.filter((f) => f.match(/\.(ts|js|tsx|jsx|mjs|cjs|py|go)$/) &&
|
|
88
|
+
!f.includes("node_modules"));
|
|
89
|
+
for (const file of codeFiles) {
|
|
90
|
+
const content = await readFileSafe(file);
|
|
91
|
+
const rel = relative(project.root, file);
|
|
92
|
+
// process.env.VAR_NAME or process.env["VAR_NAME"]
|
|
93
|
+
const nodeEnvPattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
94
|
+
let match;
|
|
95
|
+
while ((match = nodeEnvPattern.exec(content)) !== null) {
|
|
96
|
+
const name = match[1];
|
|
97
|
+
if (!envMap.has(name)) {
|
|
98
|
+
envMap.set(name, { name, source: rel, hasDefault: false });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const nodeEnvBracket = /process\.env\[['"]([A-Z_][A-Z0-9_]*)['"]\]/g;
|
|
102
|
+
while ((match = nodeEnvBracket.exec(content)) !== null) {
|
|
103
|
+
const name = match[1];
|
|
104
|
+
if (!envMap.has(name)) {
|
|
105
|
+
envMap.set(name, { name, source: rel, hasDefault: false });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Bun.env.VAR_NAME
|
|
109
|
+
const bunEnvPattern = /Bun\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
110
|
+
while ((match = bunEnvPattern.exec(content)) !== null) {
|
|
111
|
+
const name = match[1];
|
|
112
|
+
if (!envMap.has(name)) {
|
|
113
|
+
envMap.set(name, { name, source: rel, hasDefault: false });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// import.meta.env.VITE_VAR_NAME
|
|
117
|
+
const viteEnvPattern = /import\.meta\.env\.([A-Z_][A-Z0-9_]*)/g;
|
|
118
|
+
while ((match = viteEnvPattern.exec(content)) !== null) {
|
|
119
|
+
const name = match[1];
|
|
120
|
+
if (!envMap.has(name)) {
|
|
121
|
+
envMap.set(name, { name, source: rel, hasDefault: false });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Python: os.environ["VAR"] or os.environ.get("VAR") or os.getenv("VAR")
|
|
125
|
+
const pyEnvPattern = /os\.(?:environ\[['"]|environ\.get\s*\(['"]|getenv\s*\(['"])([A-Z_][A-Z0-9_]*)['"]/g;
|
|
126
|
+
while ((match = pyEnvPattern.exec(content)) !== null) {
|
|
127
|
+
const name = match[1];
|
|
128
|
+
if (!envMap.has(name)) {
|
|
129
|
+
envMap.set(name, { name, source: rel, hasDefault: false });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Go: os.Getenv("VAR")
|
|
133
|
+
const goEnvPattern = /os\.Getenv\(["']([A-Z_][A-Z0-9_]*)["']\)/g;
|
|
134
|
+
while ((match = goEnvPattern.exec(content)) !== null) {
|
|
135
|
+
const name = match[1];
|
|
136
|
+
if (!envMap.has(name)) {
|
|
137
|
+
envMap.set(name, { name, source: rel, hasDefault: false });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Array.from(envMap.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
142
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RouteInfo, ProjectInfo } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Enhances route info with request/response type information
|
|
4
|
+
* by scanning the route handler files for type annotations
|
|
5
|
+
*/
|
|
6
|
+
export declare function enrichRouteContracts(routes: RouteInfo[], project: ProjectInfo): Promise<RouteInfo[]>;
|