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:
| Package | Command | Description |
|---|---|---|
@modularityjs/modularity-cli | module:list | List loaded modules |
@modularityjs/modularity-cli | module:preference:list | Show preference override chains |
@modularityjs/modularity-cli | module:pool:list | Show pool entries by pool |
@modularityjs/database-cli | database:* | Status + migration management (5 cmds) |
@modularityjs/http-cli | http:route:list | List registered HTTP routes |
@modularityjs/openapi-cli | openapi:export | Emit OpenAPI document to stdout/file |
@modularityjs/events-cli | event:list | List event handlers |
@modularityjs/scheduler-cli | scheduler:list | List scheduled jobs |
@modularityjs/scheduler-cli | scheduler:run | Trigger a job manually |
@modularityjs/health-cli | health:status | Run health checks |
@modularityjs/queue-cli | queue:list | List queue consumers |
@modularityjs/queue-cli | queue:dead-letter:list | List dead-lettered messages |
@modularityjs/storage-cli | storage:list | List stored objects |
@modularityjs/assets-cli | assets:collect | Walk asset pool and write hashed files |
@modularityjs/auth-cli | auth:resolve | Resolve a token through the resolver chain |
@modularityjs/authz-cli | authz:check, authz:permissions | Check or list identity permissions |
@modularityjs/cache-cli | cache:delete, cache:invalidate-tag | Delete or invalidate cached values |
@modularityjs/lock-cli | lock:release | Release a distributed lock by token |
@modularityjs/outbox-cli | outbox:* | List / dispatch / retry / stats (4 cmds) |
@modularityjs/rate-limit-cli | rate-limit:get, rate-limit:reset | Inspect or reset a rate-limit key |
@modularityjs/webhook-cli | webhook:list, webhook:send | List subscriptions or dispatch a test event |
@modularityjs/ws-cli | ws:list, ws:broadcast | List clients or broadcast a message |
@modularityjs/plugins-cli | plugin:list | List 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
}
}