Skip to content

Add basic CLI commands to core package #59

@vlavrynovych

Description

@vlavrynovych

Overview

Add basic CLI commands (migrate, list, rollback) to the core package with a factory pattern that allows database-specific implementations to extend the base CLI with their own commands.

Motivation

  • Provide out-of-the-box CLI functionality for users
  • Keep basic commands simple and in core (no need for separate package)
  • Allow DBMS-specific packages (@migration-script-runner/postgresql, etc.) to extend with custom commands
  • Improve developer experience with ready-to-use CLI

Proposed Structure

src/
├── cli/
│   ├── commands/
│   │   ├── migrate.ts      # Run pending migrations
│   │   ├── list.ts         # List migrations with status
│   │   └── rollback.ts     # Rollback last migration
│   ├── createCLI.ts        # Factory for creating/extending CLI
│   └── index.ts            # Exports
├── interface/
├── logger/
├── model/
└── service/

bin/
└── msr                     # CLI entry point

Basic Commands

1. migrate

Run all pending migrations.

msr migrate [options]

Options:
  -c, --config <path>    Configuration file path
  -h, --help            Display help

2. list

List all migrations with their status.

msr list [options]

Options:
  -n, --number <count>   Number of migrations to display
  -c, --config <path>    Configuration file path
  -h, --help            Display help

3. rollback

Rollback the last migration (requires down() support).

msr rollback [options]

Options:
  -c, --config <path>    Configuration file path
  -h, --help            Display help

CLI Factory Pattern

The createCLI() factory allows DBMS-specific packages to extend base commands:

// src/cli/createCLI.ts
import { Command } from 'commander';
import { IMigrationScriptHandler } from '../interface';

export interface CLIOptions {
  handler: IMigrationScriptHandler;
  name?: string;
  description?: string;
  version?: string;
}

export function createCLI(options: CLIOptions): Command {
  const program = new Command();

  program
    .name(options.name || 'msr')
    .description(options.description || 'Migration Script Runner')
    .version(options.version || '1.0.0');

  // Add base commands
  addMigrateCommand(program, options.handler);
  addListCommand(program, options.handler);
  addRollbackCommand(program, options.handler);

  return program;
}

Example: Extending in PostgreSQL Package

// @migration-script-runner/postgresql
import { createCLI } from '@migration-script-runner/core/cli';
import { PostgresHandler } from './PostgresHandler';

// Create base CLI with PostgreSQL handler
const program = createCLI({
  handler: new PostgresHandler(),
  name: 'msr-pg',
  description: 'PostgreSQL Migration Runner'
});

// Add PostgreSQL-specific commands
program
  .command('pg:dump')
  .description('Create PostgreSQL database dump')
  .action(async () => {
    // PostgreSQL-specific logic
  });

program
  .command('pg:vacuum')
  .description('Run VACUUM on database')
  .action(async () => {
    // PostgreSQL-specific logic
  });

program.parse();

Implementation Details

Command: migrate

// src/cli/commands/migrate.ts
import { Command } from 'commander';
import { IMigrationScriptHandler } from '../../interface';
import { MigrationScriptExecutor } from '../../service';

export function addMigrateCommand(program: Command, handler: IMigrationScriptHandler) {
  program
    .command('migrate')
    .description('Run all pending migrations')
    .option('-c, --config <path>', 'Configuration file path')
    .action(async (options) => {
      const executor = new MigrationScriptExecutor(handler);
      const result = await executor.migrate();
      
      if (result.success) {
        console.log(`✓ Successfully executed ${result.executed.length} migrations`);
        process.exit(0);
      } else {
        console.error(`✗ Migration failed:`, result.errors);
        process.exit(1);
      }
    });
}

Command: list

// src/cli/commands/list.ts
export function addListCommand(program: Command, handler: IMigrationScriptHandler) {
  program
    .command('list')
    .description('List all migrations with status')
    .option('-n, --number <count>', 'Number of migrations to display', '0')
    .option('-c, --config <path>', 'Configuration file path')
    .action(async (options) => {
      const executor = new MigrationScriptExecutor(handler);
      await executor.list(parseInt(options.number));
      process.exit(0);
    });
}

Command: rollback

// src/cli/commands/rollback.ts
export function addRollbackCommand(program: Command, handler: IMigrationScriptHandler) {
  program
    .command('rollback')
    .description('Rollback the last migration')
    .option('-c, --config <path>', 'Configuration file path')
    .action(async (options) => {
      // Rollback logic (requires #50 - down() support)
      console.log('Rollback functionality requires down() migration support');
      process.exit(1);
    });
}

Dependencies

{
  "dependencies": {
    "commander": "^11.0.0"
  }
}

Benefits

  • ✅ Out-of-the-box CLI for basic operations
  • ✅ Extensible for database-specific implementations
  • ✅ No need for separate CLI package (keeps it simple)
  • ✅ Consistent command structure across all DBMS packages
  • ✅ Easy to test and maintain

Acceptance Criteria

  • createCLI() factory function implemented
  • migrate command functional
  • list command functional
  • rollback command stub (full implementation in Make backup/restore optional to support down() migrations #50)
  • CLI entry point in bin/msr
  • Commander.js integrated
  • Help text for all commands
  • Error handling for invalid options
  • Tests for CLI commands
  • Documentation with usage examples
  • Example showing how to extend CLI

Related Issues

Priority

Low (Backlog) - Nice to have but not blocking core functionality

Notes

  • Keep CLI simple - don't add too many options initially
  • Focus on the factory pattern that enables extension
  • Rollback command is a stub until Make backup/restore optional to support down() migrations #50 is implemented
  • Consider adding init command to generate migration files
  • May want to add validate command to check migration file naming

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions