diff --git a/app/src/analyzers/projectTypeDetector.ts b/app/src/analyzers/projectTypeDetector.ts new file mode 100644 index 0000000..504275b --- /dev/null +++ b/app/src/analyzers/projectTypeDetector.ts @@ -0,0 +1,171 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import { ProjectInfo, ProjectType, Framework } from '../types'; +import { parseJSONFile, parseTOMLFile, getFileExtension, isFileExists } from '../utils/fileUtils'; + +const LANGUAGE_FILE_EXTENSIONS: Record = { + node: ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'], + python: ['.py', '.pyw'], + go: ['.go'], + rust: ['.rs'], + java: ['.java', '.kt', '.scala'], + unknown: [] +}; + +const DEPENDENCY_FILES: Record = { + 'package.json': 'node', + 'requirements.txt': 'python', + 'pyproject.toml': 'python', + 'setup.py': 'python', + 'go.mod': 'go', + 'Cargo.toml': 'rust', + 'pom.xml': 'java', + 'build.gradle': 'java', + 'build.gradle.kts': 'java' +}; + +export function detectProjectType(directory: string): ProjectType { + const files = fs.readdirSync(directory); + + for (const file of files) { + if (DEPENDENCY_FILES[file]) { + return DEPENDENCY_FILES[file]; + } + } + + const fileExtensions = new Set(); + + for (const file of files) { + const fullPath = path.join(directory, file); + if (fs.statSync(fullPath).isFile()) { + const ext = getFileExtension(file); + + for (const [type, extensions] of Object.entries(LANGUAGE_FILE_EXTENSIONS)) { + if (extensions.includes(ext)) { + fileExtensions.add(type as ProjectType); + } + } + } + } + + if (fileExtensions.size === 1) { + return fileExtensions.values().next().value as ProjectType; + } + + if (fileExtensions.has('node') && fileExtensions.has('python')) { + return 'node'; + } + + return 'unknown'; +} + +export function detectFramework(directory: string, projectType: ProjectType): Framework { + if (projectType === 'unknown') { + return null; + } + + const packageJsonPath = path.join(directory, 'package.json'); + const packageJson = parseJSONFile<{ dependencies?: Record; devDependencies?: Record }>(packageJsonPath); + const deps = { + ...packageJson?.dependencies, + ...packageJson?.devDependencies + }; + + const frameworkPatterns: Record = { + react: ['react', 'react-dom'], + vue: ['vue'], + angular: ['@angular/core'], + nextjs: ['next'], + nuxt: ['nuxt'], + express: ['express'], + fastify: ['fastify'], + nestjs: ['@nestjs/core'], + django: ['django'], + flask: ['flask'], + fastapi: ['fastapi'], + gin: ['github.com/gin-gonic/gin'], + fiber: ['github.com/gofiber/fiber'], + spring: ['spring-boot'] + }; + + if (projectType === 'node' && packageJson) { + for (const [framework, packages] of Object.entries(frameworkPatterns)) { + if (['react', 'vue', 'angular', 'nextjs', 'nuxt', 'express', 'fastify', 'nestjs'].includes(framework)) { + for (const pkg of packages) { + if (deps[pkg]) { + return framework as Framework; + } + } + } + } + } + + if (projectType === 'python') { + const pyprojectPath = path.join(directory, 'pyproject.toml'); + const pyproject = parseTOMLFile(pyprojectPath); + + if (pyproject?.tool) { + const tool = pyproject.tool as Record; + if (tool['fastapi']) return 'fastapi'; + if (tool['flask']) return 'flask'; + if (tool['django']) return 'django'; + } + } + + if (projectType === 'go') { + const goModPath = path.join(directory, 'go.mod'); + const goMod = parseTOMLFile(goModPath); + + if (goMod?.require) { + const require = goMod.require as Array<{ path: string }>; + for (const dep of require) { + if (dep.path === 'github.com/gin-gonic/gin') return 'gin'; + if (dep.path === 'github.com/gofiber/fiber') return 'fiber'; + } + } + } + + if (projectType === 'java') { + if (isFileExists(path.join(directory, 'pom.xml'))) { + return 'maven'; + } + if (isFileExists(path.join(directory, 'build.gradle')) || + isFileExists(path.join(directory, 'build.gradle.kts'))) { + return 'gradle'; + } + if (isFileExists(path.join(directory, 'pom.xml'))) { + const pom = parseJSONFile<{ project?: { packaging?: string } }>(path.join(directory, 'pom.xml')); + if (pom?.project?.packaging === 'jar') { + return 'spring'; + } + } + } + + return null; +} + +export async function getProjectInfo(directory: string): Promise { + const projectType = detectProjectType(directory); + const framework = detectFramework(directory, projectType); + + let languageVersion: string | undefined; + + if (projectType === 'node') { + const packageJson = parseJSONFile<{ engines?: { node?: string } }>(path.join(directory, 'package.json')); + languageVersion = packageJson?.engines?.node; + } else if (projectType === 'python') { + const pyproject = parseJSONFile<{ 'requires-python'?: string }>(path.join(directory, 'pyproject.toml')); + languageVersion = pyproject?.['requires-python']; + } else if (projectType === 'go') { + const goMod = parseJSONFile<{ go?: string }>(path.join(directory, 'go.mod')); + languageVersion = goMod?.go; + } + + return { + type: projectType, + framework: framework, + language: projectType.charAt(0).toUpperCase() + projectType.slice(1), + languageVersion, + frameworkVersion: undefined + }; +}