CircleCI version2.1記述でnpmプロジェクトを自動tag/publish

会社でGitlab CIはいじっていたのだが、Circle CIに初めて手をつけてみた。

で、基本のサンプルなど読みながら勉強していたのだが、
タギングとパッケージ公開それぞれのサンプルは多々あったものの、
タギングとパッケージ公開を一緒に行っている簡単なサンプルが見つからなかったので落ち着いた方法を載せておく。
調べ方が悪かっただけな気もする。

# やりたいこと
- github上にあるnpmプロジェクトをCircleCIでCIしたい
- masterにマージされたときだけタギングしたい
- タギングされたらnpmにパッケージを公開したい

# 方法1: タギングとパッケージ公開を一連で行う。

一番素直に書くとこうなる。シンプルでわかりやすい。

- タギングしたときにワークフローが無限ループしてしまうのを防ぐのに、
`npm version`するときのメッセージに`[ci skip]`を入れてやる。
- `${github_email}`と`${npm_token}`は環境変数から渡してやる。

必ずpatchリリースになってしまう点に目を瞑ればこの方法が一番シンプル。

```yaml:.circleci/config.yml
version: 2.1

executors:
  default:
    docker:
      - image: circleci/node:10.15

commands:
  restore_modules:
    steps:
      - restore_cache:
          keys:
            - v1-npm-deps-{{ checksum "package-lock.json" }}
            - v1-npm-deps-
  save_modules:
    steps:
      - save_cache:
          key: v1-npm-deps-{{ checksum "package-lock.json" }}
          paths:
            - node_modules

jobs:
  setup:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run: npm install
      - save_modules
  test:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run:
          name: Test
          command: npm test
      - store_artifacts:
          path: coverage
  tag_and_publish:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run:
          name: Push a new tag
          command: |
            git config --global user.name  "EnKen"
            git config --global user.email "${github_email}"
            npm version patch -m "v%s tagged [ci skip]"
            git push --tags origin master
      - run:
          name: Publish to npm
          command: |
            echo "//registry.npmjs.org/:_authToken=${npm_token}" > ~/project/.npmrc
            npm publish

workflows:
  main:
    jobs:
      - setup
      - test:
          requires:
            - setup
      - tag_and_publish:
          requires:
            - test
          filters:
            branches:
              only: master #masterブランチでだけ実行
```


# 方法2: タギングとパッケージ公開を別々のワークフローで行う。

基本的なやることは変わらないが、
「masterにマージ -> テスト -> タギング」までを一連のワークフローで実施し、
パッケージ公開はタギングをトリガーに別のワークフローで実施する方法。

```yaml:.circleci/config.yml
version: 2.1

executors:
  default:
    docker:
      - image: circleci/node:10.15

commands:
  restore_modules:
    steps:
      - restore_cache:
          keys:
            - v1-npm-deps-{{ checksum "package-lock.json" }}
            - v1-npm-deps-
  save_modules:
    steps:
      - save_cache:
          key: v1-npm-deps-{{ checksum "package-lock.json" }}
          paths:
            - node_modules

jobs:
  setup:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run: npm install
      - save_modules
  test:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run:
          name: Test
          command: npm test
      - store_artifacts:
          path: coverage
  push_tag:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run:
          name: Push a new tag
          command: |
            git config --global user.name  "EnKen"
            git config --global user.email "${github_email}"
            npm version patch -m "v%s tagged [ci skip]"
            git push --tags origin master
  publish_package:
    executor: default
    steps:
      - checkout
      - restore_modules
      - run:
          name: Publish to npm
          command: |
            echo "//registry.npmjs.org/:_authToken=${npm_token}" > ~/project/.npmrc
            npm publish

workflows:
  main:
    jobs:
      - setup
      - test:
          requires:
            - setup
      - push_tag:
          requires:
            - test
          filters:
            branches:
              only: master
  publish:
    jobs:
      - setup:
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/
      - publish_package:
          requires:
            - setup
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/
```

- publishのワークフローでは`filters`記述で`branches`のトリガーをすべて無視し、
`tags`(vで始まるやつだけ)のトリガーでのみ動作するように記述する。
- publish_packageのジョブだけでなくsetupのジョブにも同じtagsの記述が条件を記述しないとトリガーが作動してくれない点に注意。

若干面倒くさいが、この方法であればminorやmajorバージョンを上げたい場合、
ローカルで手動タギングする運用が可能である。

```bash
npm version minor -m "%s tagged [ci skip]"
```
という感じで`[skip ci]`をメッセージに入れてバージョンを上げれば、
pushした際にmainのワークフローを実行せず、publishだけ行うことができる。

# 結論

ということで、今回はパッケージ公開の柔軟さから方法２の方法を選択した。
ワークフローがループする問題の対処法が`[ci skip]`しか思いつかなかったのだが、
もうちょっとスマートな方法あったりするんだろうか。