This commit is contained in:
238
src/templates/templateLoader.ts
Normal file
238
src/templates/templateLoader.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { TemplateData } from '../types';
|
||||
|
||||
export class TemplateLoader {
|
||||
private templates: Map<string, (data: TemplateData) => string>;
|
||||
|
||||
constructor() {
|
||||
this.templates = new Map();
|
||||
this.registerDefaultTemplates();
|
||||
}
|
||||
|
||||
private registerDefaultTemplates(): void {
|
||||
this.templates.set('default', this.renderDefaultTemplate.bind(this));
|
||||
this.templates.set('cursor', this.renderCursorTemplate.bind(this));
|
||||
this.templates.set('copilot', this.renderCopilotTemplate.bind(this));
|
||||
this.templates.set('generic', this.renderGenericTemplate.bind(this));
|
||||
}
|
||||
|
||||
async loadTemplate(name: string): Promise<(data: TemplateData) => string> {
|
||||
const builtInTemplates = ['default', 'cursor', 'copilot', 'generic'];
|
||||
|
||||
if (builtInTemplates.includes(name)) {
|
||||
return this.templates.get(name)!;
|
||||
}
|
||||
|
||||
const templatePath = path.resolve(name);
|
||||
if (await this.fileExists(templatePath)) {
|
||||
const content = await fs.promises.readFile(templatePath, 'utf-8');
|
||||
return this.compileTemplate(content);
|
||||
}
|
||||
|
||||
const customTemplateDir = path.join(process.cwd(), 'templates');
|
||||
const customTemplatePath = path.join(customTemplateDir, `${name}.template`);
|
||||
if (await this.fileExists(customTemplatePath)) {
|
||||
const content = await fs.promises.readFile(customTemplatePath, 'utf-8');
|
||||
return this.compileTemplate(content);
|
||||
}
|
||||
|
||||
throw new Error(`Template not found: ${name}`);
|
||||
}
|
||||
|
||||
compileTemplate(templateContent: string): (data: TemplateData) => string {
|
||||
return (data: TemplateData): string => {
|
||||
let result = templateContent;
|
||||
|
||||
result = result.replace(/\{\{\s*project\.type\s*\}\}/g,
|
||||
data.projectInfo.projectType.primaryLanguage);
|
||||
result = result.replace(/\{\{\s*project\.languages\s*\}\}/g,
|
||||
data.projectInfo.projectType.languages.join(', '));
|
||||
result = result.replace(/\{\{\s*project\.frameworks\s*\}\}/g,
|
||||
data.projectInfo.projectType.frameworks.join(', '));
|
||||
result = result.replace(/\{\{\s*project\.fileCount\s*\}\}/g,
|
||||
String(data.projectInfo.fileCount));
|
||||
result = result.replace(/\{\{\s*dependencies\.total\s*\}\}/g,
|
||||
String(data.projectInfo.dependencies.total));
|
||||
result = result.replace(/\{\{\s*generatedAt\s*\}\}/g,
|
||||
data.generatedAt);
|
||||
|
||||
result = this.renderDependencies(result, data);
|
||||
result = this.renderConventions(result, data);
|
||||
result = this.renderFileList(result, data);
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
private renderDefaultTemplate(data: TemplateData): string {
|
||||
const jsonOutput = JSON.stringify({
|
||||
project: data.projectInfo,
|
||||
files: data.files,
|
||||
generatedAt: data.generatedAt,
|
||||
}, null, 2);
|
||||
|
||||
return `## AI Context
|
||||
|
||||
\`\`\`json
|
||||
${jsonOutput}
|
||||
\`\`\`
|
||||
|
||||
## Summary
|
||||
|
||||
- **Language**: ${data.projectInfo.projectType.primaryLanguage}
|
||||
- **Frameworks**: ${data.projectInfo.projectType.frameworks.join(', ') || 'None detected'}
|
||||
- **Dependencies**: ${data.projectInfo.dependencies.total}
|
||||
- **Files Analyzed**: ${data.projectInfo.fileCount}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderCursorTemplate(data: TemplateData): string {
|
||||
const topDeps = data.projectInfo.dependencies.direct
|
||||
.slice(0, 15)
|
||||
.map(d => ` - ${d.name}@${d.version}`)
|
||||
.join('\n');
|
||||
|
||||
return `## Project Context
|
||||
|
||||
**Language**: ${data.projectInfo.projectType.primaryLanguage}
|
||||
**Frameworks**: ${data.projectInfo.projectType.frameworks.join(', ') || 'None'}
|
||||
**Build Tools**: ${data.projectInfo.projectType.buildTools.join(', ') || 'None'}
|
||||
|
||||
### Dependencies
|
||||
${topDeps || ' No dependencies detected'}
|
||||
|
||||
### Conventions
|
||||
${data.projectInfo.conventions ? `
|
||||
- **File Naming**: ${data.projectInfo.conventions.namingConvention.files}
|
||||
- **Import Style**: ${data.projectInfo.conventions.importStyle.style}
|
||||
- **Testing Framework**: ${data.projectInfo.conventions.testingFramework || 'None'}
|
||||
- **Code Style**:
|
||||
- Indent: ${data.projectInfo.conventions.codeStyle.indentSize} ${data.projectInfo.conventions.codeStyle.indentType}
|
||||
- Quotes: ${data.projectInfo.conventions.codeStyle.quoteStyle}
|
||||
` : ' Not analyzed'}
|
||||
|
||||
### Key Files
|
||||
${data.files.slice(0, 10).map(f => `- \`${f.path}\``).join('\n')}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderCopilotTemplate(data: TemplateData): string {
|
||||
const deps = data.projectInfo.dependencies.direct
|
||||
.map(d => ` "${d.name}": "${d.version}"`)
|
||||
.join(',\n');
|
||||
|
||||
return `/* Project Context */
|
||||
Language: ${data.projectInfo.projectType.primaryLanguage}
|
||||
Frameworks: ${data.projectInfo.projectType.frameworks.join(', ') || 'None'}
|
||||
Dependencies: ${data.projectInfo.dependencies.total}
|
||||
|
||||
/* Dependencies */
|
||||
{
|
||||
${deps}
|
||||
}
|
||||
|
||||
/* Conventions */
|
||||
File Naming: ${data.projectInfo.conventions?.namingConvention.files || 'Unknown'}
|
||||
Import Style: ${data.projectInfo.conventions?.importStyle.style || 'Unknown'}
|
||||
Testing: ${data.projectInfo.conventions?.testingFramework || 'None'}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderGenericTemplate(data: TemplateData): string {
|
||||
return `=== PROJECT CONTEXT ===
|
||||
|
||||
Project Type: ${data.projectInfo.projectType.primaryLanguage}
|
||||
Languages: ${data.projectInfo.projectType.languages.join(', ')}
|
||||
Frameworks: ${data.projectInfo.projectType.frameworks.join(', ') || 'None'}
|
||||
Build Tools: ${data.projectInfo.projectType.buildTools.join(', ') || 'None'}
|
||||
|
||||
=== DEPENDENCIES ===
|
||||
Total: ${data.projectInfo.dependencies.total}
|
||||
Production: ${data.projectInfo.dependencies.direct.length}
|
||||
Development: ${data.projectInfo.dependencies.dev.length}
|
||||
|
||||
Top Dependencies:
|
||||
${data.projectInfo.dependencies.direct.slice(0, 10).map(d => ` - ${d.name} (${d.version})`).join('\n')}
|
||||
|
||||
=== CONVENTIONS ===
|
||||
${data.projectInfo.conventions ? `
|
||||
Naming:
|
||||
Files: ${data.projectInfo.conventions.namingConvention.files}
|
||||
Variables: ${data.projectInfo.conventions.namingConvention.variables}
|
||||
Functions: ${data.projectInfo.conventions.namingConvention.functions}
|
||||
Classes: ${data.projectInfo.conventions.namingConvention.classes}
|
||||
|
||||
Import Style: ${data.projectInfo.conventions.importStyle.style}
|
||||
${data.projectInfo.conventions.importStyle.aliasPrefix ? `Alias Prefix: ${data.projectInfo.conventions.importStyle.aliasPrefix}` : ''}
|
||||
Testing Framework: ${data.projectInfo.conventions.testingFramework || 'None'}
|
||||
|
||||
Code Style:
|
||||
Indent: ${data.projectInfo.conventions.codeStyle.indentSize} ${data.projectInfo.conventions.codeStyle.indentType}
|
||||
Line Endings: ${data.projectInfo.conventions.codeStyle.lineEndings}
|
||||
Quote Style: ${data.projectInfo.conventions.codeStyle.quoteStyle}
|
||||
` : ' Not analyzed'}
|
||||
|
||||
=== FILES ===
|
||||
Total Files: ${data.projectInfo.fileCount}
|
||||
${data.files.slice(0, 20).map(f => ` - ${f.path}`).join('\n')}
|
||||
`;
|
||||
}
|
||||
|
||||
private renderDependencies(
|
||||
template: string,
|
||||
data: TemplateData
|
||||
): string {
|
||||
const deps = data.projectInfo.dependencies.direct
|
||||
.map(d => ` - ${d.name}@${d.version}`)
|
||||
.join('\n');
|
||||
|
||||
return template
|
||||
.replace(/\{\{\s*dependencies\s*\}\}/g, deps)
|
||||
.replace(/\{\{\s*dependencies\.count\s*\}\}/g,
|
||||
String(data.projectInfo.dependencies.total));
|
||||
}
|
||||
|
||||
private renderConventions(
|
||||
template: string,
|
||||
data: TemplateData
|
||||
): string {
|
||||
if (!data.projectInfo.conventions) {
|
||||
return template;
|
||||
}
|
||||
|
||||
const conventions = data.projectInfo.conventions;
|
||||
|
||||
return template
|
||||
.replace(/\{\{\s*conventions\.naming\s*\}\}/g,
|
||||
conventions.namingConvention.files)
|
||||
.replace(/\{\{\s*conventions\.importStyle\s*\}\}/g,
|
||||
conventions.importStyle.style)
|
||||
.replace(/\{\{\s*conventions\.testing\s*\}\}/g,
|
||||
conventions.testingFramework || 'None');
|
||||
}
|
||||
|
||||
private renderFileList(
|
||||
template: string,
|
||||
data: TemplateData
|
||||
): string {
|
||||
const fileList = data.files
|
||||
.slice(0, 30)
|
||||
.map(f => ` - ${f.path}`)
|
||||
.join('\n');
|
||||
|
||||
return template
|
||||
.replace(/\{\{\s*files\s*\}\}/g, fileList)
|
||||
.replace(/\{\{\s*files\.count\s*\}\}/g,
|
||||
String(data.projectInfo.fileCount));
|
||||
}
|
||||
|
||||
private async fileExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.promises.access(filePath, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user