npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

appgen-engine

v2.0.0

Published

Core generation engine for App Generator Platform

Readme

appgen-engine

Core generation engine for AppGen - Powerful, extensible project scaffolding

npm version License: MIT

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


Made with ❤️ by the AppGen Team