license-checkerでnpmの依存ライブラリのライセンスを確認する

みなさんは日々の開発でライブラリなどを利用する際に、きちんとライセンスを確認していますか？

OSS（に限らないですが）を利用する際には、ライセンスに従う必要があります。
ライセンスによっては著作権表示を求められたり、それらを組み込んだソースコードもまた公開しなければならないものもあります。

今回は[license-checker](https://www.npmjs.com/package/license-checker)を用いて、Node.jsプロジェクト内で利用しているパッケージのライセンスを確認してみたいと思います。

## license-checkerを使ってみる

license-checker
https://www.npmjs.com/package/license-checker

ツールなのでグローバルにインストールしてもいいと思いますが、個人的には`devDependencies`に足したほうがチームで共有できたりCIなどでも利用できるのでよいかと思います。[^1]

ここでは適当に作った空プロジェクトで試してみましょう。
`license-checker`もまたnpmパッケージなので、色々な依存関係のあるパッケージが入ります。試すのにちょうど良いですね。

```sh
# 適当にプロジェクトを作る
mkdir hello
cd hello
yarn init -y

# license-checkerを入れる
yarn add -D license-checker
```

`package.json`の中身はこんな感じです。
`devDependencies`に`license-checker`がいます。

```json:package.json
{
  "name": "hello",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "license-checker": "^25.0.1"
  }
}
```

準備はできました。
では早速実行してみましょう。

```sh
yarn license-checker
```

```text:実行結果
├─ abbrev@1.1.1
│  ├─ licenses: ISC
│  ├─ repository: https://github.com/isaacs/abbrev-js
│  ├─ publisher: Isaac Z. Schlueter
│  ├─ email: i@izs.me
│  ├─ path: /Users/ynakamura/projects/sandbox/hello/node_modules/abbrev
│  └─ licenseFile: /Users/ynakamura/projects/sandbox/hello/node_modules/abbrev/LICENSE
├─ ansi-styles@3.2.1
│  ├─ licenses: MIT
│  ├─ repository: https://github.com/chalk/ansi-styles
│  ├─ publisher: Sindre Sorhus
│  ├─ email: sindresorhus@gmail.com
│  ├─ url: sindresorhus.com
│  ├─ path: /Users/ynakamura/projects/sandbox/hello/node_modules/ansi-styles
│  └─ licenseFile: /Users/ynakamura/projects/sandbox/hello/node_modules/ansi-styles/license
├─ array-find-index@1.0.2
│  ├─ licenses: MIT
│  ├─ repository: https://github.com/sindresorhus/array-find-index
│  ├─ publisher: Sindre Sorhus
│  ├─ email: sindresorhus@gmail.com
│  ├─ url: sindresorhus.com
│  ├─ path: /Users/ynakamura/projects/sandbox/hello/node_modules/array-find-index
│  └─ licenseFile: /Users/ynakamura/projects/sandbox/hello/node_modules/array-find-index/license
├─ asap@2.0.6
│  ├─ licenses: MIT
│  ├─ repository: https://github.com/kriskowal/asap
│  ├─ path: /Users/ynakamura/projects/sandbox/hello/node_modules/asap
│  └─ licenseFile: /Users/ynakamura/projects/sandbox/hello/node_modules/asap/LICENSE.md
            :
            :
```

めちゃくちゃいっぱい出てきました。

プロジェクトの直接の依存パッケージである`license-checker`だけでなく、その依存ツリーのパッケージ全てが出力されます。

`--direct`という直接の依存パッケージのみに絞るオプションもあるのですが、`v25.0.1`の時点ではうまく機能しませんでした。[^2]

## 色々な出力形式

### CSVやJSON

`--csv`や`--json`オプションを指定すると、CSVやJSONといった形式で出力してくれます。

```sh
yarn license-checker --csv
```

```csv:実行結果
"module name","license","repository"
"abbrev@1.1.1","ISC","https://github.com/isaacs/abbrev-js"
"ansi-styles@3.2.1","MIT","https://github.com/chalk/ansi-styles"
"array-find-index@1.0.2","MIT","https://github.com/sindresorhus/array-find-index"
"asap@2.0.6","MIT","https://github.com/kriskowal/asap"
"balanced-match@1.0.2","MIT","https://github.com/juliangruber/balanced-match"
"brace-expansion@1.1.11","MIT","https://github.com/juliangruber/brace-expansion"
"chalk@2.4.2","MIT","https://github.com/chalk/chalk"
"color-convert@1.9.3","MIT","https://github.com/Qix-/color-convert"
"color-name@1.1.3","MIT","https://github.com/dfcreative/color-name"
"concat-map@0.0.1","MIT","https://github.com/substack/node-concat-map"
            :
            :
```

### ライセンス毎のサマリー

`--summary`を指定するとライセンス毎のパッケージ数が出力されます。
ざっと把握するにはよいでしょう。

```sh
yarn license-checker --summary
```

```text:実行結果
├─ MIT: 32
├─ ISC: 19
├─ Apache-2.0: 2
├─ BSD-3-Clause: 1
├─ BSD-2-Clause: 1
├─ CC-BY-3.0: 1
├─ CC0-1.0: 1
└─ (MIT AND CC-BY-3.0): 1
```

## その他のオプションなど

他にも特定のパッケージを除外したり、`dependencies`のみを対象としたりするオプションがあります。

便利そうなオプションだけピックアップしておきます。
完全なオプション一覧はREADMEを見るか、`--help`で確認してください。

| オプション                 | 内容                                                                    |
|----------------------------|-------------------------------------------------------------------------|
| `--summary`                | ライセンス毎のサマリー表示る                                            |
| `--production`             | `dependencies`のみ出力する                                              |
| `--development`            | `devDependencies`のみ出力する                                           |
| `--unknown`                | 推測したライセンスを`unknown`として扱う                                 |
| `--onlyunknown`            | `unknown`と推測のみ出力する                                             |
| `--json`                   | JSON形式で出力する                                                      |
| `--csv`                    | CSV形式で出力する                                                       |
| `--exclude [list]`         | 指定のライセンスのパッケージを除外する（セミコロン区切り）              |
| `--excludePackages [list]` | 指定のパッケージを除外する（セミコロン区切り）                          |
| `--packages [list]`        | 指定のパッケージのみ出力する（セミコロン区切り）                        |
| `--excludePrivatePackages` | プラベートパッケージを除外する                                          |
| `--failOn [list]`          | 指定のライセンスがあったらエラーにする（セミコロン区切り）              |
| `--onlyAllow [list]`       | 指定のライセンス以外があったらエラーにする（セミコロン区切り）          |
| `--direct`                 | 直接の依存パッケージについてのみ出力する（v25.0.1現在うまく機能しない） |

## どうやってライセンスを検出しているのか

ところで`license-checker`はどうやってパッケージのライセンスを判定しているのでしょうか。

ドキュメントによると、`node_modules`の中を順番に見ていき、各`package.json`の`license`から拾うようです。
`package.json`から拾うのに失敗した場合は、`LICENSE`, `LICENCE`, `COPYING`, `README`といったファイルの中身を見て、ライセンス固有の文字列が含まれているかどうかで判定するようです。

## 応用編: CIやコミットフックなどで自動検出する

さて、ここからはCIなどに組み込んで、特定のライセンスが含まれていたら自動的に検出するような仕組みを考えてみたいと思います。

パッと思いつくのは`--failOn`オプションですね。
これを使えば特定のライセンスが含まれていた場合に、コマンドの終了ステータスが`1`になるので、これで検出することができそうです。

試してみましょう。

```sh
yarn license-checker --failOn MIT
```

```console:実行結果
Found license defined by the --failOn flag: "MIT". Exiting.
error Command failed with exit code 1.
```

うまくいったようです。

これをCIやGitの`pre-commit`フックなどに組み込んでおくことで、いつのまにかGPLなライブラリが入り込んでいた、といった事態を避けることができます。

### Gitの`pre-commit`に設定してみる

下記のような感じのスクリプトを`.git/hooks/pre-commit`に作ります（`chmod +x`するのを忘れずに）。

```sh
$EDITOR .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit
```

```sh
#!/bin/sh

# 許容しないライセンス
DISALLOW_LICENSES="GPL;GPL-2.0;GPL-3.0;LGPL;LGPL-3.0"

# package.json, package-lock.json, yarn.lock に変更があったときだけチェックする
if git diff --cached --name-only $against | grep --quiet --word-regexp -E 'package(-lock)?\.json|yarn\.lock'
then
    result=0
    yarn license-checker --failOn "$DISALLOW_LICENSES" >/dev/null || result=$?
    if [[ ! "$result" = "0" ]]; then
        yarn license-checker --csv
        exit 1
    fi
fi

exit 0
```

これで`package.json`, `package-lock.json`, `yarn.lock`に変更があったときにライセンスチェックが走って、許容しないライセンスが含まれていた場合は、コミットに失敗するようになります。

コミットのたびに走ると重いので、パッケージ情報に変更があったときだけ走るようにしてみました。

CIでも同じようなスクリプトを書いて、最初に実行しておけばよいでしょう。

### 注意点

`--exclude`や`--failOn`等に指定するライセンス名ですが、完全一致でしかマッチしないようなので注意が必要です。

例えば`GPL-3.0`なパッケージがあったとして、`--failOn GPL`とか`--failOn GPL3.0`とか`--failOn gpl-3.0`としても引っかかりません。`--failOn GPL-3.0`でなければいけません。

また、`(MIT OR GPL-3.0)`のような表記（デュアルライセンス）も`MIT`や`GPL-3.0`や`(GPL-3.0 OR MIT)`（逆順）では引っかかりません。`--failOn "(MIT OR GPL-3.0)"`などと指定しないといけません。

このあたりをもう少し柔軟に判定しようとすると、`grep`を駆使するとか、JSON形式をパースするといった対応が必要になってきます。

安全側に倒すなら`--csv`の結果を`grep -i gpl`とかでチェックして、デュアルライセンスなど問題なさそうなものを随時`--excludePackages`に足していくとかでもいいかもしれません。

ただし、`--excludePackages`の場合、バージョンが上がってライセンスが変更されている場合に、気づかずに除外してしまうという、別の問題はありそうです。

## 応用編: ライブラリとして使う

`license-checker`はライブラリとしても使えるインターフェースを提供しているので、前項のような複雑な条件に対応するスクリプトを書くとか、パッケージとライセンスの一覧をまとめた画面コードを生成するといった用途にも使えるかと思います。

以下のコードはREADMEからの引用。

```javascript
var checker = require('license-checker');
 
checker.init({
    start: '/path/to/start/looking'
}, function(err, packages) {
    if (err) {
        //Handle error
    } else {
        //The sorted package data
        //as an Object
    }
});
```

`packages`が各パッケージ情報の配列になっていて、その中にパッケージ名やバージョン、ライセンスなどの情報が入っているようです。

## その他のライセンスチェッカー

- [microsoft/license\-checker\-webpack\-plugin](https://github.com/microsoft/license-checker-webpack-plugin)
    - Microsoft製
    - JavaScript製
    - Webpack pluginとして動く
    - ビルド結果に含まれるものしかチェックできない
        - `devDependencies`とかチェックできない
- [github/licensed](https://github.com/github/licensed)
    - GitHub製
    - Ruby製
    - 色々な言語のプロジェクトに対応している
- [franciscop/legally](https://github.com/franciscop/legally)
    - JavaScript製
    - Node.jsプロジェクトのみ
    - きれいなテーブルで表示してくれる
    - package, License, READMEを別々に表示してくれる
- [go\-enry/go\-license\-detector](https://github.com/go-enry/go-license-detector)
    - Go製
    - 特定ディレクトリに対してチェックできる
    - `LICENSE`や`README`から判定
    - 依存ツリーのチェックはできない


[^1]: 難点は`license-checker`自身の依存ツリーも結果に出てきてしまうこと。
[^2]: https://github.com/davglass/license-checker/issues/191