🙌

ド素人がパッケージリリースを自動化した話

2024/07/06に公開

🌼 はじめに

こんにちは、github acions ド素人です。

最近、社内で自作したUIライブラリの運用に関わることになりました。それは良いことですが、私が参画した時点ではリリースもリリースノートの作成もすべて手作業でした。

そのため、リリースが結構時間かかるかつめんどくさかったので、リリース頻度が減り、生産性が落ちるという課題がありました。

私は github acions については「そういうのがある」ぐらいしか知らなかったですが、リリースがあまりに面倒だったため、なんとか自動化したい!という気持ちでなんとかしました。その話をします。

Runner 設定

1番最初の段階は github acions が動くようにすることでした。

現在のプロジェクトでは、github acions が self-hosted runners でしか動かないため、Runner の設定する必要があったのです。

そこら辺の知識は本当に1ミリもなかったので、インフラに詳しいメンバーに泣きついて一緒に設定してもらいました(その方に無限の感謝を送ります)

self-hosted のプロジェクトそんなないと思いますので、大体の場合はこの段階は不要かと思います。

リリースノート作成自動化

github acions が動けるようになってからまず取り組んだことは、リリースノートの作成の自動化です。

色んなところで紹介されてる release-drafter を使いました。

https://github.com/release-drafter/release-drafter

release-drafter を使うためには Workflow ファイルと設定ファイルの2つのファイルが必要です。

Workflowファイルは README 通りにすれば問題なく動いてたので、設定ファイルだけ状況に合わせてすこしカスタムしました。

name-template: 'v$RESOLVED_VERSION 🌈'
tag-template: 'v$RESOLVED_VERSION'
categories:
  - title: '❗️ Breaking Changes'
    labels:
      - 'major'
  - title: '🚀 Features'
    labels:
      - 'enhancement'
  - title: '🐛 Bug Fixes'
    labels:
      - 'fix'
exclude-labels:
  - 'chore'
autolabeler:
  - label: 'chore'
    branch:
      - '/^chore\/.+/'
      - '/^docs\/.+/'
  - label: 'fix'
    branch:
      - '/^fix\/.+/'
  - label: 'enhancement'
      - '/^feature\/.+/'
      - '/^feat\/.+/'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
  major:
    labels:
      - 'major'
  minor:
    labels:
      - 'minor'
  patch:
    labels:
      - 'patch'
  default: patch
template: |
  ## Changes

  $CHANGES

重要な部分を解説していきます。

ラベル付与自動化

まずブランチ名に基づいて実装カテゴリラベルが自動で付与されるようにしました。release-drafter は Autolabeler 機能を内蔵しているため、autolabeler オプションでラベル自動付与ができます。

autolabeler:
  - label: 'chore'
    branch:
      - '/^chore\/.+/'
      - '/^docs\/.+/'
  - label: 'fix'
    branch:
      - '/^fix\/.+/'
  - label: 'enhancement'
      - '/^feature\/.+/'
      - '/^feat\/.+/'
  • chore/もしくはdocs/で始まるブランチ名のPRにはchoreラベルが付与される
  • fix/で始まるブランチ名のPRにはfixラベルが付与される
  • feature/もしくはfeat/で始まるブランチ名のPRにはenhancementラベルが付与される

カテゴリー分類

categories オプションを使用すると、ラベルをもとにPRを分類してリリースノートに記載することができます。

categories:
  - title: '❗️ Breaking Changes'
    labels:
      - 'major'
  - title: '🚀 Features'
    labels:
      - 'enhancement'
  - title: '🐛 Bug Fixes'
    labels:
      - 'fix'
  • majorラベルがついてるPRがマージされたら、リリースノートの ❗️ Breaking Changesの項目として記載される
  • enhancementラベルがついてるPRがマージされたら、リリースノートの 🚀 Featuresの項目として記載される
  • fixラベルがついてるPRがマージされたら、リリースノートの 🐛 Bug Fixesの項目として記載される

リリースノートに含まないPR指定

exclude-labels オプションを使うと、ラベルをもとにリリースノートからPRを除外することができます。

exclude-labels:
  - 'chore'

ymlファイル修正やグリーンキープなど、開発側にだけ関わるPRはリリースノートにのせなくてよいという判断になったので、choreラベルはリリースノートから除外しました。

バージョニング指定

version-resolverオプションを使うと、各PRのラベルに基づいてバージョン番号を自動で上げることができます。

version-resolver:
  major:
    labels:
      - 'major'
  minor:
    labels:
      - 'minor'
  patch:
    labels:
      - 'patch'
  default: patch
  • majorラベル付きのPRがマージされたらメージャーバージョンアップされたリリースノートが作られる
  • minorラベル付きのPRがマージされたらマイナーバージョンアップされたリリースノートが作られる
  • patchラベル付きのPRがマージされたらパッチバージョンアップされたリリースノートが作られる
  • バージョニングラブルがない場合、デフォルトでパッチバージョンアップされたリリースノートが作られる

Draft のリリースノート確認

もろもろ設定が終わったら、PRをマージしてちゃんと動いてるか確認します。

Github の releases ページで以下のように自動で Draft のリリースノートが作成されてるなら設定完了です!

+)この画像は release-drafter の README から持ってきたものなので、この記事で紹介した設定と違う見た目になってます

image from release-drafter

リリースノート公開したいときは、生成されたドラフトリリースノートの編集ボタンをクリック、Publish ボタン押せば公開完了です。

手で全部いちいち書いてる時に比べたら大分楽になりましたね。

リリースプロセス自動化

リリースノート自動化できたので、リリースプロセスも自動化します。
以前は手でひとつずつコマンドを打ってリリースしてたので、それをWorkflowにやらせました。

リリースのやり方はボタンポチでやることにしたので on.workflow_dispatch を採用してます。

on:
  workflow_dispatch:

権限確認

まず最初の悩みは「管理者権限のメンバーだけリリースできるようにしたい」どいうことでした。

手動リリースでは、認証情報を持ってるのは管理者権限のメンバーだけなのでそういう心配はなかったのですが、gitHub actions を使うと認証情報を Secrets に保存することになり、結果誰でもボタンポチでリリースできるようになってしまいます。

まあそんなことする人はいないとは思いますが、やはりシステム的に管理者だけリリースできるようにしといたほうが安全でいいでしょう。

それをどう実現できるか色々調べましたが、結局 actions/github-script で actor の権限取得してチェックすることにしました。

https://github.com/actions/github-script

jobs:
  check-permission:
    runs-on: self-hosted
    steps:
      - name: Check permission
        uses: actions/github-script@v6
        with:
          script: |
            const response = await github.request('GET /repos/{owner}/{repo}/collaborators/{username}/permission', {
              owner: context.repo.owner,
              repo: context.repo.repo,
              username: context.actor
            });
            const permission = response.data.permission;
            if (permission !== 'admin') core.setFailed(`User §{context.actor} does not have admin permissions.`);
            else console.log(`User §{context.actor} has admin permissions.`);

これで admin 権限じゃない人はボタンポチしてもWorkflowがこけるようになりました。

パッケージ公開

権限確認ができたら、手動で打ってたコマンドを打ってくれるWorkflowを書きます。

  release-package:
    runs-on: self-hosted
    needs: check-permission
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: 'package.json'

      - name: Install pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 8.15.6

      - name: Install dependencies
        run: pnpm install

      - name: Create .npmrc file
        run: |
          echo "{諸々必要な情報と認証情報}" > .npmrc

      - name: Build package
        run: pnpm run build

      - name: Publish package
        run: pnpm run publish

公開できないところは省いてるので実際の yml とはちょっと差はありますけど、だいたいこんな感じで書いてます。

このWorkflowが正常終了したら、リリース自体は完了です。

リリースノート公開

リリースが終わったらリリースノートを公開しないとですね。

release-drafter のおかげでボタンポチでリリースノート公開できるようになりました…が、問題はその「ボタンポチしにいくのもめんどくね??リリース終わったら勝手にリリースノートも公開してほしいんだけど」って思ってしまったことです。

ということでリリースノート公開もWorkflowにやらせるために、色々調べて、また actions/github-script に頼ることにしました。

  publish-reLease-note:
    runs-on: self-hosted
    needs: release-package
    steps:
      - name: Publish release note
        uses: actions/github-scriptav6
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            const { repo: { owner, repo }} = context;
            const releases = await github.rest.repos.listReleases({ owner, геро });
            const draftRelease = releases.data.find(release => release.draft === true);
            if (!draftRelease) {
              console.log('No draft release found to publish');
              return
            }
            await github.rest.repos.updateRelease({
              owner,
              геро,
              release_id: draftRelease.id,
              draft: false
            });
            console.log(`Published release: ${draftRelease.name}`);

listReleases を取得し、そこから draft 状態のものを抽出、draftfalse にすることでリリースノート公開できました。

(私フロントエンドエンジニアなので Javascript 書いてるときがなんか落ち着きます、、)

Storybook アップデート

リリース完了したらリリースノート公開以外にも、社内向けで公開してる Storybook を更新する必要があります。

これが1番難しかったですね。

従来の Storybook の更新手順は新しい Storybook をビルドし、 リモートの Windows 共有フォルダに格納することです。手動リリースだと、開発環境も Windows なので簡単なコマンドでできてましたが、問題は github actions が linux で動いてることでした。

とういうことで今までのコマンドなんて使えなくなり、linux で Windows 共有フォルダにアクセスする方法を調べることになります。

結果、cifs-utils で Windows 共有フォルダマウントする方法を採用しました。

  update-storybook:
  runs-on: self-hosted
  needs: release-package
  steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Node.js
      uses: actions/setup-node@v3
      with:
        node-version-file: 'package.json'

    - name: Install pnpm
      uses: pnpm/action-setup@v4
      with:
        version: 8.15.6

    - name: Install dependencies
      run: pnpm install

    - name: Install cifs-utils
      run: |
        sudo yum update -y
        sudo yum install -y cifs-utils

      # マウントするディレクトリ作成
    - name: Create mount directory
      run: mkdir -p $t{ github.workspace }}/mount

      # 認証情報を入力、先ほど作成したディレクトリに Windows 共有フォルダをマウントする
    - name: Mount network drive
      run: sudo mount -t cifs -o username=${{ secrets. NETWORK_DRIVER_USER }}, password=${{ secrets. NETWORK_DRIVER_PASSWORD }}, vers=3.0 {マウントするWindows共有フォルダのパス} ${{ github.workspace }}/mount

      # 古い Storybook ファイルを削除
    - name: Remove old Storybook
      run: sudo rm -rf ${{ github.workspace }}/mount/{Windows共有フォルダのStorybookのパス}

      # 新しい Storybook をビルド
    - name: Build Storybook
      run: pnpm run build-storybook

      # マウントしてきた Windows 共有フォルダにビルドしたStorybookをペースト
    - name: Copy new Storybook to network drive
      run: sudo cp -r ${{ github.workspace }}/{ビルドしたStorybookのパス} ${{ github.workspace }}/mount/{Windows共有フォルダのStorybookのパス}

      # アンマウント
    - name: Unmount network drive
      if: always()
      run: sudo umount ${{ github.workspace }}/mount

これでなんとか github actions でも Storybook アップデートができるようになり、一連のリリース作業を全部 Workflow にやらせることができました。

+)マウントしたら確実にアンマウントしよう

実は色々試行錯誤やってるうちに、↓のように書いてWorkflow実行してましたが、

    - name: Mount network drive
      run: sudo mount -t cifs -o username=${{ secrets. NETWORK_DRIVER_USER }}, password=${{ secrets. NETWORK_DRIVER_PASSWORD }}, vers=3.0 {マウントするWindows共有フォルダのパス} ${{ github.workspace }}/mount

    # 他のステップたち、、

    - name: Unmount network drive
      run: sudo umount ${{ github.workspace }}/mount

マウントした直後のステップでエラーが発生して、そのままアンマウントせずジョブが終了され、、その後もう一度ジョブ実行したらアンマウントされてないことの影響でジョブが正常に動かなくなってしまいました。

マウントした後はもう何かが失敗しても絶対アンマウントは実行させないといけないことを身を持って知ったので、アンマウントは常に実行されるようにしました。

    - name: Unmount network drive
      if: always() # 必ず実行させる
      run: sudo umount ${{ github.workspace }}/mount

🌷 終わり

さぐりさぐりでやった自動化なのでツッコミどころ多いと思いますし、これよりもっといい方法絶対あると思いますが、私がド素人な上に周りにも github actions 詳しい人がいなかったので今回はこれが最善でした。

それでも30分以上かかってたリリース作業がボタンポチで全部できるようになったので、まあ頑張ったのではないかと自分を慰めてます。

GitHubで編集を提案

Discussion