Skip to content

Merge branch 'master' of https://github.com/proofer-tech/dira #29

Merge branch 'master' of https://github.com/proofer-tech/dira

Merge branch 'master' of https://github.com/proofer-tech/dira #29

Workflow file for this run

# master에 앱 코드가 들어오면 버전을 올리고 릴리스까지 자동으로 간다 (요구 2026-08-01).
#
# **이 파일은 docs/DESIGN.md §CI C3(「굽지 않는다」)를 뒤집는다.** C3의 근거는 편의가 아니라
# 「서명·공증은 사람 맥의 인증서로 한다 · 시크릿은 레포에 들어가지 않는다」(§배포)였다.
# 뒤집는 대가는 **인증서가 GitHub Actions 시크릿으로 올라가는 것**이다 — 레포 파일에는
# 여전히 안 들어가지만, 사람 맥 밖으로 나가는 것은 맞다. 아래 5개가 없으면 릴리스는
# `release.sh`의 선행 확인에서 **bump 전에** 멈추고 무엇이 없는지 찍는다(조용히 안 멈춘다).
#
# MACOS_CERT_P12 `security export`한 Developer ID Application .p12의 base64
# MACOS_CERT_PASSWORD 그 .p12의 암호
# APPLE_ID 공증 계정
# APPLE_APP_SPECIFIC_PASSWORD
# APPLE_TEAM_ID
#
# 굽는 절차 자체는 여기 없다. `release.sh` 하나가 사람 맥에서도 CI에서도 같은 순서로 돈다
# (§릴리스 R4) — 두 벌로 갈라지면 사람 맥에서만 맞는 릴리스가 생긴다.
name: release
on:
# **매 master 커밋마다다** — path 필터 없다(요구 2026-08-01). 대가를 알고 고른 값이다:
# 스펙·README만 고친 커밋도 patch를 올리고 받는 사람에게 내용이 같은 버전이 내려간다.
# 줄이려면 여기에 `paths: ['apps/**', 'tick.sh', 'tickets.py', 'templates/**']`를 넣으면 된다.
push:
branches: [master]
workflow_dispatch:
inputs:
bump:
description: 'patch | minor | major'
default: patch
# 두 개가 겹치면 둘 다 같은 버전을 bump하고 뒤엣것의 push가 거절된다. 취소하지는 않는다 —
# 굽던 릴리스를 중간에 죽이면 태그만 남고 자산이 없는 상태가 된다(R4가 막으려던 그것).
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
# 사람이 자기 맥에서 `pnpm release minor`를 치면 release.sh 4번이 **사람 자격으로** master를
# 민다 — 아래 GITHUB_TOKEN 예외는 CI가 민 push에만 걸려서, 이게 없으면 러너가 그 위에
# patch를 한 번 더 얹는다(v0.2.0 뒤에 v0.2.1이 서고 뒤엣것의 릴리즈 노트는 빈 구간이다).
# 판정 문자열은 release.sh가 만드는 커밋 제목 `release v<x.y.z>` 하나뿐이다(§릴리스 R4-1).
# startsWith(...) 하나만 쓰는 것이 오답이다 — workflow_dispatch에는 head_commit이 아예 없어서
# 손으로 내는 minor·major의 판정이 GHA의 null 처리에 매달리고, 막히면 낼 자리가 사라진다.
# 첫 항(`!= 'push'`)이 앞에 있으면 dispatch는 거기서 끝나 startsWith에 닿지 않는다.
if: github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'release v')
runs-on: macos-latest
permissions:
contents: write
steps:
# ref를 안 주면 detached HEAD로 떨어져서 release.sh의 「브랜치가 master」 확인에 걸린다.
- uses: actions/checkout@v7
with:
ref: master
fetch-depth: 0
- uses: pnpm/action-setup@v6
with:
version: 11
- uses: actions/setup-node@v7
with:
node-version: 25
# 인증서를 임시 키체인에 넣고 **검색목록에 올린다.** electron-builder만 보는 게 아니다 —
# sign-preflight.sh와 sign-dmg.sh가 `security find-identity`로 찾는다(CSC_LINK만 주면
# 그 둘은 못 본다). set-key-partition-list를 빼면 codesign이 errSecInternalComponent로 죽는다.
- name: 인증서를 임시 키체인에 넣는다
env:
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
# preflight는 이 셋도 본다. 안 주면 여기서만 「공증 건너뜀」을 찍어서, 아래 릴리스
# 스텝이 멀쩡히 공증하는데도 로그를 읽는 사람은 공증이 빠진 줄 안다(실측 2026-08-10).
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
[ -n "$MACOS_CERT_P12" ] || { echo "MACOS_CERT_P12이 비어 있다 — 시크릿 5개를 채워야 한다(이 파일 머리말)." >&2; exit 1; }
kc="$RUNNER_TEMP/build.keychain-db"
security create-keychain -p "" "$kc"
security set-keychain-settings "$kc" # 잠금 타임아웃 없음 — 공증 대기 중에 잠기면 스테이플에서 죽는다
security unlock-keychain -p "" "$kc"
printf '%s' "$MACOS_CERT_P12" | base64 -d > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$kc" -P "$MACOS_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
rm -f "$RUNNER_TEMP/cert.p12"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" "$kc" >/dev/null
security list-keychains -d user -s "$kc" $(security list-keychains -d user | tr -d '" ')
./apps/desktop/sign-preflight.sh
- run: pnpm install
working-directory: apps/teams
- run: pnpm install
working-directory: apps/desktop
# ci.yml과 겹치지만 여기서 다시 돈다. 두 워크플로는 서로를 기다리지 않아서, 이게 없으면
# ci.yml이 빨간 것과 무관하게 남의 맥으로 나가는 `.dmg`가 구워진다. 초 단위라 아깝지 않다.
- run: python3 test_generic.py && python3 test_reap_manual.py && python3 test_claim_race.py && python3 test_feed_stall.py && python3 test_inbox.py
- run: pnpm test
working-directory: apps/teams
- run: pnpm test
working-directory: apps/desktop
# GA4 자격값을 번들에 넣는다 (§0-11 §자격값). **레포 파일에는 안 들어간다** — 시크릿에서
# 받아 `apps/desktop/ga.json`으로 떨어뜨리고 `build.files`가 `.app` 안으로 나른다.
# 그 파일은 `.gitignore`에 있어서 release.sh의 「워킹 트리가 깨끗하다」 확인에 안 걸린다.
#
# **없으면 그대로 성공한다** — 인증서 5개와 다른 규칙이다. 저건 없으면 남의 맥에서 안
# 열리는 자산이 나가지만, 자격값이 없는 앱은 통계를 안 보낼 뿐 정상 동작한다(§0-11).
# 값 둘을 실제로 올리는 것은 사람이 한다. 올라오면 다음 릴리스부터 실린다.
- name: GA4 자격값
working-directory: apps/desktop
env:
GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }}
GA_API_SECRET: ${{ secrets.GA_API_SECRET }}
run: |
if [ -z "$GA_MEASUREMENT_ID" ] || [ -z "$GA_API_SECRET" ]; then
echo "GA 시크릿이 없다 — 통계를 안 보내는 앱으로 굽는다(정상)."
exit 0
fi
# 따옴표·개행이 섞인 값도 안전하게 넣으려고 node로 쓴다(printf면 이스케이프가 샌다).
node -e 'require("fs").writeFileSync("ga.json", JSON.stringify({GA_MEASUREMENT_ID: process.env.GA_MEASUREMENT_ID, GA_API_SECRET: process.env.GA_API_SECRET}))'
echo "ga.json을 만들었다 — 이 빌드는 통계를 보낸다."
# release.sh가 커밋·태그·push를 한다. 저자가 없으면 git commit이 거기서 죽는다.
#
# **되먹임이 없는 이유가 여기 걸려 있다.** bump 커밋은 `apps/desktop/package.json`만
# 건드리는데 그건 위 paths에 걸린다 — 그런데도 릴리스가 릴리스를 부르지 않는 것은
# `GITHUB_TOKEN`으로 나간 push가 워크플로를 다시 트리거하지 않기 때문이다. PAT로 바꾸면
# 그 규칙이 사라지고 무한 릴리스가 된다.
- name: 릴리스
working-directory: apps/desktop
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
./release.sh "${{ inputs.bump || 'patch' }}"