Skip to main content

Commands

Zelt provides CLI command support with dependency injection through @zeltjs/core.

Creating a Command

Use the @Command decorator with cliSchema() and args() for type-safe CLI commands:

import { Command, cliSchema, args } from '@zeltjs/core';

@Command({
name: 'greet',
description: 'Greet a user',
})
export class GreetCommand {
static schema = cliSchema({
args: [{ name: 'name', type: 'string' }],
});

run(ctx = args(GreetCommand)) {
console.log(`Hello, ${ctx.name}!`);
}
}

Configuration

Create a src/cli.ts entry point for your CLI:

import { createApp } from '@zeltjs/core';
import { onNode } from '@zeltjs/adapter-node';
import { GreetCommand } from './commands/greet.command';

const app = createApp({ commands: [GreetCommand] });
const nodeApp = await onNode(app);
await nodeApp.exec(nodeApp.args);

Then configure cli.entry in your zelt.config.ts:

import { defineConfig } from '@zeltjs/cli';

export default defineConfig({
controllers: 'src/controllers/**/*.ts',
cli: { entry: './src/cli.ts' },
});

Running Commands

Use zelt run to execute commands:

# Run a command
zelt run greet Alice

# With custom config
zelt run -c ./config/zelt.config.ts greet Alice

Schema Definition

The cliSchema() function defines typed arguments and options:

Positional Arguments

@Command({ name: 'copy' })
export class CopyCommand {
static schema = cliSchema({
args: [
{ name: 'source', type: 'string' },
{ name: 'destination', type: 'string' },
],
});

run(ctx = args(CopyCommand)) {
console.log(`Copying ${ctx.source} to ${ctx.destination}`);
}
}

Options (Flags)

@Command({ name: 'build' })
export class BuildCommand {
static schema = cliSchema({
options: [
{ name: 'watch', type: 'boolean', alias: 'w' },
{ name: 'outDir', type: 'string', alias: 'o', default: 'dist' },
],
});

run(ctx = args(BuildCommand)) {
if (ctx.watch) {
console.log('Watching for changes...');
}
console.log(`Output directory: ${ctx.outDir}`);
}
}
# Usage
zelt run build --watch --outDir=out
zelt run build -w -o out

Combined Arguments and Options

@Command({ name: 'deploy' })
export class DeployCommand {
static schema = cliSchema({
args: [
{ name: 'environment', type: 'string' },
],
options: [
{ name: 'dryRun', type: 'boolean' },
{ name: 'tag', type: 'string' },
],
});

run(ctx = args(DeployCommand)) {
const { environment, dryRun, tag } = ctx;

if (dryRun) {
console.log(`[DRY RUN] Would deploy to ${environment}`);
} else {
console.log(`Deploying ${tag ?? 'latest'} to ${environment}`);
}
}
}

Schema Types

Argument Types

TypeDescription
stringString value
numberNumeric value (automatically parsed)

Arguments can be marked as optional:

static schema = cliSchema({
args: [
{ name: 'file', type: 'string' },
{ name: 'count', type: 'number', optional: true },
],
});

Option Types

TypeDescription
stringString option
numberNumeric option (automatically parsed)
booleanBoolean flag

Options can have defaults:

static schema = cliSchema({
options: [
{ name: 'port', type: 'number', default: 3000 },
{ name: 'verbose', type: 'boolean' }, // defaults to false
],
});

Dependency Injection

Commands support dependency injection:

import { Command, cliSchema, args, inject } from '@zeltjs/core';
import { DatabaseService } from '../services/database.service';

@Command({ name: 'migrate' })
export class MigrateCommand {
static schema = cliSchema({
options: [
{ name: 'force', type: 'boolean' },
],
});

constructor(private readonly db = inject(DatabaseService)) {}

async run(ctx = args(MigrateCommand)) {
if (ctx.force) {
console.log('Force migration enabled');
}
await this.db.runMigrations();
console.log('Migrations completed');
}
}

Programmatic Execution

Commands can be executed programmatically using onNode():

import { createApp } from '@zeltjs/core';
import { onNode } from '@zeltjs/adapter-node';
import { MigrateCommand } from './commands/migrate.command';

const app = createApp({ commands: [MigrateCommand] });
const nodeApp = await onNode(app);

const result = await nodeApp.exec(['migrate', '--force']);
console.log(`Exit code: ${result.exitCode}`);

Async Commands

Commands can be async:

@Command({ name: 'sync' })
export class SyncCommand {
async run() {
console.log('Starting sync...');
await this.fetchData();
await this.processData();
console.log('Sync completed');
}

private async fetchData() {
// ...
}

private async processData() {
// ...
}
}