ai-coding-assistants-setup
Version:
Setup tool for integrating AI coding assistants into development workflows
218 lines • 8.45 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import { Feedback } from './feedback.js';
/**
* Template categories for organization
*/
export var TemplateCategory;
(function (TemplateCategory) {
TemplateCategory["CONFIG"] = "configuration";
TemplateCategory["DOCUMENTATION"] = "documentation";
TemplateCategory["HOOK"] = "hook";
TemplateCategory["TEMPLATE"] = "template";
TemplateCategory["TOOL"] = "tool";
TemplateCategory["OTHER"] = "other";
})(TemplateCategory || (TemplateCategory = {}));
/**
* Scanner for template files
*/
export class TemplateScanner {
/**
* Constructor for TemplateScanner
*
* @param templateDir Directory containing template files
* @param targetDir Directory where templates will be copied/merged
* @param verbose Whether to enable verbose logging
*/
constructor(templateDir, targetDir, verbose = false) {
this.templates = [];
this.templateDir = templateDir;
this.targetDir = targetDir;
this.verbose = verbose;
}
/**
* Scan the template directory for template files
*
* @returns Array of template files
*/
async scan() {
try {
if (!fs.existsSync(this.templateDir)) {
throw new Error(`Template directory not found: ${this.templateDir}`);
}
this.templates = [];
await this.scanDirectory(this.templateDir);
if (this.verbose) {
Feedback.info(`Found ${this.templates.length} template files`);
}
return this.templates;
}
catch (error) {
Feedback.error(`Error scanning templates: ${error instanceof Error ? error.message : String(error)}`);
return [];
}
}
/**
* Recursively scan a directory for template files
*
* @param dir Directory to scan
* @param relativeDir Relative path from template root directory
*/
async scanDirectory(dir, relativeDir = '') {
try {
const items = await fs.readdir(dir);
for (const item of items) {
const itemPath = path.join(dir, item);
const relItemPath = path.join(relativeDir, item);
const targetPath = path.join(this.targetDir, relItemPath);
const stats = await fs.stat(itemPath);
if (stats.isDirectory()) {
// Skip node_modules and .git directories
if (item === 'node_modules' || item === '.git') {
continue;
}
// Recurse into subdirectories
await this.scanDirectory(itemPath, relItemPath);
}
else if (stats.isFile()) {
// Skip if template file is itself a template pattern (e.g. *.template.*)
if (item.includes('.template.')) {
continue;
}
this.templates.push({
sourcePath: itemPath,
targetPath,
relativePath: relItemPath,
category: this.categorizeTemplate(relItemPath),
});
if (this.verbose) {
Feedback.info(`Found template: ${relItemPath}`);
}
}
}
}
catch (error) {
Feedback.error(`Error scanning directory ${dir}: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Get a mapping of source to target paths for template files
*
* @returns Map of source paths to target paths
*/
getTemplateMap() {
const map = new Map();
for (const template of this.templates) {
// Special case for mcp.json - map it to both .mcp.json in the root and .roo/mcp.json
if (template.relativePath === '.roo/mcp.json') {
// Map to .mcp.json in the root
const rootTargetPath = path.join(this.targetDir, '.mcp.json');
map.set(template.sourcePath, rootTargetPath);
// Also map to .roo/mcp.json in the parent project root
const rooTargetPath = path.join(this.targetDir, '.roo', 'mcp.json');
// We need to use a different key since Map can't have duplicate keys
// Using a modified path with a special prefix to ensure uniqueness
map.set(`${template.sourcePath}.__roo_copy__`, rooTargetPath);
if (this.verbose) {
Feedback.info(`Special mapping: ${template.relativePath} -> .mcp.json and .roo/mcp.json`);
}
}
else {
map.set(template.sourcePath, template.targetPath);
}
}
return map;
}
/**
* Ensure the .roo/mcp.json file is properly created
*
* @returns Promise resolving to true if setup was successful
*/
async ensureMcpJson() {
try {
// Find the mcp.json template
const mcpTemplate = this.templates.find(t => t.relativePath === '.roo/mcp.json');
if (!mcpTemplate) {
Feedback.warning('mcp.json template not found. Skipping.');
return false;
}
// Read the template content
const content = await fs.readFile(mcpTemplate.sourcePath, 'utf8');
// Create .mcp.json in the root
const rootMcpPath = path.join(this.targetDir, '.mcp.json');
await fs.ensureFile(rootMcpPath);
await fs.writeFile(rootMcpPath, content);
Feedback.success(`Created .mcp.json at ${rootMcpPath}`);
// Create .roo/mcp.json in the project root
const rooDir = path.join(this.targetDir, '.roo');
const rooMcpPath = path.join(rooDir, 'mcp.json');
await fs.ensureDir(rooDir);
await fs.writeFile(rooMcpPath, content);
Feedback.success(`Created .roo/mcp.json at ${rooMcpPath}`);
return true;
}
catch (error) {
Feedback.error(`Failed to create mcp.json: ${error instanceof Error ? error.message : String(error)}`);
return false;
}
}
/**
* Get templates filtered by category
*
* @param category Category to filter by
* @returns Filtered templates
*/
getTemplatesByCategory(category) {
return this.templates.filter(template => template.category === category);
}
/**
* Categorize a template file based on its path
*
* @param relativePath Relative path of the template file
* @returns Template category
*/
categorizeTemplate(relativePath) {
const lowerPath = relativePath.toLowerCase();
// Configuration files
if (lowerPath.startsWith('.roo/') ||
lowerPath.startsWith('.claude/') ||
lowerPath.startsWith('.github/') ||
lowerPath.startsWith('.husky/') ||
lowerPath.endsWith('.json') ||
lowerPath.endsWith('.yml') ||
lowerPath.endsWith('.yaml') ||
lowerPath.endsWith('.config.js') ||
lowerPath.endsWith('.rc') ||
lowerPath.includes('.config')) {
return TemplateCategory.CONFIG;
}
// Documentation files
if (lowerPath.endsWith('.md') ||
lowerPath.endsWith('.txt') ||
lowerPath.endsWith('.docs')) {
return TemplateCategory.DOCUMENTATION;
}
// Hook files
if (lowerPath.includes('hook') ||
lowerPath.includes('commit') ||
lowerPath.includes('pre-') ||
lowerPath.includes('post-')) {
return TemplateCategory.HOOK;
}
// Template files
if (lowerPath.includes('template') || lowerPath.includes('scaffold')) {
return TemplateCategory.TEMPLATE;
}
// Tool files
if (lowerPath.endsWith('.sh') ||
lowerPath.endsWith('.js') ||
lowerPath.endsWith('.ts') ||
lowerPath.includes('script') ||
lowerPath.includes('tool')) {
return TemplateCategory.TOOL;
}
// Default to OTHER
return TemplateCategory.OTHER;
}
}
//# sourceMappingURL=template-scanner.js.map