FUNC - Tiny typed CLI framework

Core Concepts

A func app registers decorated classes in a FuncModule. At runtime, func finds the class that matches the user's CLI input, selects one handler method, injects fields and parameters, then runs the handler.

Command Scopes

A command scope is the class that owns the current CLI input. Start with the two common cases: no command name, and a named command.

Major Command

@CommandMajor runs when the user invokes the CLI without a command name.

import { CommandMajor, Handler } from 'func'

@CommandMajor()
export class MajorCommand {
  @Handler()
  run() {
    console.log('welcome')
  }
}

Named Command

@Command runs when the first input token matches its name or alias.

import { Command, Handler } from 'func'

@Command({ name: 'build', alias: 'b' })
export class BuildCommand {
  @Handler()
  run() {
    console.log('build')
  }
}
User Input Runs Why
my-cli MajorCommand There is no command name, so the major command runs.
my-cli build BuildCommand The first input token matches build.
my-cli b BuildCommand The first input token matches the command alias.

Unknown Command

@CommandMissing is optional. Use it when you want a custom message for input that does not match any named command.

import { CommandMissing } from 'func'

@CommandMissing()
export class MissingCommand {
  constructor() {
    console.log('unknown command')
  }
}
User Input Runs Why
my-cli unknown MissingCommand The first input token is not a registered command.

Handlers

Every command scope needs at least one @Handler method. A handler can be the default method, a flag-triggered method, or a path method. Path handlers are checked before flag handlers, and the longest matching path wins.

import { Command, Handler } from 'func'

@Command({ name: 'config' })
export class ConfigCommand {
  @Handler({ path: ['get'] })
  get() {
    console.log('get config')
  }

  @Handler({ flag: 'help', alias: 'h' })
  help() {
    console.log('usage')
  }

  @Handler()
  run() {
    console.log('config')
  }
}

For my-cli config get, the path handler runs. For my-cli config --help, the flag handler runs. If no path or flag handler matches, the default handler runs.

Modules and Services

@FuncModule can register commands, import feature modules, and register services. Services are instantiated once and can depend on other registered services.

import { Command, FuncModule, Handler, Service, run } from 'func'

@Service()
class ProjectService {
  name() {
    return 'demo'
  }
}

@Command({ name: 'build' })
class BuildCommand {
  constructor(private project: ProjectService) {}

  @Handler()
  run() {
    console.log(this.project.name())
  }
}

@FuncModule({
  commands: [BuildCommand],
  services: [ProjectService],
})
class AppModule {}

run(AppModule)

Next Topics

Use Field Options for flags and values, Parameters for @Args, @Regs, and @Exception, and Errors for local and global error handling.