Node.js スクリプトをコマンドのように使えるようにする方法

2019年、あけましておめでとうございます。今年も宜しくお願い致します。


今日は、Node.js で書いたちょっとしたスクリプトを、コマンドとして使えるようにする手順をまとめる。大きく2種類のやり方があるのでそれぞれ紹介する。

試した環境は macOS Mojave・Node.js v10.7.0・npm v6.1.0。

目次

外部パッケージに依存せず単一の JS ファイルで済む場合

依存モジュールがなく、単一の JS ファイルで済むようなコードであれば、そのファイルを PATH が通っているところに置くだけでコマンド化できる。

先にコード全量

# PATH が通っているところならどこでも
$ cd /usr/local/bin/

# サンプルスクリプトを作る。Shebang 重要
$ cat << EOF > my_node_command
> #!/usr/bin/env node
> console.log(process.cwd());
> EOF
  # コマンドを実行したカレントディレクトリを表示するだけのコード

# 実行権を付与する
$ chmod +x my_node_command

# 好きなところに移動して使う
$ cd ~/work/
# 実行
$ my_node_command
/Users/Neos21/work
  # コマンドを実行したカレントディレクトリが表示される

以下、もう少し個別に詳細を説明する。

PATH が通っているところにスクリプトファイルを置く

コマンドとして実行するには、環境変数 PATH が通っているディレクトリに、拡張子なしのファイルとしてスクリプトを置く必要がある。

上述の例では /usr/local/bin/ という、大抵 PATH に設定されているディレクトリを選択したが、その他にインストールしたファイル群と混ざりそうなので、僕はユーザホーム直下に ~/bin/ というディレクトリを作り、コレを ~/.bash_profile にて環境変数 PATH に追加して利用している。

Node.js スクリプトとして動作させるための Shebang を設定する

Node.js スクリプトの Shebang は以下のとおり。

#!/usr/bin/env node

コレがないと Node.js 製のスクリプトとして解釈してもらえない。

スクリプトを書く

スクリプトファイルの1行目に Shebang を書いたら、後はスクリプトを書くだけ。

外部モジュールは、fspath など、Node.js 本体に組み込みのモジュールであれば require() して利用できた。それ以外の npm install して使えるようなパッケージは、グローバルインストールしてあっても使うことはできない。外部 npm パッケージを使いたい場合は、後述する方法で実現できる。

さて、一例だが、こんなコードならスクリプトファイル単体で動かせた。

#!/usr/bin/env node

const fs   = require('fs');
const path = require('path');
const util = require('util');

// fs.readFile() を Promise 化する
const readFile  = util.promisify(fs.readFile);

console.log('カレントディレクトリに package.json があったらその中身を出力する');

readFile(path.join(process.cwd(), 'package.json'), 'UTF-8')
  .then((packageJson) => {
    console.log(packageJson);
  })
  .catch((error) => {
    console.error(error);
  });

Util.promisify() なる便利なヤーツが Node.js に組み込まれていたので、fs.readFile() をコレで Promise 化した。

__dirname だと、スクリプトファイルの格納場所が返されてしまうので、スクリプトファイルを呼び出したカレントディレクトリを取得するには、process.cwd() を使っている。

ファイルに実行権を付与する

先程のようなスクリプトを my_node_command というファイル名で保存したとして、コレをコマンドのように実行できるようにするには、chmod コマンドによるファイルのアクセス権限の変更が必要。実行権限を付けないといけない。

$ chmod +x my_node_command

コレで OK。Node.js スクリプトとして JS ファイル単体で済ませられるモノなら、このようなやり方でコマンド化できた。

外部モジュールに依存する Node.js スクリプトをコマンド化する場合

外部モジュールに依存する Node.js スクリプトをコマンド化したい場合は、npm パッケージをローカルで作り、それをグローバルインストールすることで実現できる。

まずは適当な作業ディレクトリを作り $ npm initpackage.json が生成されるので、$ npm install --save で好きなライブラリをインストールし、それを使用したスクリプトを作っていく。

最近のトレンドだと、CLI ベースで動作するスクリプトは、プロジェクトルートに cli.js というファイル名で置き、package.json からは以下のようにコマンド名を定義してやるのが多い。

{
  "name": "neo",
  "version": "1.0.0",
  "description": "Neo's Commands",
  "main": "./lib/index.js",
  "bin": {
    "neo": "./cli.js"  // 「neo」コマンドで動作するようにする
  },
  "dependencies": {
    // 依存パッケージ…
  }
}

cli.js の1行目は #!/usr/bin/env node という Shebang で始めること。

で、こんな風に作ったスクリプトを $ neo コマンドで実行できるようにするには、この作業ディレクトリで $ npm install -g する。

package.json があるディレクトリで npm i -g とすると、そのディレクトリ配下の資材がグローバルインストールされる。MacOS で試した限りは、資材がコピーされるのではなくこのディレクトリへのエイリアスが貼られるので、一度グローバルインストールさせた後にスクリプトを書き換えたり、別のライブラリをローカルインストールしたりしても、npm install -g で登録し直す必要はなく、即座に変更が反映される。

# こんな作業ディレクトリだとする
$ pwd
/Users/Neos21/work/neo

# グローバルインストールすると
$ npm install -g
/usr/local/bin/neo -> /usr/local/lib/node_modules/neo/bin/neo
+ neo@1.0.0
updated 1 package in 0.225s

# 確かにエイリアスが貼られていることが分かる
$ ls -l /usr/local/lib/node_modules/
total 0
lrwxr-xr-x   1 Neos21  admin   45 11 30 11:03 neo -> ../../../../Users/Neos21/work/neo
drwxr-xr-x  25 Neos21  admin  800  7 23 16:21 npm

エイリアスになるので、「作業ディレクトリ」とは言いながらも、ファイルを消したりやたらと移動させたりしないよう注意。

この作業ディレクトリを Git 管理しておいて、PC 環境が変わった時は git clonenpm installnpm install -g としてやれば、元の環境が復元できそうだ。