Skip to content

Is introduction ACL for commander.js useless? #56

@ysknsid25

Description

@ysknsid25

Hono CLI calls commander.js directly. This is tightly coupled.

If we will change CLI library, we have to re-write production code. It may be difficult to guarantee same behavior.

So, How about introducing anti-corruption layer (ACL)? 👀

1. make src/acl/command.ts

Hono CLI uses only belows from commander.js.

  • name
  • description
  • version
  • parse
  • command
  • argument
  • option
  • action

We make original class named Command which has thees methods.

e.g,)

import { Command as CommanderCommand } from 'commander'

type ActionCallback = (...args: any[]) => void | Promise<void>

export class Command {
  private instance: CommanderCommand

  /**
   * @internal
   * This method is used to pass the internal commander instance to other commands.
   * It should not be used directly.
   */
  _getCommanderInstance(): CommanderCommand {
    return this.instance
  }

  constructor(name?: string) {
    this.instance = new CommanderCommand(name)
  }

  name(name: string): this {
    this.instance.name(name)
    return this
  }

  description(str: string): this {
    this.instance.description(str)
    return this
  }

  addCommand(cmd: Command): this {
    this.instance.addCommand(cmd._getCommanderInstance())
    return this
  }

  option(flags: string, description?: string, defaultValue?: string | boolean | string[]): this {
    this.instance.option(flags, description, defaultValue)
    return this
  }

  action(fn: ActionCallback): this {
    this.instance.action(fn)
    return this
  }

  parse(argv?: readonly string[]): this {
    this.instance.parse(argv)
    return this
  }

  version(str: string, flags?: string, description?: string): this {
    this.instance.version(str, flags, description)
    return this
  }
}

This inverts the dependency: instead of Hono CLI being tied to the Commander interface, you can decide what interface you need for Hono CLI and then use the functionality you need from Commander.js.

2. Change import resouce

All files excluding src/acl/command.ts replace commander to src/acl/command.ts

- import { Command } from 'commander'
+ import { Command } from './acl/command.ts'
import { readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { docsCommand } from './commands/docs/index.js'
import { optimizeCommand } from './commands/optimize/index.js'
import { requestCommand } from './commands/request/index.js'
import { searchCommand } from './commands/search/index.js'
import { serveCommand } from './commands/serve/index.js'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

...

3. Add Test Code for src/acl/command.ts

This makes it easy to verify operation even if you replace commander.js with another CLI library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions