This commit is contained in:
171
app/src/analyzers/projectTypeDetector.ts
Normal file
171
app/src/analyzers/projectTypeDetector.ts
Normal file
@@ -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<ProjectType, string[]> = {
|
||||
node: ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'],
|
||||
python: ['.py', '.pyw'],
|
||||
go: ['.go'],
|
||||
rust: ['.rs'],
|
||||
java: ['.java', '.kt', '.scala'],
|
||||
unknown: []
|
||||
};
|
||||
|
||||
const DEPENDENCY_FILES: Record<string, ProjectType> = {
|
||||
'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<ProjectType>();
|
||||
|
||||
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<string, string>; devDependencies?: Record<string, string> }>(packageJsonPath);
|
||||
const deps = {
|
||||
...packageJson?.dependencies,
|
||||
...packageJson?.devDependencies
|
||||
};
|
||||
|
||||
const frameworkPatterns: Record<string, string[]> = {
|
||||
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<string, unknown>;
|
||||
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<ProjectInfo> {
|
||||
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
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user