appgen-engine
v2.0.0
Published
Core generation engine for App Generator Platform
Maintainers
Readme
appgen-engine
Core generation engine for AppGen - Powerful, extensible project scaffolding
The engine package provides the core functionality for template processing, file generation, and project scaffolding in AppGen. It's designed to be used programmatically in CLI tools, build systems, or custom automation.
🎯 Features
- 🎯 Multi-Source Templates - Support for local, remote, and GitHub templates
- 📝 Manifest Validation - JSON Schema-based template validation
- 🎨 Template Rendering - Handlebars-powered variable substitution
- ❓ Interactive Prompts - Built-in prompt system for user input
- 🪝 Lifecycle Hooks - Pre/post-generation and validation hooks
- 🔌 Plugin System - Extensible architecture for custom functionality
- 🔄 Migration Support - Upgrade existing projects to new versions
- 🔒 Security Sandbox - Safe execution environment for untrusted code
- 📦 Dependency Management - Smart dependency merging and conflict resolution
- 🧪 Testing Utilities - Comprehensive testing helpers
📦 Installation
npm install appgen-engine
# or
yarn add appgen-engine
# or
pnpm add appgen-engine🚀 Quick Start
Basic Project Generation
import { Engine } from 'appgen-engine';
// Create engine instance
const engine = new Engine();
// Generate a project
const result = await engine.generate({
template: 'react-tailwind',
destination: './my-project',
answers: {
projectName: 'my-project',
description: 'My awesome project',
useTypescript: true,
useRouter: true,
},
});
if (result.success) {
console.log(`✅ Generated ${result.filesGenerated} files`);
} else {
console.error(`❌ Generation failed: ${result.error}`);
}Using Local Templates
const result = await engine.generate({
template: './my-custom-template',
destination: './output',
});Using GitHub Templates
const result = await engine.generate({
template: 'github:username/repo',
destination: './output',
});📖 API Reference
Engine
The main orchestrator for project generation.
class Engine {
constructor(options?: EngineOptions);
// Generate a new project
async generate(options: GenerateOptions): Promise<GenerateResult>;
// Check for available upgrades
async checkUpgrade(options: CheckUpgradeOptions): Promise<UpgradeInfo>;
// Upgrade existing project
async upgrade(options: UpgradeOptions): Promise<UpgradeResult>;
}GenerateOptions
interface GenerateOptions {
// Template source (name, path, or GitHub URL)
template: string;
// Destination directory
destination: string;
// Pre-filled answers (skips prompts)
answers?: Record<string, any>;
// Plugins to apply
plugins?: string[];
// Skip lifecycle hooks
skipHooks?: boolean;
// Skip git initialization
skipGit?: boolean;
// Skip dependency installation
skipInstall?: boolean;
// Dry run mode (don't write files)
dryRun?: boolean;
// Verbose output
verbose?: boolean;
// Custom logger
logger?: Logger;
}GenerateResult
interface GenerateResult {
success: boolean;
template?: string;
destination?: string;
filesGenerated?: number;
pluginsApplied?: string[];
error?: string;
warnings?: string[];
}TemplateResolver
Resolves templates from various sources.
import { TemplateResolver } from 'appgen-engine';
const resolver = new TemplateResolver();
// Resolve template
const template = await resolver.resolve('react-tailwind');
// Get template metadata
console.log(template.name, template.version);
// Access template files
const files = await template.getFiles();Renderer
Renders template files with variable substitution.
import { Renderer } from 'appgen-engine';
const renderer = new Renderer();
const result = await renderer.render({
templatePath: './template',
destination: './output',
variables: {
projectName: 'my-app',
author: 'John Doe',
},
});
console.log(`Rendered ${result.length} files`);ManifestLoader
Loads and validates template manifests.
import { ManifestLoader } from 'appgen-engine';
const loader = new ManifestLoader();
// Load manifest
const manifest = await loader.load('./template/manifest.json');
// Validate manifest
await loader.validate(manifest);
// Access manifest data
console.log(manifest.name, manifest.version);
console.log(manifest.prompts);PluginManager
Manages and applies plugins.
import { PluginManager } from 'appgen-engine';
const manager = new PluginManager();
// Register plugin
manager.register('my-plugin', myPluginInstance);
// Apply plugin
await manager.apply('my-plugin', {
project: projectContext,
answers: userAnswers,
});
// Apply multiple plugins
await manager.applyAll(['eslint', 'prettier', 'docker'], context);🎨 Template System
Manifest Structure
Templates are defined by a manifest.json file:
{
"name": "react-tailwind",
"version": "1.0.0",
"description": "React with Tailwind CSS",
"engine": "^1.0.0",
"category": "frontend",
"tags": ["react", "tailwind", "typescript"],
"prompts": [
{
"type": "input",
"name": "projectName",
"message": "Project name?",
"default": "my-app"
},
{
"type": "confirm",
"name": "useTypescript",
"message": "Use TypeScript?",
"default": true
}
],
"hooks": {
"pre-generate": "./hooks/pre-generate.js",
"post-generate": "./hooks/post-generate.js",
"validate": "./hooks/validate.js"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^5.0.0",
"typescript": "^5.0.0"
}
}Variable Substitution
Templates support Handlebars syntax:
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>{{projectName}}</title>
</head>
<body>
{{#if useTypescript}}
<script src="./main.ts"></script>
{{else}}
<script src="./main.js"></script>
{{/if}}
</body>
</html>Conditional Files
Control file inclusion with .appgenignore:
# Ignore TypeScript files if not using TypeScript
{{#unless useTypescript}}
*.ts
*.tsx
tsconfig.json
{{/unless}}🪝 Lifecycle Hooks
Pre-Generate Hook
Runs before file generation:
// hooks/pre-generate.js
export default async function(context) {
const { answers, logger } = context;
// Validate answers
if (!answers.projectName) {
throw new Error('Project name is required');
}
// Modify answers
answers.projectNameKebab = answers.projectName
.toLowerCase()
.replace(/\s+/g, '-');
logger.info('Pre-generate hook completed');
}Post-Generate Hook
Runs after file generation:
// hooks/post-generate.js
export default async function(context) {
const { project, answers, logger, exec } = context;
// Install dependencies
if (!context.skipInstall) {
await exec('npm install', { cwd: project.path });
}
// Initialize git
if (!context.skipGit) {
await exec('git init', { cwd: project.path });
}
logger.success('Project ready!');
}Validate Hook
Runs to validate the generated project:
// hooks/validate.js
export default async function(context) {
const { project, logger } = context;
// Check required files exist
const requiredFiles = ['package.json', 'src/index.js'];
for (const file of requiredFiles) {
const exists = await project.fileExists(file);
if (!exists) {
throw new Error(`Missing required file: ${file}`);
}
}
logger.success('Validation passed');
}🔌 Plugin Development
Creating a Plugin
import type { Plugin, PluginContext } from 'appgen-engine';
export default class MyPlugin implements Plugin {
name = 'my-plugin';
version = '1.0.0';
async apply(context: PluginContext) {
const { project, answers, logger } = context;
// Add dependencies
project.addDependency('my-package', '^1.0.0');
// Create files
await project.writeFile('config.json', {
setting: answers.mySetting,
});
// Modify files
const pkg = await project.readJSON('package.json');
pkg.scripts.custom = 'my-script';
await project.writeJSON('package.json', pkg);
logger.success('Plugin applied');
}
}Using Custom Plugins
import { Engine } from 'appgen-engine';
import MyPlugin from './my-plugin';
const engine = new Engine({
plugins: {
'my-plugin': new MyPlugin(),
},
});
await engine.generate({
template: 'react-tailwind',
destination: './output',
plugins: ['my-plugin'],
});🧪 Testing
AppGen Engine provides testing utilities:
import { createMockContext, createTestEngine } from 'appgen-engine/testing';
describe('MyPlugin', () => {
it('applies successfully', async () => {
const context = createMockContext({
answers: { projectName: 'test' },
});
const plugin = new MyPlugin();
await plugin.apply(context);
expect(context.project.dependencies).toHaveProperty('my-package');
});
it('generates project', async () => {
const engine = createTestEngine();
const result = await engine.generate({
template: 'test-template',
destination: '/tmp/test',
});
expect(result.success).toBe(true);
expect(result.filesGenerated).toBeGreaterThan(0);
});
});🔐 Security
Sandboxed Hook Execution
Hooks run in a restricted environment:
// Hooks cannot access:
// - File system outside project
// - Network (except allowed domains)
// - Environment variables
// - Child processes (except allowed commands)
export default async function(context) {
// ✅ Allowed
await context.exec('npm install');
// ❌ Blocked
await context.exec('rm -rf /');
}Template Validation
All templates are validated against JSON Schema:
import { validateManifest } from 'appgen-engine';
const isValid = await validateManifest(manifest);🛠️ Advanced Usage
Custom Template Source
import { Engine, TemplateSource } from 'appgen-engine';
class MyTemplateSource implements TemplateSource {
async resolve(name: string) {
// Custom resolution logic
return {
path: `/my/templates/${name}`,
manifest: await this.loadManifest(name),
};
}
}
const engine = new Engine({
templateSources: [new MyTemplateSource()],
});Custom Renderer
import { Engine, Renderer } from 'appgen-engine';
class MyRenderer extends Renderer {
async renderFile(content: string, variables: any) {
// Custom rendering logic
return content.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key]);
}
}
const engine = new Engine({
renderer: new MyRenderer(),
});Progress Tracking
const result = await engine.generate({
template: 'react-tailwind',
destination: './output',
onProgress: (event) => {
console.log(`${event.step}: ${event.message}`);
},
});📚 Examples
Check out the examples directory for more:
- Custom template creation
- Plugin development
- Hook implementation
- Testing strategies
- CI/CD integration
🤝 Contributing
We welcome contributions! See our Contributing Guide.
📄 License
MIT © AppGen Team
🔗 Related Packages
- appgen-cli - Command-line interface
- appgen-registry - Template registry
- appgen-templates - Official templates
- appgen-plugins - Official plugins
Made with ❤️ by the AppGen Team
