diff --git a/apps/backend/project/command_registry/frameworks.py b/apps/backend/project/command_registry/frameworks.py index 2a9c09e7e..f811f1f84 100644 --- a/apps/backend/project/command_registry/frameworks.py +++ b/apps/backend/project/command_registry/frameworks.py @@ -163,6 +163,22 @@ "dart", "pub", }, # Dart HTTP framework (deprecated but still used) + # JVM frameworks + "spring-boot": {"mvn", "gradle", "gradlew"}, + "quarkus": {"quarkus", "mvn", "gradle"}, + "micronaut": {"mn", "mvn", "gradle"}, + "android": {"gradlew", "adb"}, + "compose": {"gradle", "gradlew"}, # Jetpack/Multiplatform Compose + # .NET frameworks + "aspnet": {"dotnet"}, + "maui": {"dotnet"}, + "unity": {"dotnet", "msbuild"}, + # C/C++ frameworks + "qt": {"qmake", "moc", "uic", "rcc"}, + "gtest": {"ctest"}, + "catch2": {"ctest"}, + # Swift frameworks + "vapor": {"vapor", "swift"}, } diff --git a/apps/backend/project/framework_detector.py b/apps/backend/project/framework_detector.py index 0d4ab3532..f0b1a393f 100644 --- a/apps/backend/project/framework_detector.py +++ b/apps/backend/project/framework_detector.py @@ -41,6 +41,11 @@ def detect_all(self) -> list[str]: self.detect_dart_frameworks() self.detect_go_frameworks() self.detect_rust_frameworks() + self.detect_jvm_frameworks() + self.detect_dotnet_frameworks() + self.detect_cpp_frameworks() + self.detect_elixir_frameworks() + self.detect_swift_frameworks() return self.frameworks def detect_nodejs_frameworks(self) -> None: @@ -307,3 +312,97 @@ def detect_rust_frameworks(self) -> None: for dep, framework in rust_framework_deps.items(): if dep in content: self.frameworks.append(framework) + + def detect_jvm_frameworks(self) -> None: + """Detect JVM frameworks from Maven/Gradle build files.""" + content = "" + for build_file in ( + "pom.xml", + "build.gradle", + "build.gradle.kts", + "settings.gradle", + "settings.gradle.kts", + ): + file_content = self.parser.read_text(build_file) + if file_content: + content += file_content + + if not content: + return + + jvm_framework_deps = { + "spring-boot": "spring-boot", + "org.springframework": "spring-boot", + "io.quarkus": "quarkus", + "io.micronaut": "micronaut", + "com.android.application": "android", + "com.android.library": "android", + "org.jetbrains.compose": "compose", + } + + for dep, framework in jvm_framework_deps.items(): + if dep in content and framework not in self.frameworks: + self.frameworks.append(framework) + + def detect_dotnet_frameworks(self) -> None: + """Detect .NET frameworks from project files.""" + csproj_files = self.parser.glob_files("*.csproj") + self.parser.glob_files( + "*/*.csproj" + ) + content = "" + for csproj in csproj_files: + try: + with open(csproj, encoding="utf-8") as f: + content += f.read() + except (OSError, UnicodeDecodeError): + continue + + if content: + if "Microsoft.AspNetCore" in content or "Microsoft.NET.Sdk.Web" in content: + self.frameworks.append("aspnet") + if "Microsoft.Maui" in content or "UseMaui" in content: + self.frameworks.append("maui") + + # Unity projects carry a ProjectVersion.txt under ProjectSettings/ + if self.parser.file_exists("ProjectSettings/ProjectVersion.txt"): + self.frameworks.append("unity") + + def detect_cpp_frameworks(self) -> None: + """Detect C/C++ frameworks and test libraries from CMake files.""" + content = self.parser.read_text("CMakeLists.txt") + if not content: + return + + cpp_framework_markers = { + "Qt5": "qt", + "Qt6": "qt", + "find_package(Qt": "qt", + "Boost": "boost", + "GTest": "gtest", + "googletest": "gtest", + "Catch2": "catch2", + } + + for marker, framework in cpp_framework_markers.items(): + if marker in content and framework not in self.frameworks: + self.frameworks.append(framework) + + def detect_elixir_frameworks(self) -> None: + """Detect Elixir frameworks from mix.exs.""" + content = self.parser.read_text("mix.exs") + if not content: + return + + if ":phoenix" in content: + self.frameworks.append("phoenix") + if ":ecto" in content and "ecto" not in self.frameworks: + self.frameworks.append("ecto") + + def detect_swift_frameworks(self) -> None: + """Detect Swift frameworks from Package.swift.""" + content = self.parser.read_text("Package.swift") + if not content: + return + + if "vapor" in content.lower(): + self.frameworks.append("vapor") diff --git a/tests/test_project_analyzer.py b/tests/test_project_analyzer.py index e270601da..a6fce4b5e 100644 --- a/tests/test_project_analyzer.py +++ b/tests/test_project_analyzer.py @@ -317,6 +317,99 @@ def test_detects_pytest(self, temp_dir: Path): assert "pytest" in analyzer.profile.detected_stack.frameworks + def test_detects_spring_boot_from_pom(self, temp_dir: Path): + """Detects Spring Boot from pom.xml.""" + (temp_dir / "pom.xml").write_text( + "org.springframework.boot" + "spring-boot-starter-parent" + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "spring-boot" in analyzer.profile.detected_stack.frameworks + + def test_detects_android_from_gradle_kts(self, temp_dir: Path): + """Detects Android from Gradle Kotlin-DSL plugin.""" + (temp_dir / "build.gradle.kts").write_text( + 'plugins { id("com.android.application") }' + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "android" in analyzer.profile.detected_stack.frameworks + + def test_detects_quarkus_from_gradle(self, temp_dir: Path): + """Detects Quarkus from build.gradle.""" + (temp_dir / "build.gradle").write_text( + "plugins { id 'io.quarkus' version '3.0.0' }" + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "quarkus" in analyzer.profile.detected_stack.frameworks + + def test_detects_aspnet_from_csproj(self, temp_dir: Path): + """Detects ASP.NET Core from csproj SDK.""" + (temp_dir / "App.csproj").write_text( + '' + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "aspnet" in analyzer.profile.detected_stack.frameworks + + def test_detects_unity_from_project_version(self, temp_dir: Path): + """Detects Unity from ProjectSettings/ProjectVersion.txt.""" + settings = temp_dir / "ProjectSettings" + settings.mkdir() + (settings / "ProjectVersion.txt").write_text("m_EditorVersion: 2022.3.0f1") + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "unity" in analyzer.profile.detected_stack.frameworks + + def test_detects_qt_and_gtest_from_cmake(self, temp_dir: Path): + """Detects Qt and GTest from CMakeLists.txt.""" + (temp_dir / "CMakeLists.txt").write_text( + "project(demo)\n" + "find_package(Qt6 REQUIRED COMPONENTS Widgets)\n" + "find_package(GTest REQUIRED)\n" + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "qt" in analyzer.profile.detected_stack.frameworks + assert "gtest" in analyzer.profile.detected_stack.frameworks + + def test_detects_phoenix_from_mix(self, temp_dir: Path): + """Detects Phoenix from mix.exs deps.""" + (temp_dir / "mix.exs").write_text( + 'defp deps do [{:phoenix, "~> 1.7"}, {:ecto, "~> 3.10"}] end' + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "phoenix" in analyzer.profile.detected_stack.frameworks + assert "ecto" in analyzer.profile.detected_stack.frameworks + + def test_detects_vapor_from_package_swift(self, temp_dir: Path): + """Detects Vapor from Package.swift.""" + (temp_dir / "Package.swift").write_text( + '.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0")' + ) + + analyzer = ProjectAnalyzer(temp_dir) + analyzer._detect_frameworks() + + assert "vapor" in analyzer.profile.detected_stack.frameworks + class TestDatabaseDetection: """Tests for database detection."""