Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions .github/workflows/pr-description.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
name: PR Description

on:
pull_request_target:
types:
- opened
- reopened
- synchronize
- ready_for_review

permissions:
contents: read
pull-requests: write

jobs:
update-description:
name: Update PR Description
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Generate PR body from commits
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail

gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}/commits?per_page=100" > commits.json
gh pr view "${PR_NUMBER}" --json body --jq '.body // ""' > current_body.md

ruby <<'RUBY' > pr_body.md
require "json"

commits = JSON.parse(File.read("commits.json"))
current_body = File.read("current_body.md")
title = ENV.fetch("PR_TITLE")
base_ref = ENV.fetch("BASE_REF")
head_ref = ENV.fetch("HEAD_REF")

keyword_order = [
"기능추가",
"기능수정",
"버그수정",
"리팩터링",
"성능개선",
"테스트",
"문서",
"빌드/설정",
"CI",
"기타"
]

groups = Hash.new { |hash, key| hash[key] = [] }
details = []

commits.each do |commit|
subject = commit.dig("commit", "message").to_s.lines.first.to_s.strip
next if subject.empty?

if subject =~ /^\[\d{8}\]\s+([^:]+):\s*(.+)$/
keyword = Regexp.last_match(1)
content = Regexp.last_match(2)
elsif subject =~ /^(feat|fix|refactor|perf|test|docs|chore|build|ci)(?:\([^)]+\))?:\s*(.+)$/i
type = Regexp.last_match(1).downcase
content = Regexp.last_match(2)
keyword = {
"feat" => "기능추가",
"fix" => "버그수정",
"refactor" => "리팩터링",
"perf" => "성능개선",
"test" => "테스트",
"docs" => "문서",
"chore" => "기타",
"build" => "빌드/설정",
"ci" => "CI"
}.fetch(type, "기타")
else
keyword = "기타"
content = subject
end

groups[keyword] << content
details << content
end

generated = []

generated << "<!-- commonutils-pr-description:start -->"
generated << "## PR 요약"
generated << ""
generated << "- #{title}"
generated << "- 기준 브랜치: `#{base_ref}`"
generated << "- 작업 브랜치: `#{head_ref}`"
generated << ""
generated << "## 커밋 기반 요약"
generated << ""

keyword_order.each do |keyword|
next if groups[keyword].empty?

generated << "### #{keyword}"
groups[keyword].uniq.each do |content|
generated << "- #{content}"
end
generated << ""
end

if groups.values.all?(&:empty?)
generated << "- 커밋 메시지에서 요약할 항목을 찾지 못했습니다."
generated << ""
end

generated << "## 변경 사항 상세"
generated << ""
details.uniq.first(7).each do |content|
generated << "- #{content}"
end
generated << "- 자세한 변경 범위는 Files changed 탭을 확인해주세요." if details.length > 7
generated << ""
generated << "## 테스트/검증"
generated << ""
generated << "- [ ] `xcodebuild test -scheme CommonUtils -destination 'platform=iOS Simulator,name=iPhone 16' CODE_SIGNING_ALLOWED=NO`"
generated << "- [ ] GitHub Actions PR Tests 통과"
generated << ""
generated << "## 릴리즈 메모"
generated << ""
generated << "- 릴리즈 대상 버전이 있으면 `CHANGELOG.md`에 해당 버전 섹션을 추가해주세요."
generated << "<!-- commonutils-pr-description:end -->"

block = generated.join("\n")
start_marker = "<!-- commonutils-pr-description:start -->"
end_marker = "<!-- commonutils-pr-description:end -->"

if current_body.include?(start_marker) && current_body.include?(end_marker)
pattern = /#{Regexp.escape(start_marker)}.*?#{Regexp.escape(end_marker)}/m
puts current_body.sub(pattern, block)
elsif current_body.strip.empty?
puts block
else
puts current_body.rstrip
puts
puts block
end
RUBY

gh pr edit "${PR_NUMBER}" --body-file pr_body.md
76 changes: 76 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: PR Tests

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
test:
name: iOS Simulator Tests
runs-on: macos-15
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select iPhone simulator
run: |
SIMULATOR_ID="$(
xcrun simctl list devices available -j | ruby -rjson -e '
devices = JSON.parse(STDIN.read)["devices"].values.flatten
simulator = devices.find { |device| device["name"].include?("iPhone") && device["isAvailable"] }
abort("No available iPhone simulator") unless simulator
puts simulator["udid"]
'
)"
echo "SIMULATOR_ID=${SIMULATOR_ID}" >> "${GITHUB_ENV}"
echo "Selected simulator: ${SIMULATOR_ID}"

- name: Run tests
run: |
xcodebuild test \
-scheme CommonUtils \
-destination "platform=iOS Simulator,id=${SIMULATOR_ID}" \
-skip-testing:CommonUtilsTests/VideoCompressionTests/testCompressVideo_Success \
-skip-testing:CommonUtilsTests/VideoCompressionTests/testCompressVideo_TooSmallMaxFileSize_ShouldFail \
-skip-testing:CommonUtilsTests/VideoCompressionTests/testCompressVideo_NoAudioTrack_Success \
CODE_SIGNING_ALLOWED=NO

video-compression-test:
name: Video Compression Integration Test
runs-on: macos-15
timeout-minutes: 20
if: github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'run-video-compression-tests')

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select iPhone simulator
run: |
SIMULATOR_ID="$(
xcrun simctl list devices available -j | ruby -rjson -e '
devices = JSON.parse(STDIN.read)["devices"].values.flatten
simulator = devices.find { |device| device["name"].include?("iPhone") && device["isAvailable"] }
abort("No available iPhone simulator") unless simulator
puts simulator["udid"]
'
)"
echo "SIMULATOR_ID=${SIMULATOR_ID}" >> "${GITHUB_ENV}"
echo "Selected simulator: ${SIMULATOR_ID}"

- name: Run video compression integration tests
env:
RUN_VIDEO_COMPRESSION_TESTS: "1"
run: |
xcodebuild test \
-scheme CommonUtils \
-destination "platform=iOS Simulator,id=${SIMULATOR_ID}" \
-only-testing:CommonUtilsTests/VideoCompressionTests/testCompressVideo_Success \
-only-testing:CommonUtilsTests/VideoCompressionTests/testCompressVideo_TooSmallMaxFileSize_ShouldFail \
-only-testing:CommonUtilsTests/VideoCompressionTests/testCompressVideo_NoAudioTrack_Success \
CODE_SIGNING_ALLOWED=NO
57 changes: 57 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Release

on:
push:
tags:
- "[0-9]*.[0-9]*.[0-9]*"
- "v[0-9]*.[0-9]*.[0-9]*"

permissions:
contents: write

jobs:
create-release:
name: Create GitHub Release
runs-on: macos-15
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Create release
env:
GH_TOKEN: ${{ github.token }}
TAG_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail

VERSION="${TAG_NAME#v}"
NOTES_FILE="$(mktemp)"

if [[ -f CHANGELOG.md ]]; then
awk -v version="${VERSION}" '
$0 ~ "^## \\[" version "\\]" {
capture = 1
next
}
capture && /^## \[/ {
capture = 0
}
capture {
print
}
' CHANGELOG.md | sed '/^[[:space:]]*$/d' > "${NOTES_FILE}"
fi

if [[ -s "${NOTES_FILE}" ]]; then
gh release create "${TAG_NAME}" \
--title "${TAG_NAME}" \
--notes-file "${NOTES_FILE}"
else
gh release create "${TAG_NAME}" \
--title "${TAG_NAME}" \
--generate-notes
fi
Loading
Loading