Bunli
Packages

@bunli/generator

Generate TypeScript definitions from CLI commands for enhanced developer experience

@bunli/generator has minimal dependencies and uses Babel for AST parsing to extract command metadata from your TypeScript files.

Installation

bash bun add @bunli/generator

Features

  • 🚀 Fast Generation - Uses Babel for efficient AST parsing
  • 📝 Type Safety - Generates fully typed TypeScript definitions
  • 🔄 Watch Mode - Automatically regenerate on file changes
  • 🎯 Command Discovery - Scans and parses command files automatically
  • 🔌 Plugin Integration - Works seamlessly with Bunli's plugin system
  • Build Integration - Integrates with bunli dev and bunli build
  • 🛠️ CLI Tool - Standalone bunli generate command

Core APIs

Generator Class

The main class for generating TypeScript definitions:

import { Generator } from "@bunli/generator";

const generator = new Generator({
  entry: "./src/cli.ts",
  directory: "./src/commands",
  outputFile: "./.bunli/commands.gen.ts",
  config: {}, // Optional config object
  generateReport: false, // Optional report generation
});

// Generate types once
await generator.run();

// Generate with specific options
await generator.run({
  type: "update",
  path: "./commands/new-command.ts",
});

CommandScanner

Scans directories for command files:

import { CommandScanner } from "@bunli/generator";

const scanner = new CommandScanner();
const files = await scanner.scanCommands("./src/cli.ts", "./src/commands");

console.log(files); // ['greet.ts', 'deploy.ts', 'test.ts']

parseCommand Function

Parse individual command files:

import { parseCommand } from "@bunli/generator";

const commandData = await parseCommand("./commands/greet.ts", "./src", "./.bunli/commands.gen.ts");
console.log(commandData);
// {
//   name: 'greet',
//   description: 'Greet someone',
//   options: { ... },
//   alias: ['hello'],
//   filePath: './commands/greet.ts',
//   importPath: '../commands/greet',
//   exportPath: 'greet'
// }

buildTypes Function

Build TypeScript definitions from command data:

import { buildTypes } from '@bunli/generator'

const commands = [
  { name: 'greet', description: 'Greet someone', options: { ... } },
  { name: 'deploy', description: 'Deploy app', options: { ... } }
]

const types = buildTypes(commands)
console.log(types) // Generated TypeScript code

Plugin Integration

bunliCodegenPlugin

Use as a Bun plugin for build integration:

import { bunliCodegenPlugin } from "@bunli/generator";

// In your build configuration
export default {
  plugins: [
    bunliCodegenPlugin({
      entry: "./src/cli.ts",
      directory: "./src/commands",
      outputFile: "./.bunli/commands.gen.ts",
    }),
  ],
};

CLI Integration

The generator is automatically integrated with Bunli CLI commands:

# Development mode with watch
bunli dev

# Build with type generation
bunli build

# Standalone generation
bunli generate --watch

Configuration Options

Generator Options

Configuration for type generation

OptionTypeDefaultDescription
entrystring'./cli.ts'CLI entry file used for command discovery
directorystringundefinedOptional fallback directory for command scanning
outputFilestring'./commands.gen.ts'Output file for generated types
includestring[]['**/*.ts', '**/*.js']File patterns to include
excludestring[]['**/*.test.*', '**/*.spec.*']File patterns to exclude

Generated Output Structure

The generator creates .bunli/commands.gen.ts which exports a GeneratedStore instance:

GeneratedStore Interface

The generated file exports a cli object (a GeneratedStore instance) and named command exports:

// Named command exports
export { greet } from "../src/commands/greet.js";
export { deploy } from "../src/commands/deploy.js";

// Generated command metadata
export const metadata: Record<string, GeneratedCommandMeta>;

// Generated store instance
export const cli: GeneratedStore<typeof commands, typeof metadata>;

GeneratedStore Methods

interface GeneratedStore<TModules, TMeta> {
  commands: TModules;
  metadata: TMeta;
  register(cli?: CLI<any>): GeneratedStore<TModules, TMeta>;
  list(): Array<{
    name: keyof TModules;
    command: TModules[keyof TModules];
    metadata: TMeta[keyof TModules];
  }>;
  get<Name extends keyof TModules>(name: Name): TModules[Name];
  getMetadata<Name extends keyof TModules>(name: Name): TMeta[Name];
  findByName<Name extends keyof TModules>(
    name: Name,
  ): { name: Name; command: TModules[Name]; metadata: TMeta[Name] };
  findByDescription(searchTerm: string): Array<{
    name: keyof TModules;
    command: TModules[keyof TModules];
    metadata: TMeta[keyof TModules];
  }>;
  getCommandNames(): Array<keyof TModules & string>;
  getFlags<Name extends keyof RegisteredCommands>(name: Name): Record<string, unknown>;
  getFlagsMeta<Name extends keyof TModules>(name: Name): Record<string, GeneratedOptionMeta>;
  validateCommand<Name extends keyof TModules>(
    name: Name,
    flags: Record<string, unknown>,
  ): { success: true; data: Record<string, unknown> } | { success: false; errors: string[] };
  withCLI(cli: CLI<any>): GeneratedExecutor<TModules>;
}

GeneratedExecutor

interface GeneratedExecutor<TModules extends Record<string, Command<any>>> {
  execute(name: string, options: unknown): Promise<void>;
}

Usage

Basic Generation

import { Generator } from "@bunli/generator";

const generator = new Generator({
  entry: "./src/cli.ts",
  directory: "./src/commands",
  outputFile: "./.bunli/commands.gen.ts",
});

await generator.run();

Watch Mode

Watch mode is handled by the CLI command:

bunli generate --watch

Performance

  • Scanning: ~10ms for 50 command files
  • Parsing: ~5ms per command file
  • Generation: ~2ms for 20 commands
  • Watch Mode: <1ms incremental updates

API Reference

Generator

class Generator {
  constructor(options: GeneratorConfig);

  // Generate types
  run(options?: GeneratorEvent): Promise<Result<void, GeneratorRunError>>;

  // Get current configuration
  getConfig(): GeneratorConfig;

  // Update configuration
  updateConfig(options: Partial<GeneratorConfig>): void;
}

GeneratorConfig

interface GeneratorConfig {
  entry: string;
  directory?: string;
  outputFile: string;
  config?: any;
  generateReport?: boolean;
}

GeneratorEvent

interface GeneratorEvent {
  type: "create" | "update" | "delete";
  path: string;
}

CommandMetadata

interface CommandMetadata {
  name: string;
  description: string;
  alias?: string | string[];
  options?: Record<string, OptionMetadata>;
  commands?: CommandMetadata[];
  filePath: string;
  importPath: string;
  exportPath: string;
  hasHandler?: boolean;
  hasRender?: boolean;
}

interface OptionMetadata {
  type: string;
  required: boolean;
  hasDefault: boolean;
  default?: any;
  description?: string;
  short?: string;
  schema?: any;
  validator?: string;
}

Integration

With Bunli CLI

// bunli.config.ts
import { defineConfig } from "@bunli/core";

export default defineConfig({
  name: "my-cli",
  version: "1.0.0",
});

With Custom Build Script

import { Generator } from "@bunli/generator";

const generator = new Generator({
  entry: "./src/cli.ts",
  directory: "./src/commands",
  outputFile: "./.bunli/commands.gen.ts",
});

await generator.run();

Troubleshooting

Generation fails:

  • Check that command files use defineCommand
  • Verify TypeScript syntax is correct

Types not updating:

  • Use bunli generate --watch for development
  • Check file paths are correct

Import errors:

  • Ensure generated file exists at ./.bunli/commands.gen.ts
  • Verify TypeScript configuration

See Also

On this page