spec-gen-cli 1.2.7 → 1.2.8
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 +135 -41
- package/dist/api/generate.d.ts.map +1 -1
- package/dist/api/generate.js +1 -0
- package/dist/api/generate.js.map +1 -1
- package/dist/api/types.d.ts +2 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/commands/analyze.d.ts +3 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +112 -17
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/drift.d.ts.map +1 -1
- package/dist/cli/commands/drift.js +8 -10
- package/dist/cli/commands/drift.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +12 -36
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts +2 -2
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +105 -2
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +9 -47
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +17 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +201 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/verify.d.ts.map +1 -1
- package/dist/cli/commands/verify.js +7 -8
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/index.js +12 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/constants.d.ts +10 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
- package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
- package/dist/core/analyzer/ai-config-generator.js +85 -0
- package/dist/core/analyzer/ai-config-generator.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +27 -2
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
- package/dist/core/analyzer/artifact-generator.js +86 -8
- package/dist/core/analyzer/artifact-generator.js.map +1 -1
- package/dist/core/analyzer/codebase-digest.d.ts.map +1 -1
- package/dist/core/analyzer/codebase-digest.js +12 -11
- package/dist/core/analyzer/codebase-digest.js.map +1 -1
- package/dist/core/analyzer/env-extractor.d.ts +33 -0
- package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/env-extractor.js +196 -0
- package/dist/core/analyzer/env-extractor.js.map +1 -0
- package/dist/core/analyzer/http-route-parser.d.ts +36 -1
- package/dist/core/analyzer/http-route-parser.d.ts.map +1 -1
- package/dist/core/analyzer/http-route-parser.js +276 -0
- package/dist/core/analyzer/http-route-parser.js.map +1 -1
- package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
- package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/middleware-extractor.js +195 -0
- package/dist/core/analyzer/middleware-extractor.js.map +1 -0
- package/dist/core/analyzer/schema-extractor.d.ts +41 -0
- package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/schema-extractor.js +229 -0
- package/dist/core/analyzer/schema-extractor.js.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.js +245 -0
- package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
- package/dist/core/generator/openspec-format-generator.d.ts.map +1 -1
- package/dist/core/generator/openspec-format-generator.js +8 -0
- package/dist/core/generator/openspec-format-generator.js.map +1 -1
- package/dist/core/generator/spec-pipeline.d.ts +9 -0
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -1
- package/dist/core/generator/spec-pipeline.js +94 -2
- package/dist/core/generator/spec-pipeline.js.map +1 -1
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -1
- package/dist/core/generator/stages/stage1-survey.js +43 -0
- package/dist/core/generator/stages/stage1-survey.js.map +1 -1
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -1
- package/dist/core/generator/stages/stage2-entities.js +6 -2
- package/dist/core/generator/stages/stage2-entities.js.map +1 -1
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -1
- package/dist/core/generator/stages/stage3-services.js +9 -2
- package/dist/core/generator/stages/stage3-services.js.map +1 -1
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -1
- package/dist/core/generator/stages/stage4-api.js +6 -2
- package/dist/core/generator/stages/stage4-api.js.map +1 -1
- package/dist/core/services/llm-service.d.ts +8 -9
- package/dist/core/services/llm-service.d.ts.map +1 -1
- package/dist/core/services/llm-service.js +59 -12
- package/dist/core/services/llm-service.js.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.d.ts +27 -1
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.js +165 -2
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
- package/dist/core/verifier/verification-engine.d.ts +67 -6
- package/dist/core/verifier/verification-engine.d.ts.map +1 -1
- package/dist/core/verifier/verification-engine.js +316 -90
- package/dist/core/verifier/verification-engine.js.map +1 -1
- package/dist/types/pipeline.d.ts +9 -0
- package/dist/types/pipeline.d.ts.map +1 -1
- package/dist/utils/command-helpers.d.ts +30 -0
- package/dist/utils/command-helpers.d.ts.map +1 -1
- package/dist/utils/command-helpers.js +69 -1
- package/dist/utils/command-helpers.js.map +1 -1
- package/examples/bmad/README.md +113 -0
- package/examples/bmad/agents/architect.md +226 -0
- package/examples/bmad/agents/dev-brownfield.md +69 -0
- package/examples/bmad/setup/architect.customize.yaml +14 -0
- package/examples/bmad/tasks/implement-story.md +254 -0
- package/examples/bmad/tasks/onboarding.md +169 -0
- package/examples/bmad/tasks/refactor.md +178 -0
- package/examples/bmad/tasks/sprint-planning.md +168 -0
- package/examples/bmad/templates/story.md +108 -0
- package/examples/cline-workflows/spec-gen-analyze-codebase.md +100 -0
- package/examples/cline-workflows/spec-gen-check-spec-drift.md +102 -0
- package/examples/cline-workflows/spec-gen-execute-refactor.md +194 -0
- package/examples/cline-workflows/spec-gen-implement-feature.md +238 -0
- package/examples/cline-workflows/spec-gen-plan-refactor.md +255 -0
- package/examples/cline-workflows/spec-gen-refactor-codebase.md +16 -0
- package/examples/drift-demo/openspec/config.yaml +14 -0
- package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
- package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
- package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
- package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
- package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
- package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
- package/examples/drift-demo/package.json +21 -0
- package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
- package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
- package/examples/drift-demo/src/auth/auth-service.ts +45 -0
- package/examples/drift-demo/src/database/connection.ts +27 -0
- package/examples/drift-demo/src/index.ts +16 -0
- package/examples/drift-demo/src/projects/project-model.ts +15 -0
- package/examples/drift-demo/src/projects/project-service.ts +34 -0
- package/examples/drift-demo/src/tasks/task-model.ts +37 -0
- package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
- package/examples/drift-demo/src/tasks/task-service.ts +60 -0
- package/examples/drift-demo/src/utils/validation.ts +11 -0
- package/examples/drift-demo/tests/auth.test.ts +4 -0
- package/examples/drift-demo/tests/tasks.test.ts +4 -0
- package/examples/drift-demo/tsconfig.json +10 -0
- package/examples/drift-test/run-drift-test.sh +1087 -0
- package/examples/gsd/README.md +119 -0
- package/examples/gsd/commands/gsd/spec-gen-drift.md +111 -0
- package/examples/gsd/commands/gsd/spec-gen-orient.md +191 -0
- package/examples/mistral-vibe/README.md +101 -0
- package/examples/mistral-vibe/antipatterns-template.md +18 -0
- package/examples/mistral-vibe/skills/spec-gen-analyze-codebase/SKILL.md +123 -0
- package/examples/mistral-vibe/skills/spec-gen-brainstorm/SKILL.md +379 -0
- package/examples/mistral-vibe/skills/spec-gen-debug/SKILL.md +320 -0
- package/examples/mistral-vibe/skills/spec-gen-execute-refactor/SKILL.md +210 -0
- package/examples/mistral-vibe/skills/spec-gen-generate/SKILL.md +245 -0
- package/examples/mistral-vibe/skills/spec-gen-implement-story/SKILL.md +274 -0
- package/examples/mistral-vibe/skills/spec-gen-plan-refactor/SKILL.md +251 -0
- package/examples/openspec-analysis/README.md +59 -0
- package/examples/openspec-analysis/SUMMARY.md +72 -0
- package/examples/openspec-analysis/config.json +16 -0
- package/examples/openspec-analysis/dependencies.mermaid +35 -0
- package/examples/openspec-analysis/dependency-graph.json +12116 -0
- package/examples/openspec-analysis/llm-context.json +119 -0
- package/examples/openspec-analysis/repo-structure.json +871 -0
- package/examples/openspec-cli/README.md +67 -0
- package/examples/openspec-cli/openspec/config.yaml +26 -0
- package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
- package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
- package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
- package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
- package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
- package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
- package/examples/spec-kit/README.md +104 -0
- package/examples/spec-kit/commands/drift.md +87 -0
- package/examples/spec-kit/commands/orient.md +138 -0
- package/examples/spec-kit/extension.yml +54 -0
- package/package.json +3 -6
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Variable Extractor
|
|
3
|
+
*
|
|
4
|
+
* Detects env vars used in a project from two complementary sources:
|
|
5
|
+
* 1. Declaration files — .env.example, .env.local, .env (with optional comments)
|
|
6
|
+
* 2. Source code — process.env.X (JS/TS), os.environ["X"] (Python),
|
|
7
|
+
* os.Getenv("X") (Go), ENV["X"] (Ruby)
|
|
8
|
+
*
|
|
9
|
+
* Variables found in declaration files are marked hasDefault=true when the
|
|
10
|
+
* declaration line has a non-empty value. Variables found only in source code
|
|
11
|
+
* are marked required=true (no known default).
|
|
12
|
+
*/
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import { extname, relative, basename } from 'node:path';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// ENV FILE PARSER
|
|
17
|
+
// ============================================================================
|
|
18
|
+
function parseEnvFile(content, relPath) {
|
|
19
|
+
const vars = [];
|
|
20
|
+
let pendingComment = '';
|
|
21
|
+
for (const rawLine of content.split('\n')) {
|
|
22
|
+
const line = rawLine.trim();
|
|
23
|
+
// Accumulate comment lines as description
|
|
24
|
+
if (line.startsWith('#')) {
|
|
25
|
+
pendingComment = line.replace(/^#+\s*/, '');
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!line) {
|
|
29
|
+
pendingComment = '';
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const eqIdx = line.indexOf('=');
|
|
33
|
+
if (eqIdx === -1) {
|
|
34
|
+
pendingComment = '';
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const name = line.slice(0, eqIdx).trim();
|
|
38
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(name)) {
|
|
39
|
+
pendingComment = '';
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const rawValue = line.slice(eqIdx + 1).trim();
|
|
43
|
+
// Strip inline comment from value
|
|
44
|
+
const valueWithoutComment = rawValue.replace(/#.*$/, '').trim();
|
|
45
|
+
const inlineComment = rawValue.includes('#')
|
|
46
|
+
? rawValue.slice(rawValue.indexOf('#') + 1).trim()
|
|
47
|
+
: '';
|
|
48
|
+
const hasDefault = valueWithoutComment.length > 0;
|
|
49
|
+
const description = inlineComment || pendingComment || undefined;
|
|
50
|
+
vars.push({ name, files: [relPath], hasDefault, required: false, description });
|
|
51
|
+
pendingComment = '';
|
|
52
|
+
}
|
|
53
|
+
return vars;
|
|
54
|
+
}
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// SOURCE CODE SCANNERS
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// JS/TS: process.env.VAR_NAME or process.env['VAR_NAME'] or process.env["VAR_NAME"]
|
|
59
|
+
const TS_ENV_RE = /process\.env\.([A-Z_][A-Z0-9_]*)|process\.env\[['"]([A-Z_][A-Z0-9_]*)['"]]/g;
|
|
60
|
+
// Fallback detection: process.env.X ?? 'default' or process.env.X || 'default'
|
|
61
|
+
const TS_HAS_FALLBACK_RE = /process\.env\.(?:[A-Z_][A-Z0-9_]*|\[['"][A-Z_][A-Z0-9_]*['"]])\s*(?:\?\?|(?<!\|)\|\|)/;
|
|
62
|
+
// Python: os.environ["X"], os.environ['X'], os.environ.get("X"), os.getenv("X")
|
|
63
|
+
const PY_ENV_RE = /os\.environ\[['"]([A-Z_][A-Z0-9_]*)['"]|os\.environ\.get\(['"]([A-Z_][A-Z0-9_]*)['"]|os\.getenv\(['"]([A-Z_][A-Z0-9_]*)['"]/g;
|
|
64
|
+
// Go: os.Getenv("X")
|
|
65
|
+
const GO_ENV_RE = /os\.Getenv\("([A-Z_][A-Z0-9_]*)"\)/g;
|
|
66
|
+
// Ruby: ENV["X"], ENV['X'], ENV.fetch("X")
|
|
67
|
+
const RUBY_ENV_RE = /ENV\[['"]([A-Z_][A-Z0-9_]*)['"]|ENV\.fetch\(['"]([A-Z_][A-Z0-9_]*)['"]/g;
|
|
68
|
+
function extractFromSource(source, relPath, ext) {
|
|
69
|
+
const found = [];
|
|
70
|
+
let re;
|
|
71
|
+
let hasFallback = false;
|
|
72
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext)) {
|
|
73
|
+
re = new RegExp(TS_ENV_RE.source, 'g');
|
|
74
|
+
hasFallback = TS_HAS_FALLBACK_RE.test(source);
|
|
75
|
+
let m;
|
|
76
|
+
while ((m = re.exec(source)) !== null) {
|
|
77
|
+
const name = m[1] ?? m[2];
|
|
78
|
+
if (name)
|
|
79
|
+
found.push({ name, required: !hasFallback });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (['.py', '.pyw'].includes(ext)) {
|
|
83
|
+
re = new RegExp(PY_ENV_RE.source, 'g');
|
|
84
|
+
let m;
|
|
85
|
+
while ((m = re.exec(source)) !== null) {
|
|
86
|
+
const name = m[1] ?? m[2] ?? m[3];
|
|
87
|
+
// os.environ.get and os.getenv have optional defaults → not strictly required
|
|
88
|
+
const isStrict = m[1] !== undefined; // only os.environ["X"] is strict
|
|
89
|
+
if (name)
|
|
90
|
+
found.push({ name, required: isStrict });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (ext === '.go') {
|
|
94
|
+
re = new RegExp(GO_ENV_RE.source, 'g');
|
|
95
|
+
let m;
|
|
96
|
+
while ((m = re.exec(source)) !== null) {
|
|
97
|
+
if (m[1])
|
|
98
|
+
found.push({ name: m[1], required: false }); // Go always uses string return, caller checks
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (ext === '.rb') {
|
|
102
|
+
re = new RegExp(RUBY_ENV_RE.source, 'g');
|
|
103
|
+
let m;
|
|
104
|
+
while ((m = re.exec(source)) !== null) {
|
|
105
|
+
const name = m[1] ?? m[2];
|
|
106
|
+
const isStrict = m[1] !== undefined; // ENV.fetch has optional default
|
|
107
|
+
if (name)
|
|
108
|
+
found.push({ name, required: isStrict });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return found;
|
|
112
|
+
}
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// PUBLIC API
|
|
115
|
+
// ============================================================================
|
|
116
|
+
const ENV_DECLARATION_FILES = new Set(['.env', '.env.example', '.env.local', '.env.test', '.env.production']);
|
|
117
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.py', '.pyw', '.go', '.rb']);
|
|
118
|
+
const SKIP_DIRS = ['/node_modules/', '/.spec-gen/', '/dist/', '/build/', '/coverage/'];
|
|
119
|
+
/**
|
|
120
|
+
* Extract all environment variables referenced or declared in the project.
|
|
121
|
+
*/
|
|
122
|
+
export async function extractEnvVars(filePaths, rootDir) {
|
|
123
|
+
const map = new Map();
|
|
124
|
+
function upsert(name, relPath, patch) {
|
|
125
|
+
const existing = map.get(name);
|
|
126
|
+
if (existing) {
|
|
127
|
+
if (!existing.files.includes(relPath))
|
|
128
|
+
existing.files.push(relPath);
|
|
129
|
+
if (patch.hasDefault)
|
|
130
|
+
existing.hasDefault = true;
|
|
131
|
+
if (patch.required)
|
|
132
|
+
existing.required = true;
|
|
133
|
+
if (patch.description && !existing.description)
|
|
134
|
+
existing.description = patch.description;
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
map.set(name, {
|
|
138
|
+
name,
|
|
139
|
+
files: [relPath],
|
|
140
|
+
hasDefault: patch.hasDefault ?? false,
|
|
141
|
+
required: patch.required ?? false,
|
|
142
|
+
description: patch.description,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await Promise.all(filePaths.map(async (fp) => {
|
|
147
|
+
if (SKIP_DIRS.some(d => fp.replace(/\\/g, '/').includes(d)))
|
|
148
|
+
return;
|
|
149
|
+
const name = basename(fp);
|
|
150
|
+
const ext = extname(fp).toLowerCase();
|
|
151
|
+
const rel = relative(rootDir, fp);
|
|
152
|
+
let source;
|
|
153
|
+
try {
|
|
154
|
+
source = await readFile(fp, 'utf-8');
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Env declaration files
|
|
160
|
+
if (ENV_DECLARATION_FILES.has(name)) {
|
|
161
|
+
for (const v of parseEnvFile(source, rel)) {
|
|
162
|
+
upsert(v.name, rel, { hasDefault: v.hasDefault, description: v.description });
|
|
163
|
+
}
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Source files
|
|
167
|
+
if (!SOURCE_EXTENSIONS.has(ext))
|
|
168
|
+
return;
|
|
169
|
+
// Skip test files
|
|
170
|
+
if (fp.includes('.test.') || fp.includes('.spec.') || fp.includes('_test.') || fp.includes('_spec.'))
|
|
171
|
+
return;
|
|
172
|
+
for (const { name: varName, required } of extractFromSource(source, rel, ext)) {
|
|
173
|
+
upsert(varName, rel, { required });
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Return a compact summary string for LLM prompts.
|
|
180
|
+
*/
|
|
181
|
+
export function summarizeEnvVars(vars) {
|
|
182
|
+
if (vars.length === 0)
|
|
183
|
+
return '';
|
|
184
|
+
const lines = vars.map(v => {
|
|
185
|
+
const flags = [];
|
|
186
|
+
if (v.required)
|
|
187
|
+
flags.push('required');
|
|
188
|
+
if (v.hasDefault)
|
|
189
|
+
flags.push('has-default');
|
|
190
|
+
const suffix = flags.length > 0 ? ` [${flags.join(', ')}]` : '';
|
|
191
|
+
const desc = v.description ? ` — ${v.description}` : '';
|
|
192
|
+
return ` ${v.name}${suffix}${desc}`;
|
|
193
|
+
});
|
|
194
|
+
return `Environment variables (${vars.length}):\n${lines.join('\n')}`;
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=env-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-extractor.js","sourceRoot":"","sources":["../../../src/core/analyzer/env-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,10 +52,16 @@ export interface RouteDefinition {
|
|
|
52
52
|
normalizedPath: string;
|
|
53
53
|
/** Name of the handler function */
|
|
54
54
|
handlerName: string;
|
|
55
|
-
/** fastapi / flask / django / starlette */
|
|
55
|
+
/** fastapi / flask / django / starlette / express / nestjs / nextjs-app etc. */
|
|
56
56
|
framework: string;
|
|
57
57
|
/** 1-based source line */
|
|
58
58
|
line: number;
|
|
59
|
+
/** Request body type extracted from handler signature, e.g. "CreateUserDto" or "z.infer<typeof schema>" */
|
|
60
|
+
requestBodyType?: string;
|
|
61
|
+
/** Response body type extracted from handler return type annotation, e.g. "User[]" or "void" */
|
|
62
|
+
responseType?: string;
|
|
63
|
+
/** How the contract was sourced */
|
|
64
|
+
contractSource: 'annotation' | 'validator' | 'none';
|
|
59
65
|
}
|
|
60
66
|
/** A resolved cross-language edge */
|
|
61
67
|
export interface HttpEdge {
|
|
@@ -108,4 +114,33 @@ export declare function extractAllHttpEdges(filePaths: string[]): Promise<{
|
|
|
108
114
|
routes: RouteDefinition[];
|
|
109
115
|
edges: HttpEdge[];
|
|
110
116
|
}>;
|
|
117
|
+
/**
|
|
118
|
+
* Extract HTTP route definitions from a TypeScript/JavaScript server file.
|
|
119
|
+
* Handles Express-style, NestJS decorators, and Next.js App Router.
|
|
120
|
+
*/
|
|
121
|
+
export declare function extractTsRouteDefinitions(filePath: string): Promise<RouteDefinition[]>;
|
|
122
|
+
export interface RouteInventory {
|
|
123
|
+
total: number;
|
|
124
|
+
byMethod: Record<string, number>;
|
|
125
|
+
byFramework: Record<string, number>;
|
|
126
|
+
routes: Array<{
|
|
127
|
+
method: string;
|
|
128
|
+
path: string;
|
|
129
|
+
framework: string;
|
|
130
|
+
file: string;
|
|
131
|
+
handler: string;
|
|
132
|
+
requestBodyType?: string;
|
|
133
|
+
responseType?: string;
|
|
134
|
+
contractSource: 'annotation' | 'validator' | 'none';
|
|
135
|
+
}>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Build a complete route inventory from all source files.
|
|
139
|
+
* Combines Python routes (extractRouteDefinitions) and TS/JS routes
|
|
140
|
+
* (extractTsRouteDefinitions) into a single summary.
|
|
141
|
+
*
|
|
142
|
+
* @param filePaths - Absolute paths to all source files in the project
|
|
143
|
+
* @param rootDir - Project root for computing relative paths
|
|
144
|
+
*/
|
|
145
|
+
export declare function buildRouteInventory(filePaths: string[], rootDir: string): Promise<RouteInventory>;
|
|
111
146
|
//# sourceMappingURL=http-route-parser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-route-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/http-route-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;
|
|
1
|
+
{"version":3,"file":"http-route-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/http-route-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { readFile } from 'node:fs/promises';
|
|
29
29
|
import { extname } from 'node:path';
|
|
30
|
+
import { getSkeletonContent, detectLanguage } from './code-shaper.js';
|
|
30
31
|
// ============================================================================
|
|
31
32
|
// NORMALISATION HELPERS
|
|
32
33
|
// ============================================================================
|
|
@@ -236,6 +237,7 @@ export async function extractRouteDefinitions(filePath) {
|
|
|
236
237
|
handlerName,
|
|
237
238
|
framework: 'fastapi',
|
|
238
239
|
line: lineNum,
|
|
240
|
+
contractSource: 'none',
|
|
239
241
|
});
|
|
240
242
|
}
|
|
241
243
|
// @app.api_route("/path", methods=["GET", "POST"])
|
|
@@ -255,6 +257,7 @@ export async function extractRouteDefinitions(filePath) {
|
|
|
255
257
|
handlerName,
|
|
256
258
|
framework: 'fastapi',
|
|
257
259
|
line: lineNum,
|
|
260
|
+
contractSource: 'none',
|
|
258
261
|
});
|
|
259
262
|
}
|
|
260
263
|
}
|
|
@@ -278,6 +281,7 @@ export async function extractRouteDefinitions(filePath) {
|
|
|
278
281
|
handlerName,
|
|
279
282
|
framework: 'flask',
|
|
280
283
|
line: lineNum,
|
|
284
|
+
contractSource: 'none',
|
|
281
285
|
});
|
|
282
286
|
}
|
|
283
287
|
}
|
|
@@ -291,6 +295,7 @@ export async function extractRouteDefinitions(filePath) {
|
|
|
291
295
|
handlerName,
|
|
292
296
|
framework: 'flask',
|
|
293
297
|
line: lineNum,
|
|
298
|
+
contractSource: 'none',
|
|
294
299
|
});
|
|
295
300
|
}
|
|
296
301
|
}
|
|
@@ -317,6 +322,7 @@ export async function extractRouteDefinitions(filePath) {
|
|
|
317
322
|
handlerName,
|
|
318
323
|
framework: 'django',
|
|
319
324
|
line: lineNum,
|
|
325
|
+
contractSource: 'none',
|
|
320
326
|
});
|
|
321
327
|
}
|
|
322
328
|
return routes;
|
|
@@ -463,4 +469,274 @@ function extractNextDefName(lines, decoratorLine) {
|
|
|
463
469
|
}
|
|
464
470
|
return 'unknown';
|
|
465
471
|
}
|
|
472
|
+
// ============================================================================
|
|
473
|
+
// CONTRACT / TYPE EXTRACTION HELPERS
|
|
474
|
+
// ============================================================================
|
|
475
|
+
/**
|
|
476
|
+
* Extract contract information from a handler function body or surrounding context.
|
|
477
|
+
*
|
|
478
|
+
* Strategies:
|
|
479
|
+
* 1. TypeScript Request<P, ResBody, ReqBody, Q> generic → requestBodyType = ReqBody
|
|
480
|
+
* 2. NestJS @Body() dto: Type → requestBodyType = Type
|
|
481
|
+
* 3. Zod .parse( / .parseAsync( → contractSource = 'validator'
|
|
482
|
+
* 4. Promise<ResponseType> return annotation
|
|
483
|
+
*/
|
|
484
|
+
function extractContractFromHandler(handlerSource) {
|
|
485
|
+
let requestBodyType;
|
|
486
|
+
let responseType;
|
|
487
|
+
let contractSource = 'none';
|
|
488
|
+
// 1. TypeScript Request<P, ResBody, ReqBody, Q> generic
|
|
489
|
+
// handler(req: Request<Params, ResBody, Body, Query>)
|
|
490
|
+
const reqGenericRe = /:\s*Request\s*<[^,>]+,\s*([^,>]+),\s*([^,>]+)/;
|
|
491
|
+
const reqGenericMatch = reqGenericRe.exec(handlerSource);
|
|
492
|
+
if (reqGenericMatch) {
|
|
493
|
+
const resBodyType = reqGenericMatch[1].trim();
|
|
494
|
+
const reqBodyType = reqGenericMatch[2].trim();
|
|
495
|
+
if (reqBodyType && reqBodyType !== 'unknown' && reqBodyType !== 'any') {
|
|
496
|
+
requestBodyType = reqBodyType;
|
|
497
|
+
contractSource = 'annotation';
|
|
498
|
+
}
|
|
499
|
+
if (resBodyType && resBodyType !== 'unknown' && resBodyType !== 'any') {
|
|
500
|
+
responseType = resBodyType;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// 2. NestJS @Body() dto: CreateUserDto
|
|
504
|
+
const bodyParamRe = /@Body\s*\(\s*\)\s+\w+\s*:\s*(\w+)/;
|
|
505
|
+
const bodyParamMatch = bodyParamRe.exec(handlerSource);
|
|
506
|
+
if (bodyParamMatch) {
|
|
507
|
+
requestBodyType = bodyParamMatch[1];
|
|
508
|
+
contractSource = 'annotation';
|
|
509
|
+
}
|
|
510
|
+
// 3. Zod validators: schema.parse( / schema.parseAsync( / z.infer<typeof
|
|
511
|
+
const zodRe = /\b\w+\.parse(?:Async)?\s*\(|z\.infer\s*<\s*typeof\s+(\w+)/;
|
|
512
|
+
const zodMatch = zodRe.exec(handlerSource);
|
|
513
|
+
if (zodMatch) {
|
|
514
|
+
contractSource = 'validator';
|
|
515
|
+
if (zodMatch[1]) {
|
|
516
|
+
requestBodyType = `z.infer<typeof ${zodMatch[1]}>`;
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
// Extract schema variable name from .parse( call
|
|
520
|
+
const parseVarRe = /(\w+)\.parse(?:Async)?\s*\(/;
|
|
521
|
+
const parseVarMatch = parseVarRe.exec(handlerSource);
|
|
522
|
+
if (parseVarMatch) {
|
|
523
|
+
requestBodyType = parseVarMatch[1];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// 4. Promise<ResponseType> return type annotation
|
|
528
|
+
const returnTypeRe = /\):\s*Promise\s*<\s*([^>]+)>/;
|
|
529
|
+
const returnTypeMatch = returnTypeRe.exec(handlerSource);
|
|
530
|
+
if (returnTypeMatch && !responseType) {
|
|
531
|
+
const rType = returnTypeMatch[1].trim();
|
|
532
|
+
if (rType && rType !== 'void' && rType !== 'unknown' && rType !== 'any') {
|
|
533
|
+
responseType = rType;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return { requestBodyType, responseType, contractSource };
|
|
537
|
+
}
|
|
538
|
+
// ============================================================================
|
|
539
|
+
// TS/JS SERVER ROUTE EXTRACTION
|
|
540
|
+
// ============================================================================
|
|
541
|
+
// Express / Hono / Fastify / Koa / Elysia style:
|
|
542
|
+
// app.get('/path', handler)
|
|
543
|
+
// router.post('/path', ...)
|
|
544
|
+
// app.use('/prefix', router) ← prefix accumulation
|
|
545
|
+
const EXPRESS_ROUTE_RE = /(?:^|[\s;(,])(?:app|router|server|api|r)\.(get|post|put|delete|patch|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gm;
|
|
546
|
+
const EXPRESS_USE_RE = /(?:^|[\s;(,])(?:app|router|server|api|r)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gm;
|
|
547
|
+
// NestJS decorator-based:
|
|
548
|
+
// @Controller('prefix') → class methods with @Get / @Post etc.
|
|
549
|
+
const NESTJS_CONTROLLER_RE = /@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g;
|
|
550
|
+
const NESTJS_METHOD_RE = /@(Get|Post|Put|Delete|Patch|Head|Options|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g;
|
|
551
|
+
const NESTJS_HANDLER_RE = /(?:async\s+)?(\w+)\s*\(/;
|
|
552
|
+
// Next.js App Router: export (async) function GET(...) in app/**/route.ts
|
|
553
|
+
const NEXTJS_APP_ROUTER_RE = /^export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/gm;
|
|
554
|
+
/** Detected framework from a file's content */
|
|
555
|
+
function detectTsFramework(source, filePath) {
|
|
556
|
+
if (/@Controller\s*\(/.test(source) && /@(Get|Post|Put|Delete|Patch)\s*\(/.test(source))
|
|
557
|
+
return 'nestjs';
|
|
558
|
+
if (/app\/.*\/route\.[jt]sx?$/.test(filePath.replace(/\\/g, '/')))
|
|
559
|
+
return 'nextjs-app';
|
|
560
|
+
if (/pages\/api\//.test(filePath.replace(/\\/g, '/')))
|
|
561
|
+
return 'nextjs-pages';
|
|
562
|
+
if (/from\s+['"]hono['"]/.test(source) || /new\s+Hono\s*[(<]/.test(source))
|
|
563
|
+
return 'hono';
|
|
564
|
+
if (/from\s+['"]fastify['"]/.test(source) || /fastify\s*\(/.test(source))
|
|
565
|
+
return 'fastify';
|
|
566
|
+
if (/from\s+['"]express['"]/.test(source) || /require\s*\(\s*['"]express['"]\s*\)/.test(source))
|
|
567
|
+
return 'express';
|
|
568
|
+
if (/from\s+['"]koa['"]/.test(source))
|
|
569
|
+
return 'koa';
|
|
570
|
+
if (/from\s+['"]elysia['"]/.test(source))
|
|
571
|
+
return 'elysia';
|
|
572
|
+
if (new RegExp(EXPRESS_ROUTE_RE.source).test(source))
|
|
573
|
+
return 'express';
|
|
574
|
+
return 'unknown';
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Extract HTTP route definitions from a TypeScript/JavaScript server file.
|
|
578
|
+
* Handles Express-style, NestJS decorators, and Next.js App Router.
|
|
579
|
+
*/
|
|
580
|
+
export async function extractTsRouteDefinitions(filePath) {
|
|
581
|
+
let source;
|
|
582
|
+
try {
|
|
583
|
+
const { readFile } = await import('node:fs/promises');
|
|
584
|
+
// Use skeleton to strip comments — prevents false positives from comment
|
|
585
|
+
// examples inside parser/extractor files that contain route pattern strings.
|
|
586
|
+
// Line numbers in the result are approximate (skeleton line positions).
|
|
587
|
+
source = getSkeletonContent(await readFile(filePath, 'utf-8'), detectLanguage(filePath));
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
592
|
+
const framework = detectTsFramework(source, filePath);
|
|
593
|
+
const routes = [];
|
|
594
|
+
const lines = source.split('\n');
|
|
595
|
+
function lineOf(index) {
|
|
596
|
+
return source.slice(0, index).split('\n').length;
|
|
597
|
+
}
|
|
598
|
+
// ── Next.js App Router ────────────────────────────────────────────────────
|
|
599
|
+
if (framework === 'nextjs-app') {
|
|
600
|
+
// Derive path from file location: app/users/route.ts → /users
|
|
601
|
+
const rel = filePath.replace(/\\/g, '/');
|
|
602
|
+
const appIdx = rel.lastIndexOf('/app/');
|
|
603
|
+
let routePath = '/';
|
|
604
|
+
if (appIdx >= 0) {
|
|
605
|
+
routePath = rel.slice(appIdx + 4).replace(/\/route\.[jt]sx?$/, '') || '/';
|
|
606
|
+
// Remove dynamic segments brackets for display: [id] → :id
|
|
607
|
+
routePath = routePath.replace(/\[([^\]]+)\]/g, ':$1');
|
|
608
|
+
}
|
|
609
|
+
const re = new RegExp(NEXTJS_APP_ROUTER_RE.source, NEXTJS_APP_ROUTER_RE.flags);
|
|
610
|
+
let m;
|
|
611
|
+
while ((m = re.exec(source)) !== null) {
|
|
612
|
+
// Extract handler body for contract detection (scan next 500 chars)
|
|
613
|
+
const handlerBody = source.slice(m.index, m.index + 500);
|
|
614
|
+
const contract = extractContractFromHandler(handlerBody);
|
|
615
|
+
routes.push({
|
|
616
|
+
file: filePath,
|
|
617
|
+
method: m[1].toUpperCase(),
|
|
618
|
+
path: routePath,
|
|
619
|
+
normalizedPath: normalizeUrl(routePath),
|
|
620
|
+
handlerName: m[1],
|
|
621
|
+
framework: 'nextjs-app',
|
|
622
|
+
line: lineOf(m.index),
|
|
623
|
+
...contract,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
return routes;
|
|
627
|
+
}
|
|
628
|
+
// ── NestJS ────────────────────────────────────────────────────────────────
|
|
629
|
+
if (framework === 'nestjs') {
|
|
630
|
+
// Collect controller prefixes
|
|
631
|
+
const ctrlRe = new RegExp(NESTJS_CONTROLLER_RE.source, NESTJS_CONTROLLER_RE.flags);
|
|
632
|
+
let ctrlPrefix = '';
|
|
633
|
+
const ctrlMatch = ctrlRe.exec(source);
|
|
634
|
+
if (ctrlMatch) {
|
|
635
|
+
ctrlPrefix = ctrlMatch[1] ? `/${ctrlMatch[1].replace(/^\//, '')}` : '';
|
|
636
|
+
}
|
|
637
|
+
const methodRe = new RegExp(NESTJS_METHOD_RE.source, NESTJS_METHOD_RE.flags);
|
|
638
|
+
let m;
|
|
639
|
+
while ((m = methodRe.exec(source)) !== null) {
|
|
640
|
+
const httpMethod = m[1].toUpperCase();
|
|
641
|
+
const subPath = m[2] ? `/${m[2].replace(/^\//, '')}` : '';
|
|
642
|
+
const fullPath = `${ctrlPrefix}${subPath}` || '/';
|
|
643
|
+
// Find handler function name on subsequent lines
|
|
644
|
+
const afterDecorator = source.slice(m.index + m[0].length);
|
|
645
|
+
const handlerMatch = NESTJS_HANDLER_RE.exec(afterDecorator.slice(0, 200));
|
|
646
|
+
const handlerName = handlerMatch?.[1] ?? 'unknown';
|
|
647
|
+
// Extract contract from decorator + handler context (scan next 400 chars)
|
|
648
|
+
const handlerContext = source.slice(m.index, m.index + 400);
|
|
649
|
+
const contract = extractContractFromHandler(handlerContext);
|
|
650
|
+
routes.push({
|
|
651
|
+
file: filePath,
|
|
652
|
+
method: httpMethod,
|
|
653
|
+
path: fullPath,
|
|
654
|
+
normalizedPath: normalizeUrl(fullPath),
|
|
655
|
+
handlerName,
|
|
656
|
+
framework: 'nestjs',
|
|
657
|
+
line: lineOf(m.index),
|
|
658
|
+
...contract,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
return routes;
|
|
662
|
+
}
|
|
663
|
+
// ── Express / Hono / Fastify / Koa / Elysia ───────────────────────────────
|
|
664
|
+
// Collect prefix map from .use() calls (best-effort)
|
|
665
|
+
const prefixes = [];
|
|
666
|
+
const useRe = new RegExp(EXPRESS_USE_RE.source, EXPRESS_USE_RE.flags);
|
|
667
|
+
let um;
|
|
668
|
+
while ((um = useRe.exec(source)) !== null) {
|
|
669
|
+
prefixes.push(um[1]);
|
|
670
|
+
}
|
|
671
|
+
const routeRe = new RegExp(EXPRESS_ROUTE_RE.source, EXPRESS_ROUTE_RE.flags);
|
|
672
|
+
let m;
|
|
673
|
+
while ((m = routeRe.exec(source)) !== null) {
|
|
674
|
+
const method = m[1].toUpperCase();
|
|
675
|
+
let path = m[2];
|
|
676
|
+
// Apply a prefix if the route is relative (no leading slash)
|
|
677
|
+
if (!path.startsWith('/') && prefixes.length > 0) {
|
|
678
|
+
path = `${prefixes[0]}/${path}`;
|
|
679
|
+
}
|
|
680
|
+
// Find the handler name from the same line
|
|
681
|
+
const lineText = lines[lineOf(m.index) - 1] ?? '';
|
|
682
|
+
const handlerMatch = lineText.match(/,\s*(?:async\s+)?(?:function\s+)?(\w+)\s*[,)]/);
|
|
683
|
+
const handlerName = handlerMatch?.[1] ?? 'handler';
|
|
684
|
+
// Extract contract from route context (scan next 600 chars)
|
|
685
|
+
const routeContext = source.slice(m.index, m.index + 600);
|
|
686
|
+
const contract = extractContractFromHandler(routeContext);
|
|
687
|
+
routes.push({
|
|
688
|
+
file: filePath,
|
|
689
|
+
method,
|
|
690
|
+
path,
|
|
691
|
+
normalizedPath: normalizeUrl(path),
|
|
692
|
+
handlerName,
|
|
693
|
+
framework,
|
|
694
|
+
line: lineOf(m.index),
|
|
695
|
+
...contract,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
return routes;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Build a complete route inventory from all source files.
|
|
702
|
+
* Combines Python routes (extractRouteDefinitions) and TS/JS routes
|
|
703
|
+
* (extractTsRouteDefinitions) into a single summary.
|
|
704
|
+
*
|
|
705
|
+
* @param filePaths - Absolute paths to all source files in the project
|
|
706
|
+
* @param rootDir - Project root for computing relative paths
|
|
707
|
+
*/
|
|
708
|
+
export async function buildRouteInventory(filePaths, rootDir) {
|
|
709
|
+
const { relative } = await import('node:path');
|
|
710
|
+
const allRoutes = [];
|
|
711
|
+
await Promise.all(filePaths.map(async (fp) => {
|
|
712
|
+
const ext = extname(fp).toLowerCase();
|
|
713
|
+
if (['.py', '.pyw'].includes(ext)) {
|
|
714
|
+
allRoutes.push(...await extractRouteDefinitions(fp));
|
|
715
|
+
}
|
|
716
|
+
else if (['.ts', '.tsx', '.js', '.jsx', '.mjs'].includes(ext)) {
|
|
717
|
+
allRoutes.push(...await extractTsRouteDefinitions(fp));
|
|
718
|
+
}
|
|
719
|
+
}));
|
|
720
|
+
const byMethod = {};
|
|
721
|
+
const byFramework = {};
|
|
722
|
+
for (const r of allRoutes) {
|
|
723
|
+
byMethod[r.method] = (byMethod[r.method] ?? 0) + 1;
|
|
724
|
+
byFramework[r.framework] = (byFramework[r.framework] ?? 0) + 1;
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
total: allRoutes.length,
|
|
728
|
+
byMethod,
|
|
729
|
+
byFramework,
|
|
730
|
+
routes: allRoutes.map(r => ({
|
|
731
|
+
method: r.method,
|
|
732
|
+
path: r.path,
|
|
733
|
+
framework: r.framework,
|
|
734
|
+
file: relative(rootDir, r.file),
|
|
735
|
+
handler: r.handlerName,
|
|
736
|
+
requestBodyType: r.requestBodyType,
|
|
737
|
+
responseType: r.responseType,
|
|
738
|
+
contractSource: r.contractSource,
|
|
739
|
+
})),
|
|
740
|
+
};
|
|
741
|
+
}
|
|
466
742
|
//# sourceMappingURL=http-route-parser.js.map
|