Template generation system using EJS for creating files from templates with variable substitution, conditional logic, and dynamic content generation.
Core template generation functionality for creating files from EJS templates with props and configuration options. The template system provides automatic template directory resolution, string utilities integration, and comprehensive context variables for advanced template rendering.
/**
* Generate file from template with variable substitution
* @param options - Template generation configuration
* @returns Promise resolving to generated file path
*/
generate(options: GluegunTemplateGenerateOptions): Promise<string>;
interface GluegunTemplateGenerateOptions {
/** Template file path (relative to plugin's template directory) */
template: string;
/** Target file path (relative to current working directory) */
target?: string;
/** Template variables and data for substitution */
props?: { [name: string]: any };
/** Absolute template directory override */
directory?: string;
}Template Generation Examples:
import { template } from "gluegun";
// Basic template generation
await template.generate({
template: "component.js.ejs",
target: "src/components/UserCard.js",
props: {
name: "UserCard",
author: "John Doe"
}
});
// Generate with custom template directory
await template.generate({
template: "api-route.ts.ejs",
target: "src/api/users.ts",
directory: "/path/to/custom/templates",
props: {
routeName: "users",
methods: ["GET", "POST", "PUT", "DELETE"]
}
});
// Generate configuration file
await template.generate({
template: "config.json.ejs",
target: "config/production.json",
props: {
environment: "production",
database: {
host: "prod-db.example.com",
port: 5432
},
features: {
analytics: true,
debugging: false
}
}
});Templates use EJS syntax for variable substitution, conditional logic, and loops.
Variable Substitution:
<!-- component.js.ejs -->
import React from 'react';
interface <%= name %>Props {
id: string;
<% if (withStyles) { %>className?: string;<% } %>
}
const <%= name %>: React.FC<<%= name %>Props> = ({ id<% if (withStyles) { %>, className<% } %> }) => {
return (
<div<% if (withStyles) { %> className={className}<% } %>>
<h1>Hello from <%= name %>!</h1>
<p>Created by <%= author %></p>
</div>
);
};
export default <%= name %>;Conditional Logic:
<!-- package.json.ejs -->
{
"name": "<%= projectName %>",
"version": "1.0.0",
"description": "<%= description %>",
"scripts": {
"start": "node index.js",
<% if (useTypeScript) { %>"build": "tsc",<% } %>
<% if (includeLinting) { %>"lint": "eslint src/",<% } %>
<% if (includeTests) { %>"test": "jest",<% } %>
"dev": "<% if (useTypeScript) { %>ts-node<% } else { %>node<% } %> index.<% if (useTypeScript) { %>ts<% } else { %>js<% } %>"
},
"dependencies": {
<% if (useExpress) { %>"express": "^4.18.0",<% } %>
<% if (useDatabase) { %>"mongoose": "^6.0.0",<% } %>
"dotenv": "^16.0.0"
},
"devDependencies": {
<% if (useTypeScript) { %>"typescript": "^4.8.0",
"@types/node": "^18.0.0",<% } %>
<% if (includeLinting) { %>"eslint": "^8.0.0",<% } %>
<% if (includeTests) { %>"jest": "^29.0.0",<% } %>
"nodemon": "^2.0.0"
}
}Loops and Arrays:
<!-- routes.js.ejs -->
const express = require('express');
const router = express.Router();
<% routes.forEach(route => { %>
// <%= route.description %>
router.<%= route.method.toLowerCase() %>('<%= route.path %>', (req, res) => {
<% if (route.auth) { %>// Authentication required<% } %>
res.json({ message: '<%= route.response %>' });
});
<% }); %>
module.exports = router;Templates are organized in directory structures within plugins or CLI projects.
Standard Template Directory:
my-cli/
├── src/
│ └── commands/
│ └── generate.js
└── templates/
├── component/
│ ├── index.js.ejs
│ ├── component.test.js.ejs
│ └── component.css.ejs
├── api/
│ ├── route.js.ejs
│ └── middleware.js.ejs
└── config/
├── package.json.ejs
└── webpack.config.js.ejsTemplate Usage in Commands:
// In generate command
export = {
name: "generate",
alias: "g",
run: async (toolbox) => {
const { template, parameters, strings, print } = toolbox;
const type = parameters.first; // component, api, config
const name = parameters.second; // ComponentName, routeName, etc.
if (!type || !name) {
print.error("Usage: my-cli generate <type> <name>");
return;
}
const props = {
name,
camelName: strings.camelCase(name),
pascalName: strings.pascalCase(name),
kebabName: strings.kebabCase(name),
author: "CLI User",
timestamp: new Date().toISOString()
};
switch (type) {
case "component":
await template.generate({
template: "component/index.js.ejs",
target: `src/components/${props.pascalName}/index.js`,
props
});
await template.generate({
template: "component/component.test.js.ejs",
target: `src/components/${props.pascalName}/${props.pascalName}.test.js`,
props
});
break;
case "api":
await template.generate({
template: "api/route.js.ejs",
target: `src/api/${props.kebabName}.js`,
props: {
...props,
methods: ["GET", "POST", "PUT", "DELETE"]
}
});
break;
}
print.success(`Generated ${type}: ${name}`);
}
};Advanced templating capabilities for complex generation scenarios.
Dynamic Template Selection:
// Dynamic template based on configuration
const templateFile = useTypeScript ? "component.tsx.ejs" : "component.jsx.ejs";
await template.generate({
template: `components/${templateFile}`,
target: `src/components/${name}.${useTypeScript ? 'tsx' : 'jsx'}`,
props: { name, useTypeScript }
});Template Inheritance and Includes:
<!-- base.js.ejs -->
/**
* Generated by <%= cliName %>
* Date: <%= timestamp %>
* Author: <%= author %>
*/
<%- include('header', { title: name }) %>
<% if (includeImports) { %>
<%- include('imports', { modules: imports }) %>
<% } %>
// Main content
<%- content %>
<%- include('footer') %>Multi-file Generation:
// Generate multiple related files
const files = [
{ template: "component/index.js.ejs", target: `${dir}/index.js` },
{ template: "component/styles.css.ejs", target: `${dir}/styles.css` },
{ template: "component/test.js.ejs", target: `${dir}/${name}.test.js` }
];
for (const file of files) {
await template.generate({
template: file.template,
target: file.target,
props: {
name,
className: strings.kebabCase(name),
testId: strings.snakeCase(name).toUpperCase()
}
});
}Gluegun automatically resolves template directories using a fallback system for maximum flexibility.
Template Directory Search Order:
options.directory is provided, use the absolute path specified{plugin.directory}/templates/{plugin.directory}/build/templates/ (for compiled plugins)Directory Resolution Examples:
// Plugin structure:
// my-plugin/
// ├── templates/ <- First choice
// │ └── component.js.ejs
// └── build/templates/ <- Fallback if templates/ doesn't exist
// └── component.js.ejs
// Use default resolution
await template.generate({
template: "component.js.ejs", // Looks in templates/ then build/templates/
target: "src/MyComponent.js"
});
// Override with custom directory
await template.generate({
template: "component.js.ejs",
target: "src/MyComponent.js",
directory: "/absolute/path/to/custom/templates" // Use this directory instead
});Templates receive a rich context with built-in variables and utility functions for advanced template generation.
Automatic Context Variables:
// Available in all templates without props
{
// CLI configuration and runtime data
config: toolbox.config, // CLI configuration object
parameters: toolbox.parameters, // Command line parameters
filename: "/path/to/template.ejs", // Current template file path
// User-provided template data
props: { /* your template props */ },
// String utility functions (from Gluegun strings toolbox)
camelCase: (str) => string, // Convert to camelCase
kebabCase: (str) => string, // Convert to kebab-case
snakeCase: (str) => string, // Convert to snake_case
pascalCase: (str) => string, // Convert to PascalCase
pluralize: (word, count?) => string, // Pluralize words
trim: (str, chars?) => string, // Trim whitespace/characters
pad: (str, length, chars?) => string, // Pad strings
// ... all other string utility functions
}Context Usage in Templates:
<!-- Using built-in context variables -->
/**
* Generated by <%= config.name || 'Gluegun CLI' %>
* Command: <%= parameters.command %>
* Template: <%= filename %>
* Date: <%= new Date().toISOString() %>
*/
// Using string utilities directly in templates
const <%= camelCase(props.name) %> = {
className: '<%= kebabCase(props.name) %>',
displayName: '<%= pascalCase(props.name) %>',
id: '<%= snakeCase(props.name).toUpperCase() %>',
<% if (parameters.options.plural) { %>
plural: '<%= pluralize(props.name) %>',
<% } %>
};
// Accessing CLI parameters and options
<% if (parameters.options.verbose) { %>
console.log('Verbose mode enabled');
<% } %>
// Using configuration values
const apiUrl = '<%= config.apiUrl || "http://localhost:3000" %>';Advanced Context Manipulation:
// In your command, enhance template context
export = {
name: "generate",
run: async (toolbox) => {
const { template, strings, filesystem } = toolbox;
// Custom template props with computed values
const props = {
name: "UserService",
methods: ["create", "read", "update", "delete"],
// Pre-compute variations using string utilities
serviceName: strings.pascalCase("UserService"),
fileName: strings.kebabCase("UserService"),
constantName: strings.snakeCase("UserService").toUpperCase(),
// Add helpful utilities
hasMethod: (method) => props.methods.includes(method),
getMethodName: (method) => strings.camelCase(`${props.name}_${method}`),
// Include file system helpers
relativePath: (target) => filesystem.path(target),
directoryExists: (dir) => filesystem.isDirectory(dir)
};
await template.generate({
template: "service.ts.ejs",
target: `src/services/${props.fileName}.ts`,
props
});
}
};interface GluegunTemplate {
/**
* Generate file from template
* @param options - Generation configuration
* @returns Generated file path
*/
generate(options: GluegunTemplateGenerateOptions): Promise<string>;
}
interface GluegunTemplateGenerateOptions {
/** Template file path relative to plugin template directory */
template: string;
/** Target file path relative to current working directory */
target?: string;
/** Template variables for substitution */
props?: { [name: string]: any };
/** Absolute template directory path override */
directory?: string;
}Comprehensive Usage Example:
// Advanced project generator command
export = {
name: "create-project",
description: "Create a full project from templates",
run: async (toolbox) => {
const { template, prompt, filesystem, strings, print, system } = toolbox;
// Get project configuration
const config = await prompt.ask([
{
type: 'input',
name: 'name',
message: 'Project name:',
validate: (v) => v.length > 0
},
{
type: 'select',
name: 'framework',
message: 'Framework:',
choices: ['react', 'vue', 'vanilla']
},
{
type: 'confirm',
name: 'typescript',
message: 'Use TypeScript?'
}
]);
const projectDir = strings.kebabCase(config.name);
const ext = config.typescript ? 'ts' : 'js';
const jsxExt = config.typescript ? 'tsx' : 'jsx';
// Shared template props
const baseProps = {
projectName: config.name,
projectDirName: projectDir,
framework: config.framework,
useTypeScript: config.typescript,
author: "Project Generator",
year: new Date().getFullYear(),
timestamp: new Date().toISOString()
};
print.info(`Creating ${config.framework} project: ${config.name}`);
// Generate package.json
await template.generate({
template: `package.json.ejs`,
target: `${projectDir}/package.json`,
props: {
...baseProps,
dependencies: getDependencies(config),
devDependencies: getDevDependencies(config),
scripts: getScripts(config)
}
});
// Generate main application files
if (config.framework === 'react') {
await template.generate({
template: `frameworks/react/App.${jsxExt}.ejs`,
target: `${projectDir}/src/App.${jsxExt}`,
props: baseProps
});
await template.generate({
template: `frameworks/react/index.${ext}.ejs`,
target: `${projectDir}/src/index.${ext}`,
props: baseProps
});
}
// Generate configuration files
if (config.typescript) {
await template.generate({
template: 'config/tsconfig.json.ejs',
target: `${projectDir}/tsconfig.json`,
props: baseProps
});
}
await template.generate({
template: 'config/gitignore.ejs',
target: `${projectDir}/.gitignore`,
props: baseProps
});
// Generate README
await template.generate({
template: 'README.md.ejs',
target: `${projectDir}/README.md`,
props: {
...baseProps,
setupSteps: getSetupSteps(config),
availableScripts: Object.keys(getScripts(config))
}
});
print.success(`✅ Project created in ${projectDir}/`);
print.info(`
Next steps:
cd ${projectDir}
npm install
npm run dev
`);
}
};
function getDependencies(config) {
const deps = {};
if (config.framework === 'react') {
deps.react = '^18.2.0';
deps['react-dom'] = '^18.2.0';
}
return deps;
}
function getDevDependencies(config) {
const devDeps = {};
if (config.typescript) {
devDeps.typescript = '^4.8.0';
devDeps['@types/node'] = '^18.0.0';
if (config.framework === 'react') {
devDeps['@types/react'] = '^18.0.0';
devDeps['@types/react-dom'] = '^18.0.0';
}
}
return devDeps;
}
function getScripts(config) {
return {
start: config.framework === 'react' ? 'react-scripts start' : 'node src/index.js',
build: config.framework === 'react' ? 'react-scripts build' : 'echo "Build not configured"',
test: 'jest',
dev: 'npm start'
};
}
function getSetupSteps(config) {
return [
'Install dependencies: `npm install`',
'Start development server: `npm run dev`',
config.typescript ? 'TypeScript compilation will run automatically' : null,
'Open http://localhost:3000 in your browser'
].filter(Boolean);
}