GitHub Actions로 tag, release 생성하기

개발

GitHub Actions로 tag, release 생성하기
최종 수정일:

개인적인 용도로 만든 npm package들도 있고, 알바몬에 사용할 npm package들에 기능 등의 추가가 필요하거나 문제가 있으면 업무 외 시간에 업데이트하곤 합니다.

어느새 그 가짓수만 해도 꽤 늘었습니다.
이 패키지들 관리하며 npm 배포 자동화를 위해 한 삽질을 좀 공유할까 합니다.

참고: 아래 작성된 글의 pipeline은 React Postscribe repository에서 확인하실 수 있습니다.

기존 작업 방식

name: Publish package
 
on:
    release:
        types: [created]
 
jobs:
    publish-npm:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - uses: actions/setup-node@v3
              with:
                  node-version: 18
                  check-latest: true
                  registry-url: https://registry.npmjs.org/
            - uses: pnpm/action-setup@v2
              with:
                  version: 8
                  run_install: |
                      - recursive: true
                        args: [--frozen-lockfile, --strict-peer-dependencies]
            - run: pnpm publish --access=public
              env:
                  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

기존에는 이 정도 yml 파일만 작성해두고, release를 수동으로 생성하여 npm에 배포를 진행했습니다.

간단하게 작업할 땐 큰 불편함을 못 느꼈으나, 관리하는 패키지의 가짓수가 늘어나고 작업을 오래 하다 보니 아래와 같은 문제들이 있었습니다.

  1. package.json의 버전을 업데이트하지 않고 release를 생성할 수 있습니다.
  2. 이전 release에서 어떤 변화가 생겼는지 파악이 쉽지 않습니다.
  3. 매번 github을 들어와 번거롭게 2번을 진행하고 release를 수동으로 생성해야 합니다.

따라서 버전을 업데이트하는 커밋을 생성하면, 자동으로 이전에 생성된 tag에서부터 현재 커밋까지 커밋 목록을 생성하고, npm에 배포까지 진행할 수 있게 작업해봤습니다.따라서 버전을 업데이트하는 커밋을 생성하면, 자동으로 이전에 생성된 tag에서부터 현재 커밋까지 커밋 목록을 생성하고, npm에 배포까지 진행할 수 있게 작업해봤습니다.

버전 업데이터 만들기

#!/bin/bash
version=${1:-'patch'}
pnpm version "$version" -m "Update version to v%s"

간단한 버전 업데이터입니다.
"update-version": "bash scripts/update.sh"와 같이 package.json에 스크립트를 추가해두고, pnpm run update-version (major|minor|patch)와 같이 실행하면, 버전을 업데이트하고(기본으로 patch) 커밋합니다.

참고로, 저는 버전을 v1.0.0같은 형식으로 관리해 앞에 v를 붙여주도록 했습니다.
만약 다른 패턴을 사용하시면 수정하시면 됩니다.

버전 업데이트 자동으로 감지하기

위 방법을 사용하지 않고, package.json이 업데이트되는 것을 감지하는 방법도 있습니다.

#!/bin/bash
 
PREVIOUS_VERSION=$(git show HEAD~1:package.json | jq -r .version)
CURRENT_VERSION=$(jq -r .version package.json)

그리 안정적인 방법은 아니지만(HEAD~1과 비교하는 등의 이유로), 적당히 쓸만한 스크립트입니다.
기존 방법과 다르게, 버전을 업데이트하고 커밋만 하면 메시지와 상관없이 특정 동작을 수행시킬 수 있습니다.

- name: Check version
  id: check-version
  shell: bash
  run: |
    PREVIOUS_VERSION=$(git show HEAD~1:package.json | jq -r .version)
    CURRENT_VERSION=$(jq -r .version package.json)
    if [[ "$PREVIOUS_VERSION" == "$CURRENT_VERSION" ]]; then
     echo "version="
    else
        echo "version=$CURRENT_VERSION"
    fi >> $GITHUB_OUTPUT

위와 같이 yml파일을 작성해두시면, check-version step의 output으로 버전 업데이트 여부를 확인할 수 있습니다.

Tag / Release 생성 파이프라인 추가

name: Create tag and release
 
on:
    push:
        branches:
            - master
 
jobs:
    check-commit:
        runs-on: ubuntu-latest
        outputs:
            result: ${{ steps.check.outputs.result }}
            version: ${{ steps.get-version.outputs.version }}
        steps:
            - uses: actions/checkout@v3
            - name: Check commit message
              id: check
              run: echo "result=$(echo '${{ github.event.head_commit.message }}' | grep -oP '^Update version to v(\d|\.)+$')" >> $GITHUB_OUTPUT
              shell: bash
            - name: Get version
              id: get-version
              run: echo "version=$(echo '${{ github.event.head_commit.message }}' | grep -oP 'v(\d|\.)+')" >> $GITHUB_OUTPUT
              shell: bash
    create-tag:
        runs-on: ubuntu-latest
        needs: ["check-commit"]
        if: ${{ needs.check-commit.outputs.result != '' }}
        outputs:
            tag-exists: ${{ steps.create-tag.outputs.tag_exists }}
            release-body: ${{ steps.generate-body.outputs.body }}
        steps:
            - uses: actions/checkout@v3
              with:
                  fetch-depth: 0
            - name: Generate body
              id: generate-body
              run: |
                  EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
                  git_logs=$(git log "$(git describe --tags --abbrev=0)"..HEAD --oneline)
                  git_logs="${git_logs//$'\n'/$'\n'- }"
                  {
                      echo "body<<$EOF"
                      echo "- $git_logs"
                      echo "$EOF"
                  } >>"$GITHUB_OUTPUT"
              shell: bash
            - uses: rickstaa/action-create-tag@v1
              id: create-tag
              with:
                  tag: ${{ needs.check-commit.outputs.version }}
                  tag_exists_error: true
                  message: ${{ needs.check-commit.outputs.version }}
    create-release:
        runs-on: ubuntu-latest
        needs: ["check-commit", "create-tag"]
        if: ${{ needs.create-tag.outputs.tag-exists == 'false' }}
        steps:
            - uses: actions/checkout@v3
            - name: Create a GitHub release
              uses: ncipollo/release-action@v1
              with:
                  tag: ${{ needs.check-commit.outputs.version }}
                  name: ${{ needs.check-commit.outputs.version }}
                  body: ${{ needs.create-tag.outputs.release-body }}
    trigger-deploy:
        runs-on: ubuntu-latest
        needs: ["create-release"]
        name: Trigger publish workflow
        steps:
            - uses: actions/github-script@v6
              env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              with:
                  script: |
                      github.rest.actions.createWorkflowDispatch({
                        owner: context.repo.owner,
                        repo: context.repo.repo,
                        workflow_id: 'npm-publish.yml',
                        ref: 'master',
                      })

원하는 대로 제작하려고 반나절 정도 삽질한 것 같습니다.
job 순서대로 하나씩 설명해보겠습니다.

check-commit

버전 업데이터에 만들어둔 커밋 메시지 패턴과 현재 커밋 메시지가 일치하는지 확인합니다.
위 업데이터에서 버전 패턴을 변경하셨으면 정규표현식도 수정해야 합니다.

regex matching string 및 버전을 output에 저장합니다.

create-tag

check-commit job에서 regex matching 이 있으면 실행됩니다(if: 참고).

먼저, 이전 tag에서부터 현재 커밋까지 커밋 목록을 생성하여 output에 저장합니다.
git log "$(git describe --tags --abbrev=0)"..HEAD --oneline를 실행하면 이전 tag부터 HEAD까지 commit log를 출력할 수 있습니다.
참고: github output에 문자를 저장할 때 줄바꿈이 들어가면 오류가 발생하는데, Github Docs의 Using Workflow 문서의 Multiline strings를 참고하여 작업했습니다.

이후에 태그를 생성하고, 태그가 이미 존재하는지를 output에 저장합니다.
create-tag-release가 deprecated되기 전까진 한 번에 가능했는데...안타깝게도 이젠 하나씩 해줘야 하는 것 같더라고요.

create-release

create-tag job이 태그 생성에 성공하면 실행됩니다.
check-commit에서 추출한 버전명을 제목, create-tag에서 생성된 body를 내용으로 사용합니다.

trigger-deploy

create-release job이 완료만 되면 실행됩니다.
GitHub actions가 release를 생성하면 자동으로 pipeline이 수행되지 않기에(무한 루프 등 방지 위해 의도된 기능), deploy workflow를 trigger해줘야 합니다.

Publish 파이프라인 수정

name: Publish package
 
on:
    release:
        types: [created]
    workflow_dispatch:
    repository_dispatch:
 
jobs:
    publish-npm:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v3
            - uses: actions/setup-node@v3
              with:
                  node-version: 18
                  check-latest: true
                  registry-url: https://registry.npmjs.org/
            - uses: pnpm/action-setup@v2
              with:
                  version: 8
                  run_install: |
                      - recursive: true
                        args: [--frozen-lockfile, --strict-peer-dependencies]
            - run: pnpm publish --access=public
              env:
                  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

다른 pipeline에서 publish pipeline이 trigger 되게 할 수 있도록, 조건을 조금 수정해줘야 합니다.
workflow_dispatch, repository_dispatch에도 trigger 되도록 수정하면 됩니다.

마무리

GitHub Actions 실행 결과
GitHub release 결과

버전 업데이터로 버전을 업데이트하면 변경된 내용이 요약된 release가 생성되고, npm에 자동으로 배포까지 되는 것을 확인할 수 있습니다.

Report an issue