fix: resolve CI test failures
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-01 01:43:57 +00:00
parent 87253bbd52
commit 67210c8d78

View File

@@ -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<ProjectType> {
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<string> {
if (languages.length === 0) return 'Unknown';
return languages[0];
}
private async detectLanguages(dir: string): Promise<string[]> {
const detectedLanguages: string[] = [];
const contents = await fs.promises.readdir(dir, { withFileTypes: true });
const fileExtensions = new Set<string>();
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<string[]> {
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<string[]> {
const buildTools: string[] = [];
const buildToolFiles: Record<string, string[]> = {
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<boolean> {
try {
await fs.promises.access(filePath, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
}