GitHub Actions と semantic-release で npm の自動リリース環境を作る

# TL;DR
- `npm run hoge` だけなら Dockerfile を作成する必要はない。workflow を定義するだけ
- `.github/workflows/*.yml` は手元で編集しない。 github.com のウェブエディタを使う
- ベータ版なので登録が必要。仕様も変わるかも知れない
- 「GitHub Action とは何か」という説明は、ここではしない

# semantic-release

> semantic-release automates the whole package release workflow including: determining the next version number, generating the release notes and publishing the package.
This removes the immediate connection between human emotions and version numbers, strictly following the Semantic Versioning specification.
from https://semantic-release.gitbook.io/semantic-release/


- `npm publish` や GitHub の Release を自動化してくれる CLI ツール
- OSS 開発や、チーム開発で複数のパッケージをリリースするケースに向いている
    - コミットログをコピペして Release notes を書く手間がなくなり、パッチバージョンをより気軽に出せるようになる
- semantic-release 自体も OSS
    - https://github.com/semantic-release/semantic-release
- どちらかといえばサーバ（CI）の上で動かすために作られている
    - 比較として、 [np](https://github.com/sindresorhus/np) は **手動** リリースに向いているが、 semantic-release は **自動** リリースに特化している
    - JavaScript 製なので、 Node.js 上で動く


# GitHub Actions で自動リリース環境を作る

## 1. ワークフローを作成する

1. https://github.com にアクセスして、リリースを自動化したいリポジトリを開く
2. Create new file ボタンをクリックする
3. ファイル名の部分に `.github/workflows/release.yml` と入力する
4. 次のようにファイルに書き込んで、 master branch に Commit new file する  
**master に push 出来ない場合は、別のブランチを作成してコミットしても良い**

```yml
name: "Release package"
on: push

jobs:
  release_package:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1

      - name: "npm ci"
        run: npm ci

      - name: "npm run semantic-release"
        run: npm run semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

```

（なぜローカルで編集しないのかは後述する）

`.github/workflows` は特別なディレクトリで、その中にファイルが作られたことをトリガーにして、 GitHub 上に Actions タブが出現する。 **なお、環境変数をセットしていないので、このコミットによる実行結果は failed （失敗）になる**

## 2. npm token を設定する
 
1. http://npmjs.com にアクセスして、ログインする
1. 右上のアイコンをクリックして、 Tokens を選択
1. Create Token ボタンをクリックして、表示されたトークン（文字列）をメモする
2. GitHub のページに戻り、リポジトリの Settings タブを開く
3. ２カラムの設定画面が開くので、左にあるメニューから Secrets を選択する
4. Add a new secret をクリックする
5. Name のところに `NPM_TOKEN` と入力する
6. Value のところに、先ほどメモしたトークンを貼り付ける
7. Add secret ボタンをクリックする

## 3. npm run semantic-release を設定する

ここからは、ローカルリポジトリでの作業になる

1. 最初に`.github/workflows/release.yml` を作成したブランチに移動する（とくに設定を変えていないなら master でよい）
1. `npm install -D semantic-release` を実行する
1. `package.json` の `"scripts"` の中に、 `"semantic-release": "semantic-release"` を追加する
1. もし `"scripts"` の `"test"` というキーが `exit 1` で終わっている（`npm init` の初期設定から変更していない）なら、 `"test": "exit 0"` とかに書き換えておく
2. これらの変更をコミット・プッシュする

上記の変更を push すると、最初に作成したワークフローが GitHub Actions 上で実行される。その結果は、リポジトリの Actions タブで確認できる。上手くいけば、最初のリリースが行われるはずだ

ただし、 master ブランチ以外で作業をしている場合は、実行には成功するものの、リリースがスキップされる。これは semantic-release の仕様で、初期設定では、master ブランチの内容がリリースされる。 master ブランチへの Pull Request を作成して Merge commit しよう

## 4. コミットとリリース

セットアップ済みのリポジトリで semver を意識したコミットをする方法

semantic-release は、リポジトリの master ブランチにプッシュされた時に新しいバージョンのリリースを試みるが、実際にどのくらいバージョンを上げるのか（あるいはリリースをしないのか）は、これまでのコミットメッセージによって決まる

公式による説明は [ここ](https://github.com/semantic-release/semantic-release#commit-message-format) で読める。以下は意訳である

- ライブラリの中身には一切変更がない場合
    - `chore: ` をコミットメッセージの前につける
    - e.g. `chore: update README.md`
- 全てのライブラリ利用者に、今すぐ新しいバージョンを使わせたい場合
    - `fix: ` をコミットメッセージの前につける
    - e.g. `fix: reference error when button clicked`
- 新機能追加や小さな仕様変更など、バージョンアップするまでの猶予を与えたい場合
    - `feat: ` をコミットメッセージの前につける
    - e.g. `feat: better animations on UI`
- 破壊的な変更を伴う変更で、旧バージョンのリリースをある程度続ける覚悟がある場合
    - `perf: ` をコミットメッセージの前につける
    - 本文に `BREAKING CHANGE: ...` を書き込むとさらによい
    - e.g. `perf: remove old APIs`

ほとんどの工程が自動化されていれば、バージョンが沢山あっても困ることは基本的にない

そのコミットが `fix` なのか `feat` なのかを明確に判断できるよう、コミットをなるべく小さくして、 小まめに master に push または merge するのが、現代の JavaScript ライブラリ開発におけるベストプラクティスと言っていいだろう

また、 semantic-release の beta 版 （semantic-release 自体も semantic-release でリリースされている）には、 master ブランチ以外のブランチからリリースする方法や、 beta-1 などのバージョンをリリースする方法も既に実装されている。ワークフローによっては beta 版を採用した方が便利かも知れない

# なぜ release.yml をローカルで編集しないのか

これが今回最もハマったポイントで、ローカルで編集したファイルを origin に push 出来ないのである

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Github Actions を試しているんだけど、 .github/workflows/*.yml を push すると<br><br> ! [remote rejected] master -&gt; master (refusing to allow an integration to create or update .github/workflows/release_package.yml)<br><br>って出て push できない。GitHubのウェブサイト上で編集すると push できる</p>&mdash; 寺本.hackforplay(); (@teramotodaiki) <a href="https://twitter.com/teramotodaiki/status/1170931998640070656?ref_src=twsrc%5Etfw">September 9, 2019</a></blockquote>

git の知識がないばかりに、特定に時間を要してしまったが、要するにこういうことだった↓

- GitHub Actions に登録している人のリポジトリ
    - `.github/workflows/release.yml` ： **プッシュできない**
    - `.github/workflows/hoge/fuga` ： **プッシュできない**
    - `.github/release.yml`：プッシュできる
    - `README.md`（など）：プッシュできる
- GitHub Actions に登録していない人のリポジトリ
    - `.github/workflows/release.yml` ：プッシュできる
    - `.github/workflows/hoge/fuga` ：プッシュできる
    - `.github/release.yml`：プッシュできる
    - `README.md`（など）：プッシュできる

GitHub Actions のチュートリアルによれば、こういうことである

> The main.yml file cannot be edited using an integration. Try editing the file using the web interface, or your command line.

> It is possible that you are using an integration (like GitHub Desktop or any other tool that authenticates as you and pushes on your behalf) if you receive a message like the one below:

> To https://github.com/your-username/your-repo.git
 ! [remote rejected] your-branch -> your-branch (refusing to allow an integration to update main.yml)
error: failed to push some refs to 'https://github.com/your-username/your-repo.git'
from https://github.com/teramotodaiki/hello-github-actions/pull/2#issuecomment-529283739

普段は GitHub Desktop や VSCode から git を利用しているので、 `an integration` による edit だったということであろう。言わずもがな、 github.com で Create new file するのは `the web interface` による edit である

しかし、 macOS のターミナルから `git push` してみても、同様のエラーが確認された。では、 `your command line` とは一体何だろうか？ ソフトウェアというより、 GitHub による Authentication の方法で決められている気がする。すなわち、 3rd party が鍵を保有しているケースでは、特定のディレクトリを変更するような commit を含む API リクエストが弾かれている可能性だ。これは、第三者がデータを盗んだり壊したりするためのセキュリティホールをなるべく作らないための措置であろう。正式リリースまでには仕様が変わっている可能性もある

# エモ

- 現時点で 22k ものプロジェクトに採用されている semantic-relesae だが、今後もっとユースケースが広がっていくと思う
- 自動リリースの恩恵を受けるには、開発者のワークフローも変える必要がある。痛みを伴う場合もあるだろうが、今後も「リリースのフレームワーク」としてソフトウェア開発のデファクトスタンダードになって欲しい
- 今回は使わなかったが、 GitHub Actions は Dockerfile をサポートしている。また、 GitHub Actions にはサードパーティー製のワークフローをウェブ上のビジュアルエディタから利用する GUI もあり、再利用可能なワークフローや Dockerfile がどんどん公開されていくことが予想される
- これらの要素技術は、既に世の中にいくつかの CI サービスとして存在している。しかし、 GitHub が公式にローンチすることで、 CI は瞬く間に大衆化されるだろう。最も影響を受けるのは、今まで CI を使っていなかった層である。例えば、スマホアプリ開発者やゲーム開発者がそうだ。 GitHub にソースをあげれば、あとはビルド、テスト、コードレビュー、そしてリリースから QA に至るまで、すべて GitHub で完結する。 `git push` は、まさに魔法の呪文なのだ