GitHub Actionsでnpmに自動でリリースするworkflowを作ってみた

みなさんこんにちは。
GitHub Actionsを使ってnpmに自動リリースできたら便利だと思いました。やりました。

目次

想定しているフロー

雑で申し訳ないですが下のようなフローを想定しています。

  1. 各々がtopicブランチで開発を行う
  2. topicブランチで開発が終わったものをversionブランチにmergeしていく
  3. mergeが全て終わったら、package.jsonのバージョンをあげる
  4. topicブランチをmasterブランチにmergeする
  5. masterへのmerge契機でGitHub Actionsを実行してリモートリポジトリへのタグ付けとnpmへのリリースを行う
master --------------------------------->
             | リリースする
version------+-------------------------->
             | featureブランチにversionブランチをmergeする
topic/ ------+

要はmasterブランチへのマージを契機にpackage.jsonのバージョンをみてリモートリポジトリへのタグ付けとnpmへのリリースを行うということです。
実際に設定ファイルを確認していきます。

GitHub Actionsの設定

こちらのリポジトリに実際に設定しています。
必要に応じてご参照ください。

ファイルの中身はこんな感じになっています。
おって内容を説明していきます。

name: automatic release
on:
  push:
    branches:
      - master
    tags:
      - "!*"
jobs:
  release:
    name: check version, add tag and release
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v1
      - name: setup Node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x
          registry-url: 'https://registry.npmjs.org'
      - name: install can-npm-publish and dependencies
        run: |
          npm install can-npm-publish
          npm ci
      - name: check version and add tag
        env:
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
          REPO: ${{github.repository}}
          COMMIT: ${{github.sha}}
        run: ./release.sh
      - name: test
        run: npm run test
      - name: build
        run: npm run build
      - name: release
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

ファイル先頭の下記部分でまずmasterブランチへのpush時でかつtagの付与出ない場合に実行するように制御します。

on:
  push:
    branches:
      - master
    tags:
      - "!*"

ジョブの先頭部分ではNode.jsの環境を整えるための準備をしています。
今回はレジストリに対して操作を行うので、withを使用してバージョンとレジストリを宣言しています。

jobs:
  release:
    name: check version, add tag and release
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v1
      - name: setup Node
        uses: actions/setup-node@v1
        with:
          node-version: 10.x
          registry-url: 'https://registry.npmjs.org'

次の部分が今回のGitHub Actionsでそこそこ重要な役割を果たします。

jobs:
  release:
    name: check version, add tag and release
    runs-on: ubuntu-latest
    steps:
      ~~~~
      - name: install can-npm-publish and dependencies
        run: |
          npm install can-npm-publish
          npm ci
      - name: check version and add tag
        env:
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
          REPO: ${{github.repository}}
          COMMIT: ${{github.sha}}
        run: ./release.sh

まずcan-npm-publishはすでにpackage.jsonに記載されているバージョンがレジストリにアップロードされているかを確認するツールです。
./release.shcan-npm-publishを使用してすでにリリースされていないかと、package.jsonのバージョン情報を元にリモートリポジトリにタグをpushするためのスクリプトです。下記のような内容になっています。
タグのpushについてはGitHub API v3を使用して行なっています。
またGitHubのアクセストークンについてはActionsが回るときに勝手に入れてくれるので特に設定は必要ありません。

#!/bin/bash

# check this version is enable to release or not
npx can-npm-publish
if [ $? -eq 1 ] ; then
  exit 255
fi

# get current version from package.json
TAG=$(cat package.json | grep version | cut -d " " -f 4 | tr -d "," | tr -d '"')
echo "add new tag to GitHub: ${TAG}"

# Add tag to GitHub
API_URL="https://api.github.com/repos/${REPO}/git/refs"

curl -s -X POST $API_URL \
  -H "Authorization: token $GITHUB_TOKEN" \
  -d @- << EOS
{
  "ref": "refs/tags/${TAG}",
  "sha": "${COMMIT}"
}
EOS

そしたら最後の部分です。testとbuildについてはそのままなのですが、publishに関しては他のCIツール都扱い方が若干異なります。
まずはリポジトリのsecretsにNPM_TOKENという名前でトークンを追加します。
secretsに登録した内容をNODE_AUTH_TOKENに渡せばそれだけでpublishが実行できます。
レジストリがGitHubのに変わったとしても、NODE_AUTH_TOKENに必要なトークンを渡せば差分を吸収して設定してくれるので非常に便利です。

jobs:
  release:
    name: check version, add tag and release
    runs-on: ubuntu-latest
    steps:
      ~~~~
      - name: test
        run: npm run test
      - name: build
        run: npm run build
      - name: release
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

あとはこの設定をmasterに取り込めば簡単に自動化ができます。

さいごに

設定もかなり簡単にできて便利なので今後もGitHub Actionsを使っていきたいです。