or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli-builder.mdcommand-system.mdfilesystem-tools.mdhttp-tools.mdindex.mdpackage-manager-tools.mdpatching-tools.mdprint-tools.mdprompt-tools.mdsemver-tools.mdstring-tools.mdsystem-tools.mdtemplate-tools.md
tile.json

template-tools.mddocs/

Template Generation

Template generation system using EJS for creating files from templates with variable substitution, conditional logic, and dynamic content generation.

Capabilities

Template 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
    }
  }
});

Template Syntax

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;

Template Directory Structure

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.ejs

Template 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 Template Features

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()
    }
  });
}

Template Directory Resolution

Gluegun automatically resolves template directories using a fallback system for maximum flexibility.

Template Directory Search Order:

  1. Custom Directory: If options.directory is provided, use the absolute path specified
  2. Plugin Templates: Look in {plugin.directory}/templates/
  3. Build Templates: Fallback to {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
});

EJS Template Context Variables

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
    });
  }
};

Complete Template Interface

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);
}