oclifでサブコマンドありのCLIコマンドを作る

やりたいこと aws cloudfront list-distributionsみたいな、サブコマンドありのコマンドを作りたい oclifでサブコマンドありのプロジェクトをセットアップする oclifでプロジェクトを作り […]

広告ここから
広告ここまで

目次

    やりたいこと

    aws cloudfront list-distributionsみたいな、サブコマンドありのコマンドを作りたい

    oclifでサブコマンドありのプロジェクトをセットアップする

    oclifでプロジェクトを作ります。

    $ npx oclif multi test
    npx: 429個のパッケージを28.809秒でインストールしました。
    (node:31637) ExperimentalWarning: The fs.promises API is experimental
    
         _-----_     ╭──────────────────────────╮
        |       |    │      Time to build a     │
        |--(o)--|    │  single-command CLI with │
       `---------´   │  oclif! Version: 1.11.4  │
        ( _´U`_ )    ╰──────────────────────────╯
        /___A___\   /
         |  ~  |     
       __'.___.'__   
     ´   `  |° ´ Y ` 
    
    ? npm package name test
    ? command bin name the CLI will export test
    ? description test cli
    ? author hideokamoto @hideokamoto
    ? version 0.1.0
    ? license (MIT) 
    ? node version supported >=8.0.0
    ? github owner of repository (https://github.com/OWNER/repo) hideokamoto
    ? github name of repository (https://github.com/owner/REPO) test
    ? optional components to include (Press <space> to select, <a> to toggle all, <i> to invert selection)
    ❯◉ yarn (npm alternative)
     ◉ mocha (testing framework)
     ◉ circleci (continuous integration/delivery service)
     ◉ appveyor (continuous integration/delivery service)
     ◉ codecov (online code coverage report viewer)
     ◉ typescript (static typing for javascript)
     ◉ tslint (static analysis tool for typescript)
    (Move up and down to reveal more choices)

    ディレクトリ構成

    全部盛りだとこんな感じ。

    $ tree -L 2 -I node_modules
    .
    ├── README.md
    ├── appveyor.yml
    ├── bin
    │   ├── run
    │   └── run.cmd
    ├── lib
    │   ├── commands
    │   ├── index.d.ts
    │   └── index.js
    ├── package.json
    ├── src
    │   ├── commands
    │   └── index.ts
    ├── test
    │   ├── commands
    │   ├── helpers
    │   ├── mocha.opts
    │   └── tsconfig.json
    ├── tsconfig.json
    ├── tslint.json
    └── yarn.lock
    

    実行するとこんな感じに。

    $ ./bin/run -h
    VERSION
      test/0.0.0 darwin-x64 node-v10.1.0
    
    USAGE
      $ test [COMMAND]
    
    COMMANDS
      hello  describe the command here
      help   display help for test
    
    $ ./bin/run hello
    hello world from ./src/commands/hello.ts

    コマンド出力をカスタマイズする

    デフォルトで生成されているhelloコマンドの出力を変えてみましょう。ファイルはsrc/commands/hello.tsにあります。

    import {Command, flags} from '@oclif/command'
    
    export default class Hello extends Command {
      static description = 'describe the command here'
    
      static examples = [
        `$ test hello
    hello world from ./src/hello.ts!
    `,
      ]
    
      static flags = {
        help: flags.help({char: 'h'}),
        // flag with a value (-n, --name=VALUE)
        name: flags.string({char: 'n', description: 'name to print'}),
        // flag with no value (-f, --force)
        force: flags.boolean({char: 'f'}),
      }
    
      static args = [{name: 'file'}]
    
      async run() {
        const {args, flags} = this.parse(Hello)
    
        const name = flags.name || 'world'
        this.log(`hello ${name} from ./src/commands/hello.ts`)
        if (args.file && flags.force) {
          this.log(`you input --force and --file: ${args.file}`)
        }
      }
    }
    

    オプションをカスタマイズする

    static flagsで定義されている内容が、オプションとして利用できます。サンプルではname/help/forceが用意されており、-hしてみるとOPTIONSにきちんと出てきます。

    $ ./bin/run hello -h
    describe the command here
    
    USAGE
      $ test hello [FILE]
    
    OPTIONS
      -f, --force
      -h, --help       show CLI help
      -n, --name=name  name to print
    
    EXAMPLE
      $ test hello
      hello world from ./src/hello.ts!

    ここに--dry-run/-dを追加したい場合、以下のようにすればOKです。

      static flags = {
        help: flags.help({char: 'h'}),
        // flag with a value (-n, --name=VALUE)
        name: flags.string({char: 'n', description: 'name to print'}),
        // flag with no value (-f, --force)
        force: flags.boolean({char: 'f'}),
        'dry-run': flags.boolean({char: 'd', description: 'dry run mode'})
      }

    async run()では以下のように変更すればOKです。

      async run() {
        const {args, flags} = this.parse(Hello)
    
        const name = flags.name || 'world'
        this.log(`hello ${name} from ./src/commands/hello.ts`)
        // flags[FLAG_NAME]で判別できる
        if (flags['dry-run']) {
          // ここにdry-run時の処理を書く
          this.log('dry run mode')
        }
        if (args.file && flags.force) {
          this.log(`you input --force and --file: ${args.file}`)
        }
      }

    追加されました。

     ./bin/run hello -h
    describe the command here
    
    USAGE
      $ test hello [FILE]
    
    OPTIONS
      -d, --dry-run    dry run mode
      -f, --force
      -h, --help       show CLI help
      -n, --name=name  name to print
    
    EXAMPLE
      $ test hello
      hello world from ./src/hello.ts!
    
    $ ./bin/run hello -d
    hello world from ./src/commands/hello.ts
    dry run mode

    サブコマンドを追加する

    次は一番やりたかったサブコマンドの追加をやりましょう。aws cloudfront list-distributionslist-distributions部分ですね。これはstatic argsを見ればOKです。

     static args = [{name: 'file'}]

    ここを以下のように変えてみましょう。

      static args = [
        {
          name: 'action',
          required: true,
          description: 'list-distributions, get-distribution'
        }
      ]

    これだけで反映されます。

    $ ./bin/run hello -h
    describe the command here
    
    USAGE
      $ test hello ACTION
    
    ARGUMENTS
      ACTION  list-distributions, get-distribution

    required: trueとすることで、必須にできます。

    $ ./bin/run hello
     ›   Error: Missing 1 required arg:
     ›   action  list-distributions, get-distribution
     ›   See more help with --help

    ちなみに配列形式なので、複数登録できます。

    // requiredじゃないものを追加する
      static args = [
        {
          name: 'action',
          required: true,
          description: 'list-distributions, get-distribution'
        }, {
          name: 'file'
        }
      ]

    requiredかどうかで表記が変わります。

    $ ./bin/run hello -h
    describe the command here
    
    USAGE
      $ test hello ACTION [FILE]
    
    ARGUMENTS
      ACTION  list-distributions, get-distribution
      FILE

    処理部分では以下のように書けばよさそうです。

        const {args, flags} = this.parse(Hello)
        switch (args.action) {
          case 'list-distributions':
            this.log('list distribution!')
            break
          case 'get-distribution':
            this.log('get-distribution!')
            break
          default: {
            this.error('uh oh!!!')
            // exit with status code
            this.exit(1)
          }
        }

    許可してないサブコマンドがきた場合はthis.errorthis.exitで終了させましょう。

    コマンドを追加する

    サブではなくメインのコマンドも追加してみましょう。追加もコマンドから実行できます。

    $ npx oclif command world
    
         _-----_
        |       |    ╭──────────────────────────╮
        |--(o)--|    │ Adding a command to test │
       `---------´   │      Version: 1.11.4     │
        ( _´U`_ )    ╰──────────────────────────╯
        /___A___\   /
         |  ~  |
       __'.___.'__
     ´   `  |° ´ Y `
    
       create src/commands/world.ts
       create test/commands/world.test.ts
    replacing <!-- usage --> in README.md
    replacing <!-- commands --> in README.md
    replacing <!-- toc --> in README.md
    
    $ ./bin/run world -h
    describe the command here
    
    USAGE
      $ test world [FILE]
    
    OPTIONS
      -f, --force
      -h, --help       show CLI help
      -n, --name=name  name to print

    簡単ですね。

    広告ここから
    広告ここまで
    Home
    Search
    Bookmark