npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ooneex/command

v0.13.0

Published

Command framework for building CLI commands with dependency injection, argument parsing, and execution logging

Downloads

979

Readme

@ooneex/command

A lightweight CLI command framework for TypeScript applications built on Bun. It provides a structured approach to building command-line interfaces with dependency injection, automatic argument parsing, and code generation utilities.

Browser Bun Deno Node.js TypeScript MIT License

Features

Decorator-Based Registration - Register commands with @decorator.command() and resolve them via the DI container

Type-Safe Options - Generic ICommand<Options> interface ensures fully typed command arguments

Automatic Argument Parsing - Built-in CLI argument parsing with support for named options, booleans, and positionals

Code Generation - commandCreate scaffolds command files and test stubs from built-in templates

Dependency Injection - Integrates with @ooneex/container supporting Singleton, Request, and Transient scopes

Error Handling - Structured CommandException with HTTP status codes and JSON-formatted stack traces

Terminal Logging - Automatic error output via @ooneex/logger on command failure

Installation

bun add @ooneex/command

Usage

Defining a Command

Implement ICommand and register it with the @decorator.command() decorator:

import { decorator } from '@ooneex/command';
import type { ICommand } from '@ooneex/command';

interface GreetOptions {
  name?: string;
}

@decorator.command()
class GreetCommand implements ICommand<GreetOptions> {
  getName(): string {
    return 'greet';
  }

  getDescription(): string {
    return 'Greet a user by name';
  }

  async run(options: GreetOptions): Promise<void> {
    const who = options.name ?? 'World';
    console.log(`Hello, ${who}!`);
  }
}

Running the CLI Entry Point

Call run() in your CLI entry file. It reads process.argv, resolves the matching command by name, and executes it:

import { run } from '@ooneex/command';
import './commands'; // import files that register commands via @decorator.command()

await run();

Invoke from the terminal:

bun run cli.ts greet --name Alice
# Hello, Alice!

Retrieving a Command Manually

import { getCommand } from '@ooneex/command';

const cmd = getCommand('greet');
if (cmd) {
  await cmd.run({ name: 'Bob' });
}

Generating a Command Scaffold

Use commandCreate to generate a command file, its test file, and update the barrel export:

import { commandCreate } from '@ooneex/command';

const paths = await commandCreate({
  name: 'database:seed',       // supports any case — normalised automatically
  commandDir: 'src/commands',  // optional, defaults to src/commands
  testsDir: 'tests/commands',  // optional, defaults to tests/commands
});

console.log(paths.commandPath); // src/commands/DatabaseSeedCommand.ts
console.log(paths.testPath);    // tests/commands/DatabaseSeedCommand.spec.ts

Generated files are based on built-in templates and include the ICommand interface, @decorator.command() registration, and a placeholder run method.

Error Handling

import { CommandException } from '@ooneex/command';

try {
  throw new CommandException('Database not available', 'DB_UNAVAILABLE', {
    host: 'localhost',
    port: 5432,
  });
} catch (error) {
  if (error instanceof CommandException) {
    console.error(error.message); // Database not available
    console.error(error.data);    // { host: 'localhost', port: 5432 }
  }
}

API Reference

Interfaces

ICommand<Options>

The contract every command must implement.

interface ICommand<Options extends Record<string, unknown> = Record<string, unknown>> {
  run(options: Options): Promise<void> | void;
  getName(): string;
  getDescription(): string;
}

| Method | Description | |--------|-------------| | getName() | Returns the command name used to match CLI input (e.g. 'db:seed') | | getDescription() | Returns a human-readable description of the command | | run(options) | Executes the command with the parsed options |

Decorators

decorator.command(scope?)

Registers a command class with the DI container and the global COMMANDS_CONTAINER.

decorator.command(scope?: EContainerScope): ClassDecorator

Parameters:

  • scope — DI container scope. Defaults to EContainerScope.Singleton

Example:

import { decorator } from '@ooneex/command';
import { EContainerScope } from '@ooneex/container';

@decorator.command(EContainerScope.Transient)
class BuildCommand implements ICommand {
  // ...
}

Functions

run(): Promise<void>

Parses Bun.argv, resolves the command from the third positional argument, and calls command.run(options). Exits with code 1 if no command is found or if the command throws.

Parsed options forwarded to command.run:

| CLI flag | options key | Type | |----------|---------------|------| | --name | name | string | | --dir | dir | string | | --channel | channel | string | | --is-socket | isSocket | boolean | | --table-name | tableName | string | | --module | module | string (kebab-cased) | | --destination | destination | string | | --drop | drop | boolean | | --override | override | boolean | | --target | target | string | | --route-name | route.name | string | | --route-path | route.path | `/${string}` | | --route-method | route.method | HttpMethodType |

getCommand(name: string): ICommand | null

Resolves a registered command by its name. Returns null if no match is found.

const command = getCommand('db:migrate');

commandCreate(config): Promise<{ commandPath: string; testPath: string }>

Scaffolds a new command class and its test file from built-in templates.

Parameters:

| Property | Type | Default | Description | |----------|------|---------|-------------| | name | string | — | Command name (any case, normalised to PascalCase) | | commandDir | string | src/commands | Directory for the generated command file | | testsDir | string | tests/commands | Directory for the generated test file |

Returns: Object with commandPath and testPath pointing to the created files.

Classes

CommandException

Structured exception for command-related errors, extending Exception from @ooneex/exception.

new CommandException(message: string, key: string, data?: Record<string, unknown>)

Parameters:

  • message — Human-readable error description
  • key — Machine-readable error key (e.g. 'INVALID_OPTION')
  • data — Optional contextual data attached to the exception

Constants

COMMANDS_CONTAINER

Global array holding all registered command constructors. Populated automatically by @decorator.command().

import { COMMANDS_CONTAINER } from '@ooneex/command';

console.log(COMMANDS_CONTAINER.length); // number of registered commands

Types

CommandClassType

Constructor type for any class implementing ICommand.

type CommandClassType = new (...args: any[]) => ICommand;

Advanced Usage

Dependency Injection with Scopes

Commands are resolved through @ooneex/container, so they support the full range of container scopes:

import { decorator } from '@ooneex/command';
import { EContainerScope } from '@ooneex/container';

@decorator.command(EContainerScope.Singleton)
class MigrateCommand implements ICommand {
  getName() { return 'db:migrate'; }
  getDescription() { return 'Run database migrations'; }

  async run(options: { drop?: boolean }): Promise<void> {
    if (options.drop) {
      console.log('Dropping existing tables...');
    }
    console.log('Running migrations...');
  }
}

Multiple Commands in a Single App

Register all commands by importing their files before calling run():

// commands/index.ts
export { MigrateCommand } from './MigrateCommand';
export { SeedCommand } from './SeedCommand';
export { BuildCommand } from './BuildCommand';

// cli.ts
import './commands';
import { run } from '@ooneex/command';

await run();
bun run cli.ts db:migrate --drop
bun run cli.ts db:seed --name fixtures
bun run cli.ts build --target production

Typed Command Options

Use the generic parameter on ICommand for full type safety:

interface DeployOptions {
  target: string;
  override?: boolean;
  destination?: string;
}

@decorator.command()
class DeployCommand implements ICommand<DeployOptions> {
  getName() { return 'deploy'; }
  getDescription() { return 'Deploy the application'; }

  async run(options: DeployOptions): Promise<void> {
    console.log(`Deploying to ${options.target}...`);
    if (options.override) {
      console.log('Overriding existing deployment');
    }
  }
}

Custom Error Reporting

import { CommandException } from '@ooneex/command';

@decorator.command()
class ValidateCommand implements ICommand {
  getName() { return 'validate'; }
  getDescription() { return 'Validate project configuration'; }

  async run(options: { name?: string }): Promise<void> {
    if (!options.name) {
      throw new CommandException(
        'The --name option is required',
        'MISSING_NAME',
        { received: options },
      );
    }
    console.log(`Validating ${options.name}...`);
  }
}

When run() catches this exception it logs it via TerminalLogger and exits with code 1.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup

  1. Clone the repository
  2. Install dependencies: bun install
  3. Run tests: bun run test
  4. Build the project: bun run build

Guidelines

  • Write tests for new features
  • Follow the existing code style
  • Update documentation for API changes
  • Ensure all tests pass before submitting PR

Made with ❤️ by the Ooneex team