oclif
コマンドラインツールを作ろうと思い立ち、たまたまoclifというものに出会ったので使い方メモ
oclifを使うと、TypeScript/JavaScripで自作のCLIがかなり簡単に作れる。
oclif (https://oclif.io) は、heroku cli をつくったチームがリリースした、CLIツール作成のフレームワーク。
コマンドに渡されるパラメータやフラグの処理、ヘルプの表示など、ベースとして必要なものがスキャフォールドできるようになっている。
Mac, Windows, Linux 用にインストーラーにパッケージングして配布できるなど、CLIとして必要なものを一通り取り揃えている。
OAuthのアクセストークンをコマンドラインから取り出す
この記事では、コマンドラインからOAuth 2.0(Authorization Code Grant)のアクセストークンを取り出す方法も、Box APIを例にして、合わせて書く。
ちなみにBox社が提供するBox cliもoclifを利用して作られている。
通常、Auth2.0はブラウザ上で認可フローが行われることが前提となっている。
通常の動作は、以下のとおり。
- ブラウザをユーザーに向け開きログイン画面を表示
- ユーザーがログインする
- ブラウザ上でリダイレクトされ、アプリケーションに認可コードが渡される
- アプリケーションは認可コードを受け取り、アクセストークンに交換、以降アクセストークンがあればリソースにアクセス可能
1〜3は必ずブラウザで実行する必要があるが、4以降は必ずしもブラウザでなくてもよく、コマンドラインツールからも利用可能。
OAuthのアクセストークンがコマンドラインで使えると、便利にAPIを呼び出せるようになるので、その方法をメモしておきたい。
ちょっとごちゃまぜ感があるが、半分個人的な備忘録なので許してほしい。
oclifのインストール
nodeとnpmは入ってる前提ですすめる。
必ずしもそうしなくていいが、とりあえずoclifをグローバルにインストールしておく。
npm i -g oclif
oclifでCLIプロジェクトを生成
これだけで、cliのプロジェクトの基本的なテンプレートが生成できてしまう。とても簡単。
mycli
の部分は好きな名前に変える
色々聞かれるので、適当に答える
oclif generate mycli
生成したコマンドの実行
cd mycli
./bin/dev hello world
生成された、サンプルのコマンドが実行される。
./bin/dev
は、リリースした後は mycli
に置き変わる。
./bin/dev
は node-ts
を使っているので、TSファイルのコンパイルなしで実行できる。
コンパイルするときは、npm run build
コンパイル済みのjsファイルを実行するときは、./bin/dev
の代わりに、./bin/run
を使う
macos用のインストーラーパッケージを作成
macos以外にも、WindowsやLinux用も作れるけど、ここではmacosのやり方だけを書く。
pakcage.json
を開き、以下の値を追加。
他のアプリと被らない名前を付ける。
...
"oclif":{
"macos": {
"identifier": "com.example.mycli"
}
}
以下を実行 (macos用)
oclif pack macos
distフォルダにxx.pkgというインストーラーが生成されるので、これでインストールが行える
インストールすると、以下のコマンドが実行できるようになる。
mycli hello world
OAuthでログインするコマンドを追加する
以下の手順で、OAuthのログインのためのコマンドを追加する。ここではloginという名前で作る。
# プロジェクトルートで実行
oclif generate command login
src/commands/login.ts
が生成される。
必要なライブラリを追加する
npm i express box-node-sdk cli-ux
cli-ux
は、oclifのサイドプロジェクト。URLをブラウザで開く、ハイパーリンクを表示、処理をウェイト、アクションの終了待ちの表現、などいろいろcliで便利なUXを提供してくれる。
BOXでアプリ設定を作成
Boxからマイアプリ(開発者コンソール)を開く → アプリの新規作成をクリック → カスタムアプリ → ユーザー認証(OAuth 2.0)を選択 → 作成
作ったアプリの構成タブを開き、クライアントIDとクライアントシークレットをコピーしておく。
リダイレクトURIに、http://localhost:3000/redirect
を入れる。
login.tsを書き換える
OAuthの取り出しかたは、この記事を参考にした。
https://thecodebarbarian.com/oauth-in-nodejs-cli-apps.html
ちなみにBox Node SDKが現時点でTypescriptのサポートが甘く、エラーが出たためファイル先頭2行を書いている。
ポイントは、コマンドを実行すると、localhostでサーバーが立ち上がることと、Promiseのハンドラの第一引数resolveを外だしにしてコード取得を待ち合わせしていること。
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import {Command} from '@oclif/core'
import cli from 'cli-ux'
const express = require('express')
const BoxNodeSDK = require('box-node-sdk')
const clientID = 'xxxxxxxxx' // BOXマイアプリからコピーしたクライアントID
const clientSecret = 'xxxxxxxxx' // BOXマイアプリからコピーしたクライアントシークレット
// box sdk
const sdk = new BoxNodeSDK({clientID, clientSecret})
// Boxの認証用URLを生成
const loginUrl = sdk.getAuthorizeURL({
// eslint-disable-next-line camelcase
client_id: clientID,
// eslint-disable-next-line camelcase
response_type: 'code',
})
export default class Login extends Command {
public async run(): Promise<void> {
// Boxがリダイレクトするlocalhostを一時的に立ち上げるため、サーバーを用意
// Boxのアプリ設定で、http://localhost:3000/redirect にリダイレクトするようになっているので、
// このコマンドを実行し、ブラウザでログインし、リダイレクトされるまでの間だけローカルにサーバーを立ち上げる。
const app = express()
// Promiseのハンドラ内部に渡される第一引数 resolveを外だしにして、後から使えるようにする。
// 取り出したresolveを実行すると、pの処理が終了する。
let resolve: any
const p = new Promise(_resolve => {
resolve = _resolve
})
// ローカスのサーバーで/redirectが呼び出されたときの処理
app.get('/redirect', function (req: any, res: any) {
// 上で取り出したresolveを呼び出す
resolve(req.query.code)
res.end('')
})
// サーバーを立ち上げる
const server = await app.listen(3000)
// Boxの認証画面をブラウザで開く。
cli.open(loginUrl)
// redirectの処理で、認可コードを取りだしてresolveが呼び出されるのを待つ
const code = await p
// 取り出した認可コードを表示
this.log(`the code is ${code}`)
// 不要になったので待たずにサーバーを停止
server.close(() => {
this.log('server stopped')
})
// 認可コードからアクセストークン類を取り出す(ここから通常のBOX APIの利用方法)
const tokenInfo = await sdk.getTokensAuthorizationCodeGrant(code)
// アクセストークンを渡して、box api clientを作成
const client = sdk.getBasicClient(tokenInfo.accessToken)
// ユーザー情報を取り出す
const user = await client.users.get(client.CURRENT_USER_ID)
this.log(user)
}
}
loginの実行
./bin/dev login
インストーラーパッケージにしてからインストールしても、同じように動作する
mycli login
注意1:最近のBOXのバグ?で、既にBOXにログイン済みのブラウザで開くと認証画面が表示されないことがあるので、ログアウトしてやり直す。
注意2:BOXのEnterpriseの設定でカスタムアプリを許可していない場合、認証画面は開くことができない。