Skip to content

CLI

Contract

@modularityjs/cli defines the CliCommand interface and pool-based command discovery.

typescript
interface CliCommand {
  readonly name: string;
  readonly description: string;
  readonly options?: CommandOption[];
  readonly arguments?: CommandArgument[];
  execute(
    args: Record<string, unknown>,
    options: Record<string, unknown>,
  ): void | Promise<void>;
}

Drivers

Commander (@modularityjs/cli-commander)

Commander.js-based CLI runner. Discovers all commands from the pool, registers them as subcommands, and parses process.argv.

typescript
import { CliModule } from '@modularityjs/cli';
import { CliCommanderModule } from '@modularityjs/cli-commander';

const modules = [
  CliModule.forRoot({ name: 'myapp', version: '1.0.0' }),
  CliCommanderModule,
];

Configuration

typescript
CliModule.forRoot({
  name: 'myapp', // CLI program name (default: 'modularityjs')
  version: '1.0.0', // Version string (default: '0.0.0')
  description: 'My CLI app', // Program description (default: 'ModularityJS CLI')
  argv: process.argv, // Custom argv (default: process.argv)
});

Defining Commands

Implement the CliCommand interface and register via CliCommandsPool:

typescript
import { CliCommandsPool, CliModule } from '@modularityjs/cli';
import { Injectable } from '@modularityjs/di';
import type { CliCommand } from '@modularityjs/cli';
import { Module } from '@modularityjs/modularity';

@Injectable()
class GreetCommand implements CliCommand {
  readonly name = 'greet';
  readonly description = 'Say hello';
  readonly arguments = [{ name: 'name', description: 'Name to greet' }];
  readonly options = [
    { flags: '-u, --uppercase', description: 'Use uppercase' },
  ];

  execute(args: Record<string, unknown>, options: Record<string, unknown>) {
    const name = args['name'] as string;
    const greeting = `Hello ${name}`;
    console.log(options['uppercase'] ? greeting.toUpperCase() : greeting);
  }
}

@Module({
  name: 'greet',
  imports: [CliModule],
  providers: [GreetCommand],
  pools: [{ pool: CliCommandsPool, key: 'greet', useClass: GreetCommand }],
})
class GreetModule {}

Arguments and Options

typescript
interface CommandArgument {
  name: string;
  description: string;
  defaultValue?: string; // Makes the argument optional
}

interface CommandOption {
  flags: string; // Commander.js flag syntax: '-s, --short <value>'
  description: string;
  defaultValue?: string | boolean | string[];
}

Framework CLI Packages

Several packages provide built-in CLI commands:

PackageCommandDescription
@modularityjs/modularity-climodule:listList loaded modules
@modularityjs/modularity-climodule:preference:listShow preference override chains
@modularityjs/modularity-climodule:pool:listShow pool entries by pool
@modularityjs/database-clidatabase:*Status + migration management (5 cmds)
@modularityjs/http-clihttp:route:listList registered HTTP routes
@modularityjs/openapi-cliopenapi:exportEmit OpenAPI document to stdout/file
@modularityjs/events-clievent:listList event handlers
@modularityjs/scheduler-clischeduler:listList scheduled jobs
@modularityjs/scheduler-clischeduler:runTrigger a job manually
@modularityjs/health-clihealth:statusRun health checks
@modularityjs/queue-cliqueue:listList queue consumers
@modularityjs/queue-cliqueue:dead-letter:listList dead-lettered messages
@modularityjs/storage-clistorage:listList stored objects
@modularityjs/assets-cliassets:collectWalk asset pool and write hashed files
@modularityjs/auth-cliauth:resolveResolve a token through the resolver chain
@modularityjs/authz-cliauthz:check, authz:permissionsCheck or list identity permissions
@modularityjs/cache-clicache:delete, cache:invalidate-tagDelete or invalidate cached values
@modularityjs/lock-clilock:releaseRelease a distributed lock by token
@modularityjs/outbox-clioutbox:*List / dispatch / retry / stats (4 cmds)
@modularityjs/rate-limit-clirate-limit:get, rate-limit:resetInspect or reset a rate-limit key
@modularityjs/webhook-cliwebhook:list, webhook:sendList subscriptions or dispatch a test event
@modularityjs/ws-cliws:list, ws:broadcastList clients or broadcast a message
@modularityjs/plugins-cliplugin:listList registered plugins

Error Handling

Exceptions thrown in execute() are caught by the Commander driver. The error message is printed to stderr and process.exitCode is set to 1:

typescript
import { Injectable } from '@modularityjs/di';
import type { CliCommand } from '@modularityjs/cli';

@Injectable()
class MigrateCommand implements CliCommand {
  readonly name = 'migrate';
  readonly description = 'Run database migrations';
  readonly options = [
    { flags: '-e, --env <env>', description: 'Target environment' },
  ];

  execute(_args: Record<string, unknown>, options: Record<string, unknown>) {
    const env = options['env'] as string | undefined;
    if (!env) {
      throw new Error('--env is required');
    }
    // ... run migration
  }
}

Async Commands

execute() can return Promise<void>. The Commander driver awaits the result:

typescript
async execute() {
  const pending = await this.migrationRunner.pending();
  console.log(`${pending.length} pending migrations`);
  await this.migrationRunner.run();
}

Injecting Services

Commands are @Injectable() classes with full DI access. Inject any framework service:

typescript
import { DatabaseConnection } from '@modularityjs/database';
import { Inject, Injectable } from '@modularityjs/di';
import { LoggerFactory } from '@modularityjs/logger';
import type { CliCommand } from '@modularityjs/cli';

@Injectable()
class SeedCommand implements CliCommand {
  readonly name = 'db:seed';
  readonly description = 'Seed the database';

  constructor(
    @Inject(DatabaseConnection) private readonly db: DatabaseConnection,
    @Inject(LoggerFactory) private readonly loggerFactory: LoggerFactory,
  ) {}

  async execute() {
    const logger = this.loggerFactory.create('seed');
    logger.info('Seeding database...');
    // ... use this.db
  }
}