import path from 'path'; import { __logger } from '../logger/internal-logger'; import { CBFileSystem } from '../node/file-system'; /** * Template service for managing templates across different CLI packages */ export class TemplateService { constructor( private fileSystem: CBFileSystem, private templatesDir: string ) { } /** * Validates template exists * @param {string} template - Template name to validate * @returns {boolean} True if template is valid */ validateTemplate(template: string): boolean { const availableTemplates = this.getAvailableTemplates(); return availableTemplates.includes(template); } /** * Validates app directory for conflicts * @param {string} targetDir - Target directory path * @param {string} appDir - Original app directory argument * @param {string} template - Template name * @returns {string[]} Array of conflicting files (empty if no conflicts) */ validateAppDirectory(targetDir: string, appDir: string, template: string): string[] { if (!this.fileSystem.existsSync(targetDir)) { return []; } if (appDir === '.') { // When using current directory, check for conflicting files const templatePath = path.join(this.templatesDir, template); const templateFiles = this.getTemplateFiles(templatePath); const existingFiles = this.fileSystem.readdirSync(targetDir); return templateFiles.filter(file => existingFiles.includes(file)); } else { // For other directories, check if not empty const files = this.fileSystem.readdirSync(targetDir); if (files.length > 0) { throw new Error(`Directory '${targetDir}' already exists and is not empty.`); } return []; } } /** * Gets a list of available templates from the templates directory * @returns {string[]} Array of template names */ getAvailableTemplates(): string[] { if (!this.fileSystem.existsSync(this.templatesDir)) { __logger.warn(`Templates directory not found: ${this.templatesDir}`); return []; } const templates = this.fileSystem.readdirSync(this.templatesDir, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); return templates; } /** * Gets the full path to a specific template * @param {string} template - Template name * @returns {string} Full path to the template directory */ getTemplatePath(template: string): string { return path.join(this.templatesDir, template); } /** * Gets the templates directory path * @returns {string} Path to the templates directory */ getTemplatesDirectory(): string { return this.templatesDir; } /** * Recursively collects all files from a template directory * @param {string} templatePath - Path to the template directory * @returns {string[]} Array of file paths relative to template root */ getTemplateFiles(templatePath: string): string[] { const files: string[] = []; const collectFiles = (dir: string, basePath: string = ''): void => { const items = this.fileSystem.readdirSync(dir); for (const item of items) { const sourcePath = path.join(dir, item); const relativePath = path.join(basePath, item); const stat = this.fileSystem.statSync(sourcePath); if (stat.isDirectory()) { collectFiles(sourcePath, relativePath); } else { files.push(relativePath); } } }; collectFiles(templatePath); return files; } /** * Recursively copies all files from template directory to target directory * @param {string} templatePath - Source template directory path * @param {string} targetPath - Target directory path */ copyTemplateFiles(templatePath: string, targetPath: string): void { const items = this.fileSystem.readdirSync(templatePath); for (const item of items) { const sourcePath = path.join(templatePath, item); const destPath = path.join(targetPath, item); const stat = this.fileSystem.statSync(sourcePath); if (stat.isDirectory()) { this.fileSystem.mkdirSync(destPath, { recursive: true }); this.copyTemplateFiles(sourcePath, destPath); } else { this.fileSystem.copyFileSync(sourcePath, destPath); } } } /** * Prints a tree-like structure of the project directory * @param {string} dir - Directory path to print structure for * @param {string} [indent=''] - Current indentation level */ printProjectStructure(dir: string, indent: string = ''): void { const items = this.fileSystem.readdirSync(dir); for (let i = 0; i < items.length; i++) { const item = items[i]; const itemPath = path.join(dir, item || ''); const stat = this.fileSystem.statSync(itemPath); const isDirectory = stat.isDirectory(); const isLastItem = i === items.length - 1; // Tree characters const treeChar = isLastItem ? '└── ' : '├── '; const nextIndent = isLastItem ? ' ' : '│ '; // File/directory name const name = isDirectory ? item + '/' : item; const coloredName = name; __logger.info(`${indent}${treeChar}${coloredName}`); if (isDirectory) { this.printProjectStructure(itemPath, indent + nextIndent); } } } }