自作パッケージの実行ファイルをnpm installした際にnode_modules/.bin/に登録する

# TL;DR
* package.jsonの中でbinとして登録しておくと、`npm install`のときにnode_modules/.binの配下に勝手に実行ファイルへのリンクが追加される。
* package.jsonのbinには複数設定できる。
* 複数設定してもいいけど、個人的にはハブとなる実行ファイル1つを登録して、振り分ける方法にした。

# はじめに
npmを使ってinstallをおこなうと、node_modules/.binの中に実行ファイルのリンクが貼られます。
そういえば、自分の作ったパッケージでこれを実現する方法を知らなかったな、というわけで調べました。

最終的なプロジェクトの構成図は以下のようになっています。

プロジェクトA（実行ファイルを作成して、`npm publish`するパッケージ）
![ma2-cmd.png](https://qiita-image-store.s3.amazonaws.com/0/33347/147543e9-c5a9-26f8-e207-dc00911401c0.png)

プロジェクトB（プロジェクトAをpackage.jsonのdependenciesに追加してインストールし、動作検証するパッケージ）
![ma2-cmd-runner.png](https://qiita-image-store.s3.amazonaws.com/0/33347/2280353b-ff7f-4398-2ed3-5b72ceae29f6.png)



# `npm install`した時にnode_modules/.bin/ディレクトリに実行ファイルを登録
[npm package.json 日本語版 取扱説明書](http://liberty-technology.biz/PublicItems/npm/package.json.html)にやり方が書いてありました。

> 例えば npm ではこう指定されています:
> { "bin" : { "npm" : "./cli.js" } }

これで解決ですね。
あとは、個人的にスクリプト系を一つのパッケージにまとめておきたかったので、package.jsonのbinに複数指定できるのか試してみました。

```json:package.json
"bin": {
	"hoge": "src/hoge.js",
	"fuga": "bin/fuga.js"
}
```

各jsファイルは以下のように適当です。

```js:src/hoge.js
#!/usr/bin/env node
console.log("Hello");
```

```js:bin/fuga.js
#!/usr/bin/env node
console.log("World");
```

## 配置確認
作業手順
1. プロジェクトAを`npm publish`
2. プロジェクトBを新規作成
3. プロジェクトBのpackage.jsonに、プロジェクトAのpackage.jsonのnameで指定した名前をdependenciesに追加
4. プロジェクトBで`npm install`して、node_modules/.bin/配下に`hoge`と`fuga`が入っていれば完了

以上のことから、次の2点がわかりました。

1. 実行ファイルは複数指定できる
2. パスがあっていれば別にsrcディレクトリだろうが、binディレクトリだろうが関係なし

ちなみに私はverdaccioを使ってプライベートのリポジトリを構築した状態で試したので、ゴミのような履歴が残るとか気にせず何度も`npm publish`しながら試しました。
npmのアカウントを作って、`npm publish`をすると公開されるので、それが嫌な方はご注意を。

## 実行ファイルの実行確認

```sh
$ npm install
$ node_modules/.bin/hoge
Hello
```

はい、動きました。


# 実行ファイルを1つにしたい
個人的には`hoge`とか`fuga`とか名前もかぶりそうですし、グローバルにインストールするのは難しいなと感じました。
解決策として、名前がかぶらなさそうな実行ファイル名をつけて、その引数としてhogeとfugaを指定する方法を取ることにしました。

何はともあれ、起点となるjsファイルとして、プロジェクトAに`ma2-cmd.js`を新規作成します。

```js:ma2-cmd.js
#!/usr/bin/env node
const childProcess = require("child_process");
const execSync = childProcess.execSync;

const cmdPattern = 
	"  hoge || h\n" +
	"  fuga || f\n";

const argv = process.argv.slice(2);
if (argv == null || argv.length < 1) {
	throw new Error("実行するコマンドを指定してください。\n" + cmdPattern);
}

const map = {
	"hoge": "src/hoge.js",
	"h": "src/hoge.js",
	"fuga": "bin/fuga.js",
	"f": "bin/fuga.js"
};

const cmdTarget = map[argv[0]];
if (cmdTarget == null) {
	throw new Error("指定されたコマンド名に紐づくコマンドがありません。\n" + cmdPattern);
}

let args = "";
if (argv.length > 1) {
	args = " " + argv.slice(1)
		.join(" ");
}

const res = execSync("node node_modules/${プロジェクト名}/" + cmdTarget + args).toString();
console.log(res);
```

package.jsonも修正します。

```json:package.json
"bin":{
	"ma2-cmd": "ma2.js"
}
```

あとは同じようにpublishして更新します。

1. プロジェクトAのpackage.jsonのバージョンをあげる。（`npm version patch`とかみたいにコマンドでもいいし、手動であげてもいいです）
2. プロジェクトAで`npm publish`
3. プロジェクトBで`npm update`

そして、コマンドを打ちます。

```sh
$ node_modules/.bin/ma2-cmd hoge
Hello
```

# おまけ

## コマンドの短縮

以下のように指定したことで、コマンドを短縮して実行できるようにしています。

```js:ma2.js
const map = {
	"hoge": "src/hoge.js",
	"h": "src/hoge.js",
	"fuga": "bin/fuga.js",
	"f": "bin/fuga.js"
};
```
つまり、以下のコマンドは同じファイルを実行します。

```sh
$ node_modules/.bin/ma2 hoge
$ node_modules/.bin/ma2 h
```

## 各コマンドに対する引数の設定
今回のサンプルコードでは使っていませんが、引数を渡せるようにしています。

抜粋すると、このあたりです。

```js:ma2-cmd.js
:
const argv = process.argv.slice(2);
:
const cmdTarget = map[argv[0]];
:
let args = "";
if (argv.length > 1) {
	args = " " + argv.slice(1)
		.join(" ");
}


const res = execSync("node node_modules/${プロジェクト名}/" + cmdTarget + args).toString();
console.log(res);
```

## jsファイルの先頭行のnode宣言
先頭行の宣言、最初必要だと知りませんでした。

```js
#!/usr/bin/env node
```
他の.binに入っている先人の知恵を物色したところ発見し、調べたらnodeを使うという宣言であると知りました。
参考：[Node.jsを使ってCommand line ツールことはじめ](http://mid0111.hatenablog.com/entry/2014/06/08/153613)

ちなみに、先頭に指定しなかった場合はエラーが出ます。

```sh
$ node_modules/.bin/ma2-cmd hoge

node_modules/.bin/ma2: line 1: syntax error near unexpected token `('
node_modules/.bin/ma2: line 1: `const childProcess = require("child_process");`
```

先頭に`node`を付けると動きます。

```sh
$ node node_modules/.bin/ma2-cmd hoge
Hello
```