supernal-coding
Version:
Comprehensive development workflow CLI with kanban task management, project validation, git safety hooks, and cross-project distribution system
191 lines (162 loc) • 5.85 kB
JavaScript
const handlebars = require('handlebars');
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
/**
* Template engine for generating files from Handlebars templates
*/
class TemplateEngine {
constructor() {
this.templatesDir = path.join(__dirname);
this.setupHelpers();
}
/**
* Setup Handlebars helpers
*/
setupHelpers() {
// Date formatting helper
handlebars.registerHelper('formatDate', (date, format) => {
if (!date) date = new Date();
if (typeof date === 'string') date = new Date(date);
switch (format) {
case 'year':
return date.getFullYear();
case 'iso':
return date.toISOString();
default:
return date.toLocaleDateString();
}
});
// String transformation helpers
handlebars.registerHelper('lowercase', str => str.toLowerCase());
handlebars.registerHelper('uppercase', str => str.toUpperCase());
handlebars.registerHelper('camelCase', str =>
str.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
);
handlebars.registerHelper('kebabCase', str =>
str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
);
handlebars.registerHelper('pascalCase', str =>
str.replace(/(^|-)([a-z])/g, (g) => g.slice(-1).toUpperCase())
);
// Array helpers
handlebars.registerHelper('join', (array, separator = ', ') => {
if (!Array.isArray(array)) return '';
return array.join(separator);
});
handlebars.registerHelper('length', array => {
if (!Array.isArray(array)) return 0;
return array.length;
});
// Conditional helpers
handlebars.registerHelper('ifEquals', function(arg1, arg2, options) {
return (arg1 == arg2) ? options.fn(this) : options.inverse(this);
});
handlebars.registerHelper('ifContains', function(array, value, options) {
if (!Array.isArray(array)) return options.inverse(this);
return array.includes(value) ? options.fn(this) : options.inverse(this);
});
// Package.json helpers
handlebars.registerHelper('dependenciesObject', function(dependencies) {
if (!Array.isArray(dependencies)) return '{}';
const deps = {};
dependencies.forEach(dep => {
if (typeof dep === 'string') {
deps[dep] = '^1.0.0'; // Default version
} else if (dep.name) {
deps[dep.name] = dep.version || '^1.0.0';
}
});
return JSON.stringify(deps, null, 2).split('\n').slice(1, -1).join('\n');
});
// Scripts helpers
handlebars.registerHelper('scriptsObject', function(scripts) {
if (!scripts || typeof scripts !== 'object') return '{}';
return JSON.stringify(scripts, null, 2).split('\n').slice(1, -1).join('\n');
});
}
/**
* Generate a file from template
*/
async generateTemplate(templatePath, variables = {}) {
try {
const fullTemplatePath = path.join(this.templatesDir, templatePath);
if (!await fs.pathExists(fullTemplatePath)) {
throw new Error(`Template not found: ${templatePath}`);
}
const templateContent = await fs.readFile(fullTemplatePath, 'utf8');
const template = handlebars.compile(templateContent);
return template(variables);
} catch (error) {
console.error(chalk.red(`❌ Template generation failed for ${templatePath}:`), error.message);
throw error;
}
}
/**
* Generate and write template to file
*/
async generateTemplateToFile(templatePath, outputPath, variables = {}) {
try {
const content = await this.generateTemplate(templatePath, variables);
await fs.ensureDir(path.dirname(outputPath));
await fs.writeFile(outputPath, content);
return content;
} catch (error) {
console.error(chalk.red(`❌ Failed to generate ${templatePath} to ${outputPath}:`), error.message);
throw error;
}
}
/**
* Generate multiple templates
*/
async generateTemplates(templates, variables = {}) {
const results = {};
for (const [name, templatePath] of Object.entries(templates)) {
try {
results[name] = await this.generateTemplate(templatePath, variables);
} catch (error) {
console.error(chalk.red(`❌ Failed to generate template ${name} (${templatePath}):`), error.message);
throw error;
}
}
return results;
}
/**
* Check if template exists
*/
async templateExists(templatePath) {
const fullPath = path.join(this.templatesDir, templatePath);
return await fs.pathExists(fullPath);
}
/**
* List available templates
*/
async listTemplates(directory = '') {
const searchDir = path.join(this.templatesDir, directory);
if (!await fs.pathExists(searchDir)) {
return [];
}
const files = await fs.readdir(searchDir, { withFileTypes: true });
const templates = [];
for (const file of files) {
if (file.isFile() && file.name.endsWith('.hbs')) {
templates.push(path.join(directory, file.name));
} else if (file.isDirectory()) {
const subTemplates = await this.listTemplates(path.join(directory, file.name));
templates.push(...subTemplates);
}
}
return templates;
}
}
// Create singleton instance
const templateEngine = new TemplateEngine();
// Export main functions
module.exports = {
generateTemplate: templateEngine.generateTemplate.bind(templateEngine),
generateTemplateToFile: templateEngine.generateTemplateToFile.bind(templateEngine),
generateTemplates: templateEngine.generateTemplates.bind(templateEngine),
templateExists: templateEngine.templateExists.bind(templateEngine),
listTemplates: templateEngine.listTemplates.bind(templateEngine),
engine: templateEngine
};