168 lines
4.6 KiB
TypeScript
168 lines
4.6 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import yaml from 'js-yaml';
|
|
import {
|
|
ProjectInfo,
|
|
ContextConfig,
|
|
TemplateData,
|
|
FileInfo,
|
|
ProjectType,
|
|
DependencyInfo,
|
|
ConventionInfo,
|
|
} from '../types';
|
|
import { ProjectTypeDetector } from '../analyzers/projectTypeDetector';
|
|
import { DependencyAnalyzer } from '../analyzers/dependencyAnalyzer';
|
|
import { ConventionExtractor } from '../analyzers/conventionExtractor';
|
|
import { FileUtils } from '../utils/fileUtils';
|
|
import { ConfigLoader } from '../config/configLoader';
|
|
|
|
export class ContextGenerator {
|
|
private projectTypeDetector: ProjectTypeDetector;
|
|
private dependencyAnalyzer: DependencyAnalyzer;
|
|
private conventionExtractor: ConventionExtractor;
|
|
private fileUtils: FileUtils;
|
|
|
|
constructor() {
|
|
this.projectTypeDetector = new ProjectTypeDetector();
|
|
this.dependencyAnalyzer = new DependencyAnalyzer();
|
|
this.conventionExtractor = new ConventionExtractor();
|
|
this.fileUtils = FileUtils.getInstance();
|
|
}
|
|
|
|
async generate(
|
|
dir: string,
|
|
config?: ContextConfig
|
|
): Promise<ProjectInfo> {
|
|
const resolvedDir = this.fileUtils.resolveDirectory(dir);
|
|
|
|
const contextConfig = config ?? await ConfigLoader.load();
|
|
|
|
if (contextConfig.respectGitignore) {
|
|
await this.fileUtils.loadGitignore(resolvedDir);
|
|
this.fileUtils.addCustomPatterns(contextConfig.excludes);
|
|
}
|
|
|
|
const files = await this.fileUtils.getFiles(
|
|
resolvedDir,
|
|
contextConfig.includes,
|
|
contextConfig.excludes
|
|
);
|
|
|
|
const projectType = await this.projectTypeDetector.detect(resolvedDir);
|
|
const dependencies = await this.dependencyAnalyzer.analyze(
|
|
resolvedDir,
|
|
contextConfig.includeDevDependencies
|
|
);
|
|
|
|
let conventions: ConventionInfo | null = null;
|
|
if (contextConfig.analyzeConventions) {
|
|
conventions = await this.conventionExtractor.extract(resolvedDir, files) ?? null;
|
|
}
|
|
|
|
return {
|
|
projectType,
|
|
language: projectType.primaryLanguage,
|
|
framework: projectType.frameworks[0] || null,
|
|
dependencies,
|
|
conventions,
|
|
fileCount: files.length,
|
|
analysisDate: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
async generateWithFiles(
|
|
dir: string,
|
|
config?: ContextConfig
|
|
): Promise<TemplateData> {
|
|
const projectInfo = await this.generate(dir, config);
|
|
const contextConfig = config ?? await ConfigLoader.load();
|
|
const resolvedDir = this.fileUtils.resolveDirectory(dir);
|
|
|
|
const files = await this.fileUtils.getFiles(
|
|
resolvedDir,
|
|
contextConfig.includes,
|
|
contextConfig.excludes
|
|
);
|
|
|
|
const fileInfos: FileInfo[] = [];
|
|
for (const file of files) {
|
|
try {
|
|
const size = await this.fileUtils.getFileSize(file);
|
|
fileInfos.push({
|
|
path: path.relative(resolvedDir, file),
|
|
size,
|
|
type: this.fileUtils.getFileExtension(file),
|
|
language: this.detectLanguage(file),
|
|
});
|
|
} catch {
|
|
// Skip files that can't be read
|
|
}
|
|
}
|
|
|
|
return {
|
|
projectInfo,
|
|
files: fileInfos,
|
|
config: contextConfig,
|
|
generatedAt: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
async generateJson(dir: string, config?: ContextConfig): Promise<string> {
|
|
const data = await this.generateWithFiles(dir, config);
|
|
return JSON.stringify(data, null, 2);
|
|
}
|
|
|
|
async generateYaml(dir: string, config?: ContextConfig): Promise<string> {
|
|
const data = await this.generateWithFiles(dir, config);
|
|
return yaml.dump(data, { indent: 2, lineWidth: -1 });
|
|
}
|
|
|
|
async saveContext(
|
|
dir: string,
|
|
outputPath: string,
|
|
format: 'json' | 'yaml',
|
|
config?: ContextConfig
|
|
): Promise<void> {
|
|
let content: string;
|
|
let finalPath = outputPath;
|
|
|
|
if (format === 'json') {
|
|
content = await this.generateJson(dir, config);
|
|
if (!finalPath.endsWith('.json')) {
|
|
finalPath = `${finalPath}.json`;
|
|
}
|
|
} else {
|
|
content = await this.generateYaml(dir, config);
|
|
if (!finalPath.endsWith('.yaml') && !finalPath.endsWith('.yml')) {
|
|
finalPath = `${finalPath}.yaml`;
|
|
}
|
|
}
|
|
|
|
await this.fileUtils.writeFile(finalPath, content);
|
|
}
|
|
|
|
private detectLanguage(filePath: string): string {
|
|
const ext = this.fileUtils.getFileExtension(filePath);
|
|
const languageMap: Record<string, string> = {
|
|
'.ts': 'TypeScript',
|
|
'.tsx': 'TypeScript',
|
|
'.js': 'JavaScript',
|
|
'.jsx': 'JavaScript',
|
|
'.py': 'Python',
|
|
'.go': 'Go',
|
|
'.rs': 'Rust',
|
|
'.java': 'Java',
|
|
'.c': 'C',
|
|
'.cpp': 'C++',
|
|
'.h': 'C',
|
|
'.hpp': 'C++',
|
|
'.cs': 'C#',
|
|
'.rb': 'Ruby',
|
|
'.php': 'PHP',
|
|
'.swift': 'Swift',
|
|
};
|
|
|
|
return languageMap[ext] || 'Unknown';
|
|
}
|
|
}
|