UNPKG

@morodomi/ait3

Version:

AIT³ Development Platform - AI + Ticket + Test + Tool driven development methodology

295 lines (294 loc) 9.76 kB
import { readFile, access } from 'fs/promises'; import { join } from 'path'; import { constants } from 'fs'; export class ConfigBasedCommandDetector { rootPath; languageDetectors = { test: { Python: this.detectPythonTest.bind(this), PHP: this.detectPHPTest.bind(this) }, lint: { Python: this.detectPythonLint.bind(this), PHP: this.detectPHPLint.bind(this), TypeScript: this.detectJSLint.bind(this), JavaScript: this.detectJSLint.bind(this) }, format: { Python: this.detectPythonFormat.bind(this), PHP: this.detectPHPFormat.bind(this), TypeScript: this.detectJSFormat.bind(this), JavaScript: this.detectJSFormat.bind(this) }, build: { Python: this.detectPythonBuild.bind(this), PHP: this.detectPHPBuild.bind(this), TypeScript: this.detectTSBuild.bind(this) } }; constructor(rootPath) { this.rootPath = rootPath; } async detectTestCommand(path, language) { return this.detectCommand('test', path, language, 'npm test'); } async detectLintCommand(path, language) { return this.detectCommand('lint', path, language, ''); } async detectFormatCommand(path, language) { return this.detectCommand('format', path, language, ''); } async detectBuildCommand(path, language) { return this.detectCommand('build', path, language, ''); } async detectCommand(type, path, language, defaultCommand) { const targetPath = path || this.rootPath; // Try package.json first for JS/TS projects const packageCommand = await this.detectPackageJsonCommand(targetPath, type); if (packageCommand) return packageCommand; // Try language-specific detection if (language && this.languageDetectors[type][language]) { const result = await this.languageDetectors[type][language](targetPath); if (result) return result; } // Try all language detectors if no language specified if (!language) { for (const detector of Object.values(this.languageDetectors[type])) { const result = await detector(targetPath); if (result) return result; } } // Return default or not found if (defaultCommand) { return { command: defaultCommand, detected: false, source: 'default', confidence: 0.3 }; } return { command: '', detected: false, source: 'not-found', confidence: 0 }; } async detectPackageJsonCommand(targetPath, type) { const packageJson = await this.readPackageJson(targetPath); if (packageJson && typeof packageJson === 'object' && 'scripts' in packageJson && packageJson.scripts && typeof packageJson.scripts === 'object' && type in packageJson.scripts) { return { command: `npm ${type === 'test' ? 'test' : `run ${type}`}`, detected: true, source: 'package.json', confidence: 1.0 }; } return null; } // Python detection methods async detectPythonTest(targetPath) { const [hasPytest, hasPyproject, hasTests] = await Promise.all([ this.fileExists(join(targetPath, 'pytest.ini')), this.fileExists(join(targetPath, 'pyproject.toml')), this.fileExists(join(targetPath, 'tests')) ]); if (hasPytest || hasPyproject) { return { command: 'pytest', detected: true, source: 'config-file', confidence: 0.9 }; } if (hasTests) { return { command: 'python -m unittest', detected: true, source: 'convention', confidence: 0.7 }; } return null; } async detectPythonLint(targetPath) { const [hasRuff, hasPyproject] = await Promise.all([ this.fileExists(join(targetPath, '.ruff.toml')), this.readPyprojectToml(targetPath) ]); if (hasRuff || hasPyproject?.includes('[tool.ruff]')) { return { command: 'ruff check', detected: true, source: 'config-file', confidence: 0.9 }; } if (await this.fileExists(join(targetPath, '.flake8'))) { return { command: 'flake8', detected: true, source: 'config-file', confidence: 0.8 }; } return null; } async detectPythonFormat(targetPath) { const pyproject = await this.readPyprojectToml(targetPath); if (pyproject?.includes('[tool.black]')) { return { command: 'black .', detected: true, source: 'config-file', confidence: 0.9 }; } return null; } async detectPythonBuild(targetPath) { if (await this.fileExists(join(targetPath, 'setup.py'))) { return { command: 'python setup.py build', detected: true, source: 'config-file', confidence: 0.7 }; } return null; } // PHP detection methods async detectPHPTest(targetPath) { const [hasPhpUnit, hasPhpUnitDist] = await Promise.all([ this.fileExists(join(targetPath, 'phpunit.xml')), this.fileExists(join(targetPath, 'phpunit.xml.dist')) ]); if (hasPhpUnit || hasPhpUnitDist) { return { command: './vendor/bin/phpunit', detected: true, source: 'config-file', confidence: 0.9 }; } return null; } async detectPHPLint(targetPath) { const [hasPhpCs, hasPhpCsDist] = await Promise.all([ this.fileExists(join(targetPath, 'phpcs.xml')), this.fileExists(join(targetPath, 'phpcs.xml.dist')) ]); if (hasPhpCs || hasPhpCsDist) { return { command: './vendor/bin/phpcs', detected: true, source: 'config-file', confidence: 0.9 }; } return null; } async detectPHPFormat(targetPath) { const [hasPhpCsFixer, hasLegacyFixer] = await Promise.all([ this.fileExists(join(targetPath, '.php-cs-fixer.php')), this.fileExists(join(targetPath, '.php_cs')) ]); if (hasPhpCsFixer || hasLegacyFixer) { return { command: './vendor/bin/php-cs-fixer fix', detected: true, source: 'config-file', confidence: 0.9 }; } return null; } async detectPHPBuild(targetPath) { if (await this.fileExists(join(targetPath, 'composer.json'))) { return { command: 'composer install --no-dev', detected: true, source: 'config-file', confidence: 0.6 }; } return null; } // JavaScript/TypeScript detection methods async detectJSLint(targetPath) { const eslintConfigs = ['.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']; const hasEslint = await this.anyFileExists(targetPath, eslintConfigs); if (hasEslint) { return { command: 'npx eslint .', detected: true, source: 'config-file', confidence: 0.8 }; } return null; } async detectJSFormat(targetPath) { const prettierConfigs = ['.prettierrc', '.prettierrc.json', '.prettierrc.js']; const hasPrettier = await this.anyFileExists(targetPath, prettierConfigs); if (hasPrettier) { return { command: 'npx prettier --write .', detected: true, source: 'config-file', confidence: 0.8 }; } return null; } async detectTSBuild(targetPath) { if (await this.fileExists(join(targetPath, 'tsconfig.json'))) { return { command: 'npx tsc', detected: true, source: 'config-file', confidence: 0.7 }; } return null; } // Utility methods async anyFileExists(basePath, files) { const checks = await Promise.all(files.map(file => this.fileExists(join(basePath, file)))); return checks.some(exists => exists); } async readPackageJson(targetPath) { try { const content = await readFile(join(targetPath, 'package.json'), 'utf-8'); return JSON.parse(content); } catch { return null; } } async readPyprojectToml(targetPath) { try { return await readFile(join(targetPath, 'pyproject.toml'), 'utf-8'); } catch { return null; } } async fileExists(filePath) { try { await access(filePath, constants.F_OK); return true; } catch { return false; } } }