@morodomi/ait3
Version:
AIT³ Development Platform - AI + Ticket + Test + Tool driven development methodology
271 lines (270 loc) • 9.44 kB
JavaScript
import { readdir, stat, readFile, access } from 'fs/promises';
import { join } from 'path';
import { constants } from 'fs';
export class DirectoryStructureAnalyzer {
rootPath;
constructor(rootPath) {
this.rootPath = rootPath;
}
async analyzeStructure(path) {
const targetPath = path || this.rootPath;
const directories = await this.scanDirectories(targetPath);
const hasGitRepository = await this.checkForGit(targetPath);
const hasCICD = await this.checkForCICD(targetPath);
const hasDocker = await this.checkForDocker(targetPath);
return {
rootPath: targetPath,
directories,
hasGitRepository,
hasCICD,
hasDocker
};
}
async detectFramework(path, language) {
const targetPath = path || this.rootPath;
// TypeScript/JavaScript frameworks
if (language === 'TypeScript' || language === 'JavaScript' || !language) {
// Next.js
if (await this.fileExists(join(targetPath, 'next.config.js')) ||
await this.fileExists(join(targetPath, 'next.config.mjs'))) {
const version = await this.getPackageVersion(targetPath, 'next');
return {
name: 'Next.js',
version,
type: 'fullstack',
confidence: 0.95
};
}
// Express
const packageJson = await this.readPackageJson(targetPath);
if (packageJson?.dependencies?.express) {
return {
name: 'Express',
version: this.extractVersion(packageJson.dependencies.express),
type: 'backend',
confidence: 0.85
};
}
// Check for library project
if (packageJson?.main || packageJson?.types) {
return {
name: 'Node.js Library',
version: packageJson.version,
type: 'library',
confidence: 0.7
};
}
}
// Python frameworks
if (language === 'Python' || !language) {
// Django
if (await this.fileExists(join(targetPath, 'manage.py'))) {
const requirementsVersion = await this.getRequirementsVersion(targetPath, 'django');
return {
name: 'Django',
version: requirementsVersion || '4.2.0',
type: 'fullstack',
confidence: 0.95
};
}
// Flask
const requirements = await this.readRequirements(targetPath);
if (requirements?.includes('flask')) {
const version = await this.getRequirementsVersion(targetPath, 'flask');
return {
name: 'Flask',
version: version || '2.3.0',
type: 'backend',
confidence: 0.9
};
}
}
// PHP frameworks
if (language === 'PHP' || !language) {
// Laravel
if (await this.fileExists(join(targetPath, 'artisan'))) {
const composerJson = await this.readComposerJson(targetPath);
const version = this.extractVersion(composerJson?.require?.['laravel/framework']);
return {
name: 'Laravel',
version: version || '10.0',
type: 'fullstack',
confidence: 0.95
};
}
}
return {
name: 'Unknown',
type: 'unknown',
confidence: 0
};
}
async scanDirectories(targetPath) {
const directories = [];
try {
const entries = await readdir(targetPath);
for (const entry of entries) {
const fullPath = join(targetPath, entry);
const stats = await stat(fullPath);
if (stats.isDirectory() && !this.shouldIgnoreDirectory(entry)) {
const fileCount = await this.countFiles(fullPath);
const type = this.detectDirectoryType(entry);
directories.push({
path: fullPath,
name: entry,
type,
fileCount
});
}
}
}
catch {
// Return empty array on error
}
return directories.sort((a, b) => a.name.localeCompare(b.name));
}
async countFiles(dir) {
let count = 0;
try {
const entries = await readdir(dir);
for (const entry of entries) {
const fullPath = join(dir, entry);
const stats = await stat(fullPath);
if (stats.isFile()) {
count++;
}
else if (stats.isDirectory() && !this.shouldIgnoreDirectory(entry)) {
count += await this.countFiles(fullPath);
}
}
}
catch {
// Ignore errors
}
return count;
}
detectDirectoryType(name) {
// Source directories
if (['src', 'lib', 'app', 'source'].includes(name)) {
return 'source';
}
// Test directories
if (['test', 'tests', 'spec', 'specs', '__tests__'].includes(name)) {
return 'test';
}
// Build/output directories
if (['dist', 'build', 'out', 'public', 'target'].includes(name)) {
return 'build';
}
// Configuration directories
if (['config', 'configs', '.config'].includes(name)) {
return 'config';
}
// Documentation
if (['docs', 'doc', 'documentation'].includes(name)) {
return 'docs';
}
return 'other';
}
shouldIgnoreDirectory(name) {
const ignoreDirs = [
'node_modules',
'.git',
'.svn',
'.hg',
'coverage',
'.nyc_output',
'.next',
'__pycache__',
'.pytest_cache',
'vendor',
'.venv',
'venv',
'env',
'.env',
'.idea',
'.vscode',
'.DS_Store'
];
return ignoreDirs.includes(name) || name.startsWith('.');
}
async checkForGit(targetPath) {
return await this.fileExists(join(targetPath, '.git')) ||
await this.fileExists(join(targetPath, '.gitignore'));
}
async checkForCICD(targetPath) {
return await this.fileExists(join(targetPath, '.github/workflows')) ||
await this.fileExists(join(targetPath, '.gitlab-ci.yml')) ||
await this.fileExists(join(targetPath, 'Jenkinsfile')) ||
await this.fileExists(join(targetPath, '.circleci')) ||
await this.fileExists(join(targetPath, 'azure-pipelines.yml'));
}
async checkForDocker(targetPath) {
return await this.fileExists(join(targetPath, 'Dockerfile')) ||
await this.fileExists(join(targetPath, 'docker-compose.yml')) ||
await this.fileExists(join(targetPath, 'docker-compose.yaml'));
}
async fileExists(filePath) {
try {
await access(filePath, constants.F_OK);
return true;
}
catch {
return false;
}
}
async readPackageJson(targetPath) {
try {
const content = await readFile(join(targetPath, 'package.json'), 'utf-8');
return JSON.parse(content);
}
catch {
return null;
}
}
async readComposerJson(targetPath) {
try {
const content = await readFile(join(targetPath, 'composer.json'), 'utf-8');
return JSON.parse(content);
}
catch {
return null;
}
}
async readRequirements(targetPath) {
try {
return await readFile(join(targetPath, 'requirements.txt'), 'utf-8');
}
catch {
return null;
}
}
async getPackageVersion(targetPath, packageName) {
const packageJson = await this.readPackageJson(targetPath);
if (packageJson?.dependencies?.[packageName]) {
return this.extractVersion(packageJson.dependencies[packageName]);
}
return undefined;
}
async getRequirementsVersion(targetPath, packageName) {
const requirements = await this.readRequirements(targetPath);
if (!requirements)
return null;
const lines = requirements.split('\n');
for (const line of lines) {
if (line.toLowerCase().startsWith(packageName)) {
const match = line.match(/==(.+)/);
if (match)
return match[1];
}
}
return null;
}
extractVersion(versionString) {
if (!versionString)
return undefined;
// Remove prefixes like ^, ~, >=, etc.
const match = versionString.match(/\d+\.\d+(\.\d+)?/);
return match ? match[0] : undefined;
}
}