배경
v3 는 PR 이 머지될 때마다 Vercel 이 운영에 배포한다. 버전은 package.json 을 손으로 올리고 있어서, v3.0.0 과 v3.0.1 모두 버전 커밋을 따로 만들었다. 푸터가 package.json 의 버전을 읽으므로, 버전을 빠뜨리면 운영 화면의 버전 표시가 틀린다.
v3 에 머지하는 단위를 곧 릴리즈 단위로 삼는다(trunk-based). 이 이슈는 그 앞 절반이다: PR 이 머지되기 전에 버전이 올라가 있게 만들고, 올라가 있지 않으면 머지를 막는다.
정한 규칙
PR 에 붙인 버전 레이블로 정한다. 셋 중 하나만 붙이고, 없으면 버전을 올리지 않는다.
| 레이블 |
bump |
흔한 PR Type |
major |
major |
개편 |
minor |
minor |
feat |
patch |
patch |
fix, style, refactor |
| 없음 |
none |
docs, chore |
- 처음에는 PR 제목의 타입(
fix:, feat:)으로 정하려 했다. 타입은 변경의 성격이지 운영에 주는 영향이 아니라서, 판단을 명시하는 레이블로 바꿨다. 커밋 제목(hotfix/#104:)은 Conventional Commits 파서와 맞지 않아 처음부터 쓰지 않았다
/pr-convention 이 PR 을 만들 때 레이블을 고른다
- bump commit 은 머지 전에 bot 이 PR 브랜치에 올린다. 머지 커밋 하나가 올바른 버전으로 한 번만 배포된다
- sofac-front 의
.github/actions/version-parser 처럼 버전 계산을 composite action 으로 떼어 둔다
토큰
GITHUB_TOKEN 으로 push 하면 GitHub 이 그 push 로 워크플로를 다시 돌리지 않는다. bump commit 이 새 head 가 되어도 필수 체크가 돌지 않아 머지가 영원히 막힌다. 그래서 bot push 는 PAT 로 한다.
작업 범위
대상 브랜치는 .github/release.env 로 관리하고, 단계마다 composite action 으로 떼어 #108 의 릴리즈 워크플로와 나중에 바뀔 브랜치에서도 그대로 쓴다.
.github/release.env
RELEASE_BRANCHES="v3": 머지 1건이 릴리즈 1건인 브랜치. 공백으로 여러 개를 적을 수 있다
VERSION_FILE="package.json"
.github/actions/
release-config: env 파일을 읽어 PR 의 base 브랜치가 대상인지(enabled)와 버전 파일 경로를 돌려준다
version-label: PR 레이블에서 major·minor·patch 를 찾아 올릴 자리를 돌려준다. 없으면 none, 둘 이상이면 실패
semver-bump: 버전과 올릴 자리로 다음 버전을 계산한다 (sofac-front 의 version-parser 자리)
commit-version: 버전 파일을 기대 버전으로 덮어쓰고(누적하지 않음) 다르면 github-actions[bot] 이름으로 커밋해 push 한다
.github/scripts/getBumpType.ts (+spec): version-label 의 판단 규칙. 규칙은 spec 이 있는 이 파일에만 둔다
- 테스트가
src/** 만 보므로 vitest.config.ts 의 include 에 더한다
.github/workflows/version-bump.yml
on.pull_request.branches 는 파일을 읽을 수 없어 모든 PR 의 opened, synchronize, reopened, labeled, unlabeled 에서 돌고, 대상 브랜치가 아니면 설정 단계 뒤에 끝난다
concurrency 를 PR 번호로 묶고 이전 실행을 취소한다
- 기대 버전 = 지금 base 브랜치 끝의 버전 + 레이블의 bump. PR 의 base SHA 는 오래됐을 수 있어 쓰지 않는다
- bump commit 을 push 한 실행은 실패로 끝내고, push 로 다시 도는 실행이 통과를 낸다. 레이블을
patch → minor → 없음 으로 바꿔도 매번 기대 버전으로 맞춰진다
.github/actions/ensure-version: version-label → base 버전 → semver-bump → commit-version 을 잇는다. 두 워크플로가 같은 규칙을 쓴다
.github/actions/sync-base: base 를 merge 한다. 버전 파일만 충돌하면 base 쪽 값으로 풀고, 다른 파일이 충돌하면 취소한다
.github/workflows/sync-release-prs.yml
- PR 이 릴리즈 브랜치에 머지되면 같은 base 로 열린 PR 을 matrix 로 갱신한다: sync-base → ensure-version → push
- 다른 파일이 충돌하면 PR 에 코멘트만 남긴다. fork PR 은 뺀다
- 이슈마다 PR 이 자동으로 열려 여러 PR 이 동시에 열린다. 룰셋의 up-to-date 요구가 뒤처진 PR 을 막고, 이 워크플로가 풀어 준다
- 레포 레이블
major, minor, patch 생성
/pr-convention
- PR 을 만들 때 버전 레이블을 고르는 단계와 기준표를 더한다
- PR 을 올린 뒤 로컬에서 더 커밋하려면 bot 커밋을 먼저
git pull --rebase 로 받는다
사용자가 할 일
- Fine-grained PAT 생성:
Wisesaturn/blog 한정, Contents 읽기·쓰기, Pull requests 읽기, No expiration
- 레포 secret
RELEASE_BOT_TOKEN 으로 등록
룰셋 생성 완료: release-branches (id 24037187), 대상 refs/heads/v3, 우회 없음
- PR 필수(merge commit 만), 필수 체크
version-bump, Require branches to be up to date, 삭제·force push 금지
RELEASE_BRANCHES 를 바꾸면 룰셋 대상도 함께 바꾼다
- PR 필수, 우회 없음
- 필수 상태 체크:
version-bump
- Require branches to be up to date before merging 켜기
- 허용 머지 방식: merge commit 만
이 설정이 막는 것
- 동시에 열린 PR 두 개가 같은 버전(예: 둘 다 3.0.2)을 잡는 경우. 두 PR 의
version 변경이 같아 git 이 충돌 없이 합치므로, up-to-date 요구가 없으면 같은 버전이 두 번 나간다. 브랜치를 갱신하면 synchronize 로 다시 계산되어 3.0.3 이 된다
- fork 에서 온 PR 은 secret 이 없어 체크가 실패한다. 개인 레포라 받아들인다
- 이 이슈의 PR 은 워크플로 추가라 버전 레이블을 붙이지 않는다. 버전이 오르지 않는다
완료 조건
선행 의존
없음
배경
v3 는 PR 이 머지될 때마다 Vercel 이 운영에 배포한다. 버전은
package.json을 손으로 올리고 있어서, v3.0.0 과 v3.0.1 모두 버전 커밋을 따로 만들었다. 푸터가package.json의 버전을 읽으므로, 버전을 빠뜨리면 운영 화면의 버전 표시가 틀린다.v3 에 머지하는 단위를 곧 릴리즈 단위로 삼는다(trunk-based). 이 이슈는 그 앞 절반이다: PR 이 머지되기 전에 버전이 올라가 있게 만들고, 올라가 있지 않으면 머지를 막는다.
정한 규칙
PR 에 붙인 버전 레이블로 정한다. 셋 중 하나만 붙이고, 없으면 버전을 올리지 않는다.
majorminorfeatpatchfix,style,refactordocs,chorefix:,feat:)으로 정하려 했다. 타입은 변경의 성격이지 운영에 주는 영향이 아니라서, 판단을 명시하는 레이블로 바꿨다. 커밋 제목(hotfix/#104:)은 Conventional Commits 파서와 맞지 않아 처음부터 쓰지 않았다/pr-convention이 PR 을 만들 때 레이블을 고른다.github/actions/version-parser처럼 버전 계산을 composite action 으로 떼어 둔다토큰
GITHUB_TOKEN으로 push 하면 GitHub 이 그 push 로 워크플로를 다시 돌리지 않는다. bump commit 이 새 head 가 되어도 필수 체크가 돌지 않아 머지가 영원히 막힌다. 그래서 bot push 는 PAT 로 한다.작업 범위
대상 브랜치는
.github/release.env로 관리하고, 단계마다 composite action 으로 떼어 #108 의 릴리즈 워크플로와 나중에 바뀔 브랜치에서도 그대로 쓴다..github/release.envRELEASE_BRANCHES="v3": 머지 1건이 릴리즈 1건인 브랜치. 공백으로 여러 개를 적을 수 있다VERSION_FILE="package.json".github/actions/release-config: env 파일을 읽어 PR 의 base 브랜치가 대상인지(enabled)와 버전 파일 경로를 돌려준다version-label: PR 레이블에서major·minor·patch를 찾아 올릴 자리를 돌려준다. 없으면none, 둘 이상이면 실패semver-bump: 버전과 올릴 자리로 다음 버전을 계산한다 (sofac-front 의 version-parser 자리)commit-version: 버전 파일을 기대 버전으로 덮어쓰고(누적하지 않음) 다르면github-actions[bot]이름으로 커밋해 push 한다.github/scripts/getBumpType.ts(+spec):version-label의 판단 규칙. 규칙은 spec 이 있는 이 파일에만 둔다src/**만 보므로vitest.config.ts의include에 더한다.github/workflows/version-bump.ymlon.pull_request.branches는 파일을 읽을 수 없어 모든 PR 의opened,synchronize,reopened,labeled,unlabeled에서 돌고, 대상 브랜치가 아니면 설정 단계 뒤에 끝난다concurrency를 PR 번호로 묶고 이전 실행을 취소한다patch→minor→ 없음 으로 바꿔도 매번 기대 버전으로 맞춰진다.github/actions/ensure-version: version-label → base 버전 → semver-bump → commit-version 을 잇는다. 두 워크플로가 같은 규칙을 쓴다.github/actions/sync-base: base 를 merge 한다. 버전 파일만 충돌하면 base 쪽 값으로 풀고, 다른 파일이 충돌하면 취소한다.github/workflows/sync-release-prs.ymlmajor,minor,patch생성/pr-conventiongit pull --rebase로 받는다사용자가 할 일
Wisesaturn/blog한정, Contents 읽기·쓰기, Pull requests 읽기, No expirationRELEASE_BOT_TOKEN으로 등록룰셋 생성완료:release-branches(id 24037187), 대상refs/heads/v3, 우회 없음version-bump, Require branches to be up to date, 삭제·force push 금지RELEASE_BRANCHES를 바꾸면 룰셋 대상도 함께 바꾼다version-bump이 설정이 막는 것
version변경이 같아 git 이 충돌 없이 합치므로, up-to-date 요구가 없으면 같은 버전이 두 번 나간다. 브랜치를 갱신하면synchronize로 다시 계산되어 3.0.3 이 된다완료 조건
patch레이블 PR 을 열면 bot 이 patch bump commit 을 올리고, 새 커밋에서 체크가 다시 돌아 통과한다minor로 바꾸면 minor 로 다시 맞춰지고, 레이블을 떼면 v3 의 버전으로 되돌아간다release-branches(PR 필수, 우회 없음) 적용을 API 로 확인했다. 막히지 않으면 곧바로 운영 배포가 나가서 실제 push 는 시도하지 않았다pnpm test에 포함되어 통과한다chore/#N: merge v3 from #M (x.y.z)로 갱신되고 version-bump 통과선행 의존
없음