@jmkim85/dev-flow-mcp
Version:
MCP-based Dev Flow - AI-powered development workflow management with 13 essential tools for TDD and context management
192 lines • 7.76 kB
JavaScript
/**
* Template Manager for Dev Flow MCP v2.1
* Handles project template initialization and management
*/
import { promises as fs } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
export class TemplateManager {
projectRoot;
templatesDir;
constructor(projectRoot) {
this.projectRoot = projectRoot;
// Get the directory where this script is located
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
this.templatesDir = join(__dirname, '..', 'templates');
}
async initializeTemplate(templateType) {
try {
const templateInfo = this.getTemplateInfo(templateType);
const targetPath = join(this.projectRoot, templateInfo.targetFile);
// Check if file already exists
try {
await fs.access(targetPath);
return {
success: true,
message: `${templateInfo.displayName} already exists at ${templateInfo.targetFile}`,
file_path: targetPath,
skipped: true
};
}
catch {
// File doesn't exist, proceed with creation
}
// Read template content
const templatePath = join(this.templatesDir, templateInfo.templateFile);
let templateContent;
try {
templateContent = await fs.readFile(templatePath, 'utf-8');
}
catch (error) {
return {
success: false,
message: `Template file not found: ${templateInfo.templateFile}. Error: ${error.message}`
};
}
// Process template content (replace placeholders)
const processedContent = await this.processTemplate(templateContent, templateType);
// Ensure target directory exists
const targetDir = dirname(targetPath);
await fs.mkdir(targetDir, { recursive: true });
// Write the processed template
await fs.writeFile(targetPath, processedContent, 'utf-8');
return {
success: true,
message: `${templateInfo.displayName} created successfully at ${templateInfo.targetFile}`,
file_path: targetPath,
skipped: false
};
}
catch (error) {
return {
success: false,
message: `Error initializing ${templateType} template: ${error.message}`
};
}
}
getTemplateInfo(templateType) {
const templates = {
project_overview: {
templateFile: 'project_overview.md',
targetFile: 'project_overview.md',
displayName: 'Project Overview'
},
tasklist: {
templateFile: 'tasklist_template.yaml',
targetFile: '.devflow/tasks.yaml',
displayName: 'Task List'
},
readme: {
templateFile: 'readme_template.md',
targetFile: 'README.md',
displayName: 'README'
},
requirements: {
templateFile: 'requirements.md',
targetFile: '.devflow/requirements.md',
displayName: 'Requirements Document'
},
design_spec: {
templateFile: 'design_spec.md',
targetFile: '.devflow/design_spec.md',
displayName: 'Design Specification'
}
};
return templates[templateType];
}
async processTemplate(content, templateType) {
// Get project name from directory
const projectName = this.projectRoot.split(/[/\\]/).pop() || 'My Project';
const currentDate = new Date().toISOString().split('T')[0];
// Basic placeholder replacements
let processed = content
.replace(/\[Project Name\]/g, projectName)
.replace(/\[Date\]/g, currentDate)
.replace(/\[project-directory\]/g, projectName.toLowerCase().replace(/\s+/g, '-'))
.replace(/\[repository-url\]/g, `https://github.com/username/${projectName.toLowerCase().replace(/\s+/g, '-')}`);
// Template-specific processing
if (templateType === 'tasklist') {
// Update timestamps in YAML
const timestamp = new Date().toISOString();
processed = processed
.replace(/2024-01-01T00:00:00Z/g, timestamp);
}
// Detect project type and customize accordingly
const projectType = await this.detectProjectType();
processed = this.customizeForProjectType(processed, projectType);
return processed;
}
async detectProjectType() {
const indicators = [
{ file: 'package.json', type: 'Node.js' },
{ file: 'requirements.txt', type: 'Python' },
{ file: 'Cargo.toml', type: 'Rust' },
{ file: 'go.mod', type: 'Go' },
{ file: 'pom.xml', type: 'Java (Maven)' },
{ file: 'build.gradle', type: 'Java (Gradle)' }
];
for (const indicator of indicators) {
try {
await fs.access(join(this.projectRoot, indicator.file));
return indicator.type;
}
catch {
// File doesn't exist, continue checking
}
}
return 'General';
}
customizeForProjectType(content, projectType) {
const testCommands = {
'Node.js': 'npm test',
'Python': 'pytest',
'Rust': 'cargo test',
'Go': 'go test ./...',
'Java (Maven)': 'mvn test',
'Java (Gradle)': 'gradle test',
'General': 'npm test'
};
const installCommands = {
'Node.js': 'npm install',
'Python': 'pip install -r requirements.txt',
'Rust': 'cargo build',
'Go': 'go mod download',
'Java (Maven)': 'mvn install',
'Java (Gradle)': 'gradle build',
'General': 'npm install'
};
const languages = {
'Node.js': 'javascript',
'Python': 'python',
'Rust': 'rust',
'Go': 'go',
'Java (Maven)': 'java',
'Java (Gradle)': 'java',
'General': 'javascript'
};
return content
.replace(/\[test command\]/g, testCommands[projectType] || 'npm test')
.replace(/\[installation command\]/g, installCommands[projectType] || 'npm install')
.replace(/\[development command\]/g, installCommands[projectType] || 'npm start')
.replace(/\[language\]/g, languages[projectType] || 'javascript')
.replace(/npm test/g, testCommands[projectType] || 'npm test')
.replace(/\[Primary programming language\]/g, projectType);
}
async listAvailableTemplates() {
return ['project_overview', 'tasklist', 'readme', 'requirements', 'design_spec'];
}
async getTemplatePreview(templateType) {
const templateInfo = this.getTemplateInfo(templateType);
const templatePath = join(this.templatesDir, templateInfo.templateFile);
try {
const content = await fs.readFile(templatePath, 'utf-8');
// Return first 10 lines as preview
return content.split('\n').slice(0, 10).join('\n') + '\n...';
}
catch (error) {
return `Preview not available: ${error.message}`;
}
}
}
//# sourceMappingURL=template-manager.js.map