From 67210c8d788511564ebd4182bc31ae00e5f5e784 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Sun, 1 Feb 2026 01:43:57 +0000 Subject: [PATCH] fix: resolve CI test failures --- .../src/analyzers/projectTypeDetector.ts | 326 ++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 .ai-context-generator-cli/src/analyzers/projectTypeDetector.ts diff --git a/.ai-context-generator-cli/src/analyzers/projectTypeDetector.ts b/.ai-context-generator-cli/src/analyzers/projectTypeDetector.ts new file mode 100644 index 0000000..896b8b0 --- /dev/null +++ b/.ai-context-generator-cli/src/analyzers/projectTypeDetector.ts @@ -0,0 +1,326 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { ProjectType } from '../types'; + +interface FrameworkPattern { + name: string; + indicators: string[]; + language: string; +} + +interface LanguagePattern { + extensions: string[]; + configFiles: string[]; + language: string; +} + +export class ProjectTypeDetector { + private languagePatterns: LanguagePattern[] = [ + { + extensions: ['.ts', '.tsx'], + language: 'TypeScript', + configFiles: ['tsconfig.json'], + }, + { + extensions: ['.js', '.jsx'], + language: 'JavaScript', + configFiles: ['package.json', 'jsconfig.json'], + }, + { + extensions: ['.py'], + language: 'Python', + configFiles: ['pyproject.toml', 'setup.py', 'requirements.txt'], + }, + { + extensions: ['.go'], + language: 'Go', + configFiles: ['go.mod', 'go.sum'], + }, + { + extensions: ['.rs'], + language: 'Rust', + configFiles: ['Cargo.toml', 'Cargo.lock'], + }, + { + extensions: ['.java'], + language: 'Java', + configFiles: ['pom.xml', 'build.gradle', 'build.gradle.kts'], + }, + { + extensions: ['.c', '.h'], + language: 'C', + configFiles: ['CMakeLists.txt', 'Makefile'], + }, + { + extensions: ['.cpp', '.cc', '.cxx', '.hpp'], + language: 'C++', + configFiles: ['CMakeLists.txt', 'Makefile'], + }, + { + extensions: ['.cs'], + language: 'C#', + configFiles: ['*.csproj', '*.sln'], + }, + { + extensions: ['.rb'], + language: 'Ruby', + configFiles: ['Gemfile', 'Rakefile'], + }, + { + extensions: ['.php'], + language: 'PHP', + configFiles: ['composer.json', 'phpunit.xml'], + }, + { + extensions: ['.swift'], + language: 'Swift', + configFiles: ['Package.swift', '*.xcodeproj'], + }, + ]; + + private frameworkPatterns: FrameworkPattern[] = [ + { + name: 'React', + indicators: ['react', 'react-dom', 'react-scripts'], + language: 'TypeScript', + }, + { + name: 'Vue', + indicators: ['vue', 'vue-loader', '@vue/'], + language: 'TypeScript', + }, + { + name: 'Express', + indicators: ['express'], + language: 'JavaScript', + }, + { + name: 'Next.js', + indicators: ['next'], + language: 'TypeScript', + }, + { + name: 'NestJS', + indicators: ['@nestjs/'], + language: 'TypeScript', + }, + { + name: 'Django', + indicators: ['django'], + language: 'Python', + }, + { + name: 'FastAPI', + indicators: ['fastapi', 'uvicorn'], + language: 'Python', + }, + { + name: 'Flask', + indicators: ['flask'], + language: 'Python', + }, + { + name: 'Gin', + indicators: ['gin-gonic/gin'], + language: 'Go', + }, + { + name: 'Echo', + indicators: ['labstack/echo'], + language: 'Go', + }, + { + name: 'Actix-web', + indicators: ['actix-web'], + language: 'Rust', + }, + { + name: 'Rocket', + indicators: ['rocket'], + language: 'Rust', + }, + { + name: 'Spring Boot', + indicators: ['spring-boot'], + language: 'Java', + }, + ]; + + async detect(dir: string): Promise { + const languages = await this.detectLanguages(dir); + const primaryLanguage = await this.determinePrimaryLanguage(languages); + const frameworks = await this.detectFrameworks(dir, primaryLanguage); + const buildTools = await this.detectBuildTools(dir, languages); + + return { + primaryLanguage, + languages, + frameworks, + buildTools, + }; + } + + private async determinePrimaryLanguage( + languages: string[] + ): Promise { + if (languages.length === 0) return 'Unknown'; + return languages[0]; + } + + private async detectLanguages(dir: string): Promise { + const detectedLanguages: string[] = []; + const contents = await fs.promises.readdir(dir, { withFileTypes: true }); + + const fileExtensions = new Set(); + for (const item of contents) { + if (item.isFile()) { + const ext = path.extname(item.name).toLowerCase(); + fileExtensions.add(ext); + } else if (item.isDirectory()) { + const subDirContents = await fs.promises.readdir( + path.join(dir, item.name), + { withFileTypes: true } + ); + for (const subItem of subDirContents) { + if (subItem.isFile()) { + const ext = path.extname(subItem.name).toLowerCase(); + fileExtensions.add(ext); + } + } + } + } + + for (const pattern of this.languagePatterns) { + for (const ext of pattern.extensions) { + if (fileExtensions.has(ext)) { + if (!detectedLanguages.includes(pattern.language)) { + detectedLanguages.push(pattern.language); + } + break; + } + } + } + + for (const pattern of this.languagePatterns) { + for (const configFile of pattern.configFiles) { + const fullPath = path.join(dir, configFile); + if (await this.fileExists(fullPath)) { + if (!detectedLanguages.includes(pattern.language)) { + detectedLanguages.push(pattern.language); + } + break; + } + } + } + + return detectedLanguages; + } + + private async detectFrameworks( + dir: string, + primaryLanguage: string + ): Promise { + const detectedFrameworks: string[] = []; + + for (const item of this.frameworkPatterns) { + if (item.language !== primaryLanguage) continue; + + const packageJsonPath = path.join(dir, 'package.json'); + if (await this.fileExists(packageJsonPath)) { + const content = await fs.promises.readFile(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(content); + const allDeps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }; + + for (const indicator of item.indicators) { + if (Object.keys(allDeps).some(dep => dep.startsWith(indicator))) { + if (!detectedFrameworks.includes(item.name)) { + detectedFrameworks.push(item.name); + } + break; + } + } + } + + if (item.language === 'Python') { + const requirementsPath = path.join(dir, 'requirements.txt'); + const pyprojectPath = path.join(dir, 'pyproject.toml'); + + const filesToCheck = [requirementsPath, pyprojectPath]; + for (const filePath of filesToCheck) { + if (await this.fileExists(filePath)) { + const content = await fs.promises.readFile(filePath, 'utf-8'); + for (const indicator of item.indicators) { + if (content.toLowerCase().includes(indicator.toLowerCase())) { + if (!detectedFrameworks.includes(item.name)) { + detectedFrameworks.push(item.name); + } + break; + } + } + } + } + } + + if (item.language === 'Go') { + const goModPath = path.join(dir, 'go.mod'); + if (await this.fileExists(goModPath)) { + const content = await fs.promises.readFile(goModPath, 'utf-8'); + for (const indicator of item.indicators) { + if (content.includes(indicator)) { + if (!detectedFrameworks.includes(item.name)) { + detectedFrameworks.push(item.name); + } + break; + } + } + } + } + } + + return detectedFrameworks; + } + + private async detectBuildTools( + dir: string, + _languages: string[] + ): Promise { + const buildTools: string[] = []; + + const buildToolFiles: Record = { + npm: ['package.json'], + yarn: ['yarn.lock', '.yarnrc'], + pnpm: ['pnpm-lock.yaml'], + make: ['Makefile'], + cmake: ['CMakeLists.txt'], + gradle: ['build.gradle', 'build.gradle.kts'], + maven: ['pom.xml'], + cargo: ['Cargo.toml', 'Cargo.lock'], + }; + + for (const [tool, files] of Object.entries(buildToolFiles)) { + for (const file of files) { + const filePath = path.join(dir, file); + if (await this.fileExists(filePath)) { + if (!buildTools.includes(tool)) { + buildTools.push(tool); + } + break; + } + } + } + + return buildTools; + } + + private async fileExists(filePath: string): Promise { + try { + await fs.promises.access(filePath, fs.constants.F_OK); + return true; + } catch { + return false; + } + } +}