Add framework detector and setup generator
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-29 13:22:23 +00:00
parent 6de9de9094
commit 45147f856f

View File

@@ -0,0 +1,231 @@
"""Framework detector by analyzing dependencies and file patterns."""
from pathlib import Path
from typing import Any
from contextgen.analyzers.base import BaseAnalyzer
FRAMEWORK_INDICATORS: dict[str, dict[str, list[str]]] = {
"Django": {
"files": ["manage.py", "django-admin.py", "urls.py", "settings.py", "wsgi.py", "asgi.py"],
"dependencies": ["django", "djangorestframework", "django-filter"],
"imports": ["from django", "import django"],
},
"Flask": {
"files": ["app.py", "wsgi.py"],
"dependencies": ["flask"],
"imports": ["from flask", "import flask"],
},
"FastAPI": {
"files": ["main.py", "app.py"],
"dependencies": ["fastapi", "uvicorn"],
"imports": ["from fastapi", "import fastapi"],
},
"React": {
"files": ["package.json", "tsconfig.json", "webpack.config.js"],
"dependencies": ["react", "react-dom", "react-scripts"],
"imports": ["from 'react'", 'from "react"', "import React"],
},
"Vue": {
"files": ["package.json", "vue.config.js", "vue.config.ts"],
"dependencies": ["vue", "@vue/cli", "vue-loader"],
"imports": ["from 'vue'", 'from "vue"', "import Vue"],
},
"Next.js": {
"files": ["next.config.js", "next.config.ts", "pages/", "app/"],
"dependencies": ["next", "react", "react-dom"],
"imports": ["from 'next'", 'from "next"', "next/router"],
},
"Express": {
"files": ["app.js", "server.js", "index.js"],
"dependencies": ["express"],
"imports": ["from 'express'", 'from "express"', "import express"],
},
"Spring": {
"files": ["pom.xml", "build.gradle", "application.properties", "application.yml"],
"dependencies": ["spring-boot", "springframework"],
"imports": ["import org.springframework", "@SpringBootApplication"],
},
"Gin": {
"files": ["main.go", "go.mod"],
"dependencies": ["github.com/gin-gonic/gin"],
"imports": ['"github.com/gin-gonic/gin"'],
},
"Rails": {
"files": ["Gemfile", "config.ru", "app/controllers/", "config/routes.rb"],
"dependencies": ["rails"],
"imports": ["class ApplicationController < ActionController"],
},
"Laravel": {
"files": ["composer.json", "artisan", "config/app.php"],
"dependencies": ["laravel/framework"],
"imports": ["use Illuminate", "Route::"],
},
"Angular": {
"files": ["angular.json", "package.json", "tsconfig.json"],
"dependencies": ["@angular/core", "@angular/cli"],
"imports": ["@Component", "@Injectable", "@angular/core"],
},
"Svelte": {
"files": ["svelte.config.js", "package.json"],
"dependencies": ["svelte"],
"imports": ["from 'svelte'", 'from "svelte"', "import Svelte"],
},
"Phoenix": {
"files": ["mix.exs", "config/config.exs"],
"dependencies": ["phoenix"],
"imports": ["import Phoenix", "use Phoenix"],
},
"NestJS": {
"files": ["nest-cli.json", "tsconfig.json"],
"dependencies": ["@nestjs/core", "@nestjs/common"],
"imports": ["@nestjs/common", "@Controller", "@Injectable"],
},
}
BUILD_TOOLS: dict[str, dict[str, list[str]]] = {
"npm": {
"files": ["package.json", "package-lock.json", "npm-shrinkwrap.json"],
"commands": ["npm install", "npm run", "npm build"],
},
"yarn": {
"files": ["yarn.lock", "package.json"],
"commands": ["yarn install", "yarn run", "yarn build"],
},
"pnpm": {
"files": ["pnpm-lock.yaml", "package.json"],
"commands": ["pnpm install", "pnpm run", "pnpm build"],
},
"pip": {
"files": ["requirements.txt", "Pipfile", "pyproject.toml", "setup.py"],
"commands": ["pip install", "pipenv install"],
},
"poetry": {
"files": ["pyproject.toml", "poetry.lock"],
"commands": ["poetry install", "poetry run"],
},
"cargo": {
"files": ["Cargo.toml", "Cargo.lock"],
"commands": ["cargo build", "cargo run", "cargo test"],
},
"maven": {
"files": ["pom.xml"],
"commands": ["mvn install", "mvn compile", "mvn test"],
},
"gradle": {
"files": ["build.gradle", "build.gradle.kts", "gradlew"],
"commands": ["./gradlew build", "gradle build", "./gradlew test"],
},
"go_modules": {
"files": ["go.mod", "go.sum"],
"commands": ["go mod download", "go build", "go run"],
},
"bundler": {
"files": ["Gemfile", "Gemfile.lock"],
"commands": ["bundle install", "bundle exec"],
},
"composer": {
"files": ["composer.json", "composer.lock"],
"commands": ["composer install", "composer update"],
},
}
class FrameworkDetector(BaseAnalyzer):
"""Detects frameworks and build tools used in the project."""
def analyze(self) -> dict[str, Any]:
"""Detect frameworks and build tools."""
frameworks = self._detect_frameworks()
build_tools = self._detect_build_tools()
return {
"frameworks": frameworks,
"build_tools": build_tools,
"full_stack": self._detect_full_stack(frameworks),
}
def _detect_frameworks(self) -> list[dict[str, Any]]:
"""Detect frameworks from file patterns and dependencies."""
detected: list[dict[str, Any]] = []
for framework, indicators in FRAMEWORK_INDICATORS.items():
score = 0
for filename in indicators["files"]:
if (self.project_path / filename).exists():
score += 2
if self._has_dependency(indicators["dependencies"]):
score += 3
if score >= 3:
detected.append({
"name": framework,
"confidence": min(score / 5, 1.0),
"indicators_found": score,
})
detected.sort(key=lambda x: x["confidence"], reverse=True)
return detected
def _detect_build_tools(self) -> list[dict[str, Any]]:
"""Detect build tools from file patterns."""
detected: list[dict[str, Any]] = []
for tool, indicators in BUILD_TOOLS.items():
score = 0
for filename in indicators["files"]:
if (self.project_path / filename).exists():
score += 2
if score >= 2:
detected.append({
"name": tool,
"confidence": min(score / 3, 1.0),
})
detected.sort(key=lambda x: x["confidence"], reverse=True)
return detected
def _has_dependency(self, dependencies: list[str]) -> bool:
"""Check if any dependency is found in project files."""
config_files = [
self.project_path / "package.json",
self.project_path / "pyproject.toml",
self.project_path / "requirements.txt",
self.project_path / "Cargo.toml",
self.project_path / "pom.xml",
self.project_path / "build.gradle",
self.project_path / "go.mod",
self.project_path / "Gemfile",
self.project_path / "composer.json",
]
for config_file in config_files:
if config_file.exists():
content = self._safe_read_file(config_file)
if content:
for dep in dependencies:
if dep in content:
return True
return False
def _detect_full_stack(self, frameworks: list[dict[str, Any]]) -> dict[str, str | None]:
"""Detect full stack technologies."""
frontend = None
backend = None
frontend_frameworks = ["React", "Vue", "Next.js", "Angular", "Svelte"]
backend_frameworks = ["Django", "Flask", "FastAPI", "Express", "Spring", "Gin", "Rails", "Laravel", "Phoenix", "NestJS"]
for fw in frameworks:
name = fw["name"]
if name in frontend_frameworks:
frontend = name
elif name in backend_frameworks:
backend = name
return {"frontend": frontend, "backend": backend}