import Handlebars from 'handlebars'; import { readFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Templates directory const TEMPLATES_DIR = join(__dirname, '..', '..', 'templates'); // Register Handlebars helpers Handlebars.registerHelper('if_eq', function(a, b, options) { return a === b ? options.fn(this) : options.inverse(this); }); Handlebars.registerHelper('if_includes', function(arr, value, options) { if (Array.isArray(arr) && arr.includes(value)) { return options.fn(this); } return options.inverse(this); }); Handlebars.registerHelper('join', function(arr, separator) { if (Array.isArray(arr)) { return arr.join(separator); } return ''; }); Handlebars.registerHelper('lowercase', function(str) { return str ? str.toLowerCase() : ''; }); Handlebars.registerHelper('sanitize', function(str) { // Convert to lowercase and replace invalid chars for Docker return str ? str.toLowerCase().replace(/[^a-z0-9-]/g, '-') : ''; }); /** * Load a template file from the templates directory */ export function loadTemplate(templatePath) { const fullPath = join(TEMPLATES_DIR, templatePath); if (!existsSync(fullPath)) { throw new Error(`Template not found: ${templatePath}`); } return readFileSync(fullPath, 'utf-8'); } /** * Render a template with the given context */ export function renderTemplate(templateContent, context) { const template = Handlebars.compile(templateContent, { noEscape: true }); return template(context); } /** * Load and render a template file */ export function processTemplate(templatePath, context) { const content = loadTemplate(templatePath); return renderTemplate(content, context); } /** * Get the template directory path for a given project type */ export function getTemplateDir(projectType) { // Map project types to template directories const typeMap = { 'nodejs-express': 'nodejs/express', 'nodejs-vite-react': 'nodejs/vite-react', 'nodejs-vite-react-ssr': 'nodejs/vite-react-ssr', 'nodejs-generic': 'nodejs/express', 'python-standard': 'python/standard', 'python-ml-pytorch': 'python/ml-pytorch', 'dotnet-blazor': 'dotnet/blazor', 'dotnet-webapi': 'dotnet/webapi', 'static-nginx': 'static/nginx', 'flutter-web': 'static/nginx' }; return typeMap[projectType] || 'nodejs/express'; } /** * Get all template files for a project type */ export function getTemplateFiles(projectType) { const templateDir = getTemplateDir(projectType); const files = [ { template: `${templateDir}/Dockerfile.template`, output: 'Dockerfile' }, { template: `${templateDir}/docker-compose.yml.template`, output: 'docker-compose.yml' }, { template: `${templateDir}/.dockerignore.template`, output: '.dockerignore' } ]; // Add nginx.conf for static sites if (projectType === 'static-nginx' || projectType === 'flutter-web') { files.push({ template: `${templateDir}/nginx.conf.template`, output: 'nginx.conf' }); } return files; } /** * Build template context from config */ export function buildTemplateContext(config, detection) { const projectName = config.project.name; const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-'); return { // Project info PROJECT_NAME: sanitizedName, PROJECT_NAME_RAW: projectName, PROJECT_TYPE: config.project.type, // Build settings NODE_VERSION: config.build.nodeVersion || '20', PYTHON_VERSION: config.build.pythonVersion || '3.11', DOTNET_VERSION: config.build.dotnetVersion || '9.0', PLATFORM: config.build.platform || 'linux/amd64', ENTRY_POINT: config.build.entryPoint || detection?.entryPoint || 'index.js', BUILD_COMMAND: config.build.buildCommand, // Runtime settings PORT: config.runtime.port || 3000, USE_ENV_FILE: config.runtime.envFile !== false, VOLUMES: config.runtime.volumes || [], HAS_VOLUMES: (config.runtime.volumes || []).length > 0, EXTRA_HOSTS: config.runtime.extraHosts || false, // Deployment settings SSH_HOST: config.deployment.sshHost || '', SSH_USER: config.deployment.sshUser || '', TARGET_PATH: config.deployment.targetPath || `~/containers/${sanitizedName}/files`, HAS_SSH: !!(config.deployment.sshHost && config.deployment.sshUser), // Detection info HAS_BUILD_COMMAND: !!detection?.buildCommand, IS_SSR: config.project.type === 'nodejs-vite-react-ssr', IS_STATIC: config.project.type === 'static-nginx' || config.project.type === 'flutter-web', // .NET specific CSPROJ_FILE: detection?.csprojFile || '', DLL_NAME: detection?.dllName || '', // Data directory (for projects that need persistence) DATA_DIR: detection?.dataDir || 'data' }; } export { TEMPLATES_DIR };