UNPKG

dna-template-cli

Version:

DNA Template CLI v0.3.4 - Enhanced Commands Added (enhanced-create, enhanced-list, enhanced-validate)

300 lines 11.9 kB
"use strict"; /** * @fileoverview Template Registry - Manages available templates */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateRegistry = void 0; const tslib_1 = require("tslib"); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const path_1 = tslib_1.__importDefault(require("path")); const fuse_js_1 = tslib_1.__importDefault(require("fuse.js")); const logger_1 = require("../utils/logger"); class TemplateRegistry { constructor() { this.templates = []; this.lastUpdateTime = 0; this.fuseInstance = null; // In development, use local templates directory // In production, this would be a user cache directory this.registryPath = path_1.default.resolve(process.cwd(), 'templates'); } async load() { try { await this.loadLocalTemplates(); this.sortTemplates(); } catch (error) { logger_1.logger.debug('Failed to load template registry:', error); // Initialize with empty registry if load fails this.templates = []; } } async loadLocalTemplates() { if (!await fs_extra_1.default.pathExists(this.registryPath)) { logger_1.logger.debug('Templates directory not found'); this.templates = []; return; } const categories = await fs_extra_1.default.readdir(this.registryPath); this.templates = []; for (const category of categories) { const categoryPath = path_1.default.join(this.registryPath, category); const stat = await fs_extra_1.default.stat(categoryPath); if (!stat.isDirectory()) continue; const templates = await fs_extra_1.default.readdir(categoryPath); for (const templateDir of templates) { const templatePath = path_1.default.join(categoryPath, templateDir); const templateStat = await fs_extra_1.default.stat(templatePath); if (!templateStat.isDirectory()) continue; try { const template = await this.loadTemplate(templatePath, category); if (template) { this.templates.push(template); } } catch (error) { logger_1.logger.debug(`Failed to load template ${templateDir}:`, error); } } } } async loadTemplate(templatePath, category) { const metadataPath = path_1.default.join(templatePath, 'template.json'); if (!await fs_extra_1.default.pathExists(metadataPath)) { // Generate metadata from directory structure if template.json doesn't exist return this.generateTemplateMetadata(templatePath, category); } try { const metadata = await fs_extra_1.default.readJSON(metadataPath); return this.validateAndNormalizeMetadata(metadata, templatePath); } catch (error) { logger_1.logger.debug(`Invalid template.json in ${templatePath}:`, error); return null; } } generateTemplateMetadata(templatePath, category) { const templateName = path_1.default.basename(templatePath); return { id: `${category}-${templateName}`, name: templateName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), description: `A ${category} template for ${templateName}`, type: category, // This would need proper type mapping framework: 'nextjs', // Default framework version: '1.0.0', author: 'DNA Templates', tags: [category], dnaModules: [], requirements: {}, features: [], complexity: 'intermediate', estimatedSetupTime: 5, lastUpdated: new Date(), }; } validateAndNormalizeMetadata(metadata, templatePath) { // Validate required fields const required = ['name', 'description', 'type', 'framework', 'version']; for (const field of required) { if (!metadata[field]) { throw new Error(`Missing required field: ${field}`); } } // Normalize and set defaults return { id: metadata.id || path_1.default.basename(templatePath), name: metadata.name, description: metadata.description, type: metadata.type, framework: metadata.framework, version: metadata.version, author: metadata.author || 'Unknown', tags: metadata.tags || [], dnaModules: metadata.dnaModules || [], requirements: metadata.requirements || {}, features: metadata.features || [], complexity: metadata.complexity || 'intermediate', estimatedSetupTime: metadata.estimatedSetupTime || 5, lastUpdated: new Date(metadata.lastUpdated || Date.now()), downloadCount: metadata.downloadCount || undefined, rating: metadata.rating || undefined, variables: metadata.variables || [], }; } sortTemplates() { this.templates.sort((a, b) => { // Sort by rating first (if available), then by name if (a.rating && b.rating) { return b.rating - a.rating; } if (a.rating && !b.rating) return -1; if (!a.rating && b.rating) return 1; return a.name.localeCompare(b.name); }); // Initialize Fuse.js for fuzzy search this.initializeFuzzySearch(); } initializeFuzzySearch() { const fuseOptions = { keys: [ { name: 'name', weight: 0.3 }, { name: 'description', weight: 0.2 }, { name: 'tags', weight: 0.2 }, { name: 'features', weight: 0.15 }, { name: 'dnaModules', weight: 0.1 }, { name: 'framework', weight: 0.05 } ], threshold: 0.4, includeScore: true, includeMatches: true }; this.fuseInstance = new fuse_js_1.default(this.templates, fuseOptions); } getTemplates() { return [...this.templates]; } getTemplate(id) { return this.templates.find(t => t.id === id); } searchTemplates(query, useFuzzy = true) { if (!query.trim()) { return this.getTemplates(); } if (useFuzzy && this.fuseInstance) { const results = this.fuseInstance.search(query); return results.map(result => result.item); } // Fallback to basic search const lowercaseQuery = query.toLowerCase(); return this.templates.filter(template => template.name.toLowerCase().includes(lowercaseQuery) || template.description.toLowerCase().includes(lowercaseQuery) || template.tags.some(tag => tag.toLowerCase().includes(lowercaseQuery)) || template.features.some(feature => feature.toLowerCase().includes(lowercaseQuery))); } filterByFramework(framework) { return this.templates.filter(t => t.framework.toLowerCase() === framework.toLowerCase()); } filterByComplexity(complexity) { return this.templates.filter(t => t.complexity === complexity); } filterByType(type) { return this.templates.filter(t => t.type === type); } filterByDnaModule(dnaModule) { return this.templates.filter(t => t.dnaModules.includes(dnaModule)); } filterByTag(tag) { return this.templates.filter(t => t.tags.includes(tag)); } filterTemplates(options) { let filtered = [...this.templates]; if (options.framework) { filtered = filtered.filter(t => t.framework === options.framework); } if (options.type) { filtered = filtered.filter(t => t.type === options.type); } if (options.complexity) { filtered = filtered.filter(t => t.complexity === options.complexity); } if (options.dnaModules && options.dnaModules.length > 0) { filtered = filtered.filter(t => options.dnaModules.some(module => t.dnaModules.includes(module))); } if (options.tags && options.tags.length > 0) { filtered = filtered.filter(t => options.tags.some(tag => t.tags.includes(tag))); } if (options.maxSetupTime) { filtered = filtered.filter(t => t.estimatedSetupTime <= options.maxSetupTime); } if (options.minRating) { filtered = filtered.filter(t => t.rating && t.rating >= options.minRating); } if (options.query) { const searchResults = this.searchTemplates(options.query, true); const searchIds = new Set(searchResults.map(t => t.id)); filtered = filtered.filter(t => searchIds.has(t.id)); } return filtered; } getTemplatesByCategory() { const categories = {}; for (const template of this.templates) { const category = this.getCategoryFromType(template.type); if (!categories[category]) { categories[category] = []; } categories[category].push(template); } return categories; } getCategoryFromType(type) { switch (type) { case 'ai-saas': case 'ai-mobile': return 'AI Native'; case 'performance': return 'Performance'; case 'cross-platform': return 'Cross Platform'; default: return 'Foundation'; } } getAvailableFrameworks() { const frameworks = new Set(this.templates.map(t => t.framework)); return Array.from(frameworks); } getAvailableComplexities() { const complexities = new Set(this.templates.map(t => t.complexity)); return Array.from(complexities); } getAvailableDnaModules() { const modules = new Set(); this.templates.forEach(t => t.dnaModules.forEach(m => modules.add(m))); return Array.from(modules).sort(); } getAvailableTags() { const tags = new Set(); this.templates.forEach(t => t.tags.forEach(tag => tags.add(tag))); return Array.from(tags).sort(); } getRecommendedTemplates(limit = 5) { return this.templates .filter(t => t.rating && t.rating >= 4.0) .sort((a, b) => (b.rating || 0) - (a.rating || 0)) .slice(0, limit); } getPopularTemplates(limit = 5) { return this.templates .filter(t => t.downloadCount && t.downloadCount > 0) .sort((a, b) => (b.downloadCount || 0) - (a.downloadCount || 0)) .slice(0, limit); } async getLastUpdateTime() { return this.lastUpdateTime; } async update() { // In a real implementation, this would fetch from a remote registry // For now, we'll simulate an update const oldCount = this.templates.length; await this.load(); // Reload local templates const newCount = this.templates.length; this.lastUpdateTime = Date.now(); return { updated: true, newTemplates: Math.max(0, newCount - oldCount), updatedTemplates: 0, changes: [ 'Updated AI SaaS template with new LLM providers', 'Fixed Flutter template compatibility issues', 'Added new React Native business template', ], }; } } exports.TemplateRegistry = TemplateRegistry; //# sourceMappingURL=template-registry.js.map