Skip to content

Commit 3ec3b72

Browse files
committed
Merge branch 'develop' into feat/all-logic-test
# Conflicts: # core/common/build.gradle.kts # gradle/libs.versions.toml
2 parents 2ccd914 + e65645f commit 3ec3b72

70 files changed

Lines changed: 877 additions & 448 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
name: deploy-eatssu
3+
description: EAT-SSU Android 앱 릴리즈 자동화. 버전 범프, 릴리즈 노트 작성, release 브랜치 생성, PR 생성까지 전체 배포 플로우를 실행한다. "배포", "릴리즈", "release", "deploy", "출시", "버전 올려" 등의 키워드에 반응.
4+
version: 1.0.0
5+
---
6+
7+
# Deploy EAT-SSU
8+
9+
EAT-SSU Android 앱의 Google Play Store 릴리즈를 자동화하는 스킬.
10+
11+
## 트리거
12+
13+
사용자가 다음과 같이 요청할 때 이 스킬을 사용한다:
14+
- "배포해줘", "릴리즈 만들어줘", "출시해줘"
15+
- "버전 올려줘", "X.Y.Z 버전 만들어줘"
16+
- "release 브랜치 만들어줘"
17+
- "deploy", "release", "publish"
18+
19+
## 전제 조건
20+
21+
- `develop` 브랜치가 최신 상태여야 한다
22+
- 새 버전 번호가 필요하다 (사용자 지정 또는 자동 결정)
23+
24+
## 릴리즈 플로우
25+
26+
### Step 1: 버전 결정
27+
28+
사용자가 버전을 지정하지 않았으면, 현재 버전을 확인하고 적절한 다음 버전을 제안한다.
29+
30+
```bash
31+
# 현재 버전 확인
32+
grep 'versionName' app/build.gradle.kts | head -1
33+
grep 'versionCode' app/build.gradle.kts | head -1
34+
35+
# 최신 태그 확인
36+
git tag --list | sort -V | tail -5
37+
```
38+
39+
버전 규칙:
40+
- `versionName`: Semantic Versioning (`X.Y.Z`)
41+
- `versionCode`: 정수, 항상 +1 증가
42+
- 태그 형식: `X.Y.Z` (v 접두사 없음)
43+
- 릴리즈 노트 파일명: `release-notes/v<버전>.yml` (v 접두사 포함)
44+
→ 태그는 v 없이, 릴리즈 노트 파일명은 v 포함
45+
46+
### Step 2: 변경사항 파악
47+
48+
직전 릴리즈 태그 이후의 커밋을 분석한다.
49+
50+
```bash
51+
# 이전 태그 이후 커밋 목록 (머지 커밋 제외)
52+
git log <이전태그>..develop --oneline --no-merges
53+
54+
# 변경된 파일 통계
55+
git diff <이전태그>..develop --stat
56+
```
57+
58+
각 커밋의 의미를 파악하여:
59+
- 사용자에게 보이는 변경 (Feat, Fix) → 릴리즈 노트에 포함
60+
- 내부 변경 (Refactor, CI/CD, Chore) → 릴리즈 노트에서 제외하되 PR 본문에는 포함
61+
62+
### Step 3: develop 브랜치에서 release 브랜치 생성
63+
64+
```bash
65+
git checkout develop
66+
git pull origin develop
67+
git checkout -b release/<버전>
68+
```
69+
70+
### Step 4: 버전 범프
71+
72+
`app/build.gradle.kts``defaultConfig` 블록에서:
73+
74+
```kotlin
75+
versionCode = <기존 + 1>
76+
versionName = "<새 버전>"
77+
```
78+
79+
### Step 5: 릴리즈 노트 작성
80+
81+
`release-notes/v<버전>.yml` 파일을 생성한다.
82+
83+
형식:
84+
```yaml
85+
ko: |
86+
- 한국어 릴리즈 노트 항목
87+
en: |
88+
- English release note item
89+
```
90+
91+
규칙 (반드시 준수):
92+
1. 각 항목은 `-`로 시작
93+
2. **한국어와 영어 모두 작성 필수**
94+
3. 개발자 용어 사용 금지 — 사용자 친화적 언어 사용
95+
4. 최소 10자 이상의 내용
96+
5. CI/CD, 리팩토링 등 사용자에게 보이지 않는 변경은 "앱의 안정성과 성능을 개선했어요" 류로 통합
97+
6. 이 내용이 **Google Play Store에 그대로 업로드**됨 (ko → Korea, en → Global)
98+
99+
### Step 6: 커밋
100+
101+
```bash
102+
git add app/build.gradle.kts release-notes/v<버전>.yml
103+
git commit -m "release: <버전>"
104+
```
105+
106+
커밋 메시지는 반드시 `release: X.Y.Z` 형식을 사용한다.
107+
108+
### Step 7: 푸시 & PR 생성
109+
110+
```bash
111+
git push -u origin release/<버전>
112+
```
113+
114+
PR 생성:
115+
- **base**: `develop`
116+
- **head**: `release/<버전>`
117+
- **title**: `release: <버전>`
118+
- **body**: 아래 템플릿 사용
119+
120+
```markdown
121+
## Release <버전>
122+
123+
### 변경사항 (<이전버전> → <새버전>)
124+
125+
| 커밋 | 설명 | PR |
126+
|------|------|----|
127+
| `<hash>` | <커밋 메시지> | #<PR번호> |
128+
| ... | ... | ... |
129+
130+
### 릴리즈 노트 (Play Store)
131+
132+
**한국어**
133+
- <릴리즈 노트 항목들>
134+
135+
**English**
136+
- <릴리즈 노트 항목들>
137+
138+
### 버전 정보
139+
- `versionCode`: <이전> → <새>
140+
- `versionName`: <이전버전> → <새버전>
141+
```
142+
143+
## PR 머지 후 자동 처리
144+
145+
PR이 `develop`에 머지되면 GitHub Actions (`release.yml`)가 자동으로:
146+
147+
1. Fastlane으로 Release AAB 빌드
148+
2. Google Play Store `production` 트랙에 배포
149+
3. GitHub Release 생성 (태그 + 자동 릴리즈 노트 + AAB 첨부)
150+
4. Slack 알림 전송
151+
152+
## 수동 배포 (workflow_dispatch)
153+
154+
GitHub Actions 탭에서 직접 실행도 가능:
155+
1. Actions → **Production Release** 워크플로우
156+
2. **Run workflow** 클릭
157+
3. `version`, `track` (internal/alpha/beta/production), `release_notes` 입력
158+
159+
## 주의사항
160+
161+
- **절대 `main`/`master`에 직접 푸시하지 않는다** — 항상 PR을 통해 머지
162+
- 릴리즈 노트 YAML 파일이 없거나 내용이 부족하면 Play Store 배포가 실패할 수 있다
163+
- `versionCode`는 Play Store에서 고유해야 하므로 **반드시 증가**시킨다
164+
- GitHub Secrets가 올바르게 설정되어 있어야 CI/CD가 동작한다

.github/workflows/debug.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,25 @@ jobs:
152152
issue_number: context.payload.pull_request.number,
153153
body,
154154
});
155+
156+
- name: Slack notification for App Distribution
157+
if: success()
158+
uses: 8398a7/action-slack@v3
159+
with:
160+
status: success
161+
text: |
162+
🔥 Firebase App Distribution 배포 완료!
163+
164+
PR: #${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}
165+
브랜치: ${{ github.event.pull_request.head.ref }}
166+
테스터 그룹: eat-ssu-android-qa
167+
custom_payload: |
168+
{
169+
attachments: [{
170+
color: 'good',
171+
text: '🔥 Firebase App Distribution 배포 완료!\n\nPR: #${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}\n브랜치: ${{ github.event.pull_request.head.ref }}\n테스터 그룹: eat-ssu-android-qa'
172+
}]
173+
}
174+
env:
175+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_APP_DISTRIBUTION_WEBHOOK_URL }}
176+

.github/workflows/release.yml

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -184,22 +184,34 @@ jobs:
184184
if [ -f "$RELEASE_NOTE_FILE" ]; then
185185
echo "✅ $RELEASE_NOTE_FILE 파일 발견"
186186
echo "exists=true" >> $GITHUB_OUTPUT
187-
188-
RELEASE_NOTES_CONTENT=$(ruby -e "require 'yaml'; begin; puts YAML.load_file('$RELEASE_NOTE_FILE')['ko']; rescue; end")
189-
190-
if [ -z "$RELEASE_NOTES_CONTENT" ]; then
191-
echo "⚠️ YAML 파일에서 'ko' 항목을 찾을 수 없거나 비어있습니다."
192-
echo "release_notes=" >> $GITHUB_OUTPUT
193-
else
194-
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
195-
echo "$RELEASE_NOTES_CONTENT" >> $GITHUB_OUTPUT
196-
echo "EOF" >> $GITHUB_OUTPUT
197-
echo "📄 릴리즈 노트 내용:"
198-
echo "$RELEASE_NOTES_CONTENT"
187+
188+
RELEASE_NOTES_KO=$(ruby -e "require 'yaml'; begin; data = YAML.load_file('$RELEASE_NOTE_FILE') || {}; puts((data['ko-KR'] || data['ko'] || '').to_s); rescue; end")
189+
RELEASE_NOTES_EN=$(ruby -e "require 'yaml'; begin; data = YAML.load_file('$RELEASE_NOTE_FILE') || {}; puts((data['en-US'] || data['en'] || '').to_s); rescue; end")
190+
191+
if [ -z "$RELEASE_NOTES_KO" ]; then
192+
echo "⚠️ YAML 파일에서 한국어 릴리즈 노트(ko/ko-KR)를 찾지 못했습니다."
193+
fi
194+
195+
if [ -z "$RELEASE_NOTES_EN" ]; then
196+
echo "⚠️ YAML 파일에서 영어 릴리즈 노트(en/en-US)를 찾지 못했습니다."
199197
fi
198+
199+
echo "release_notes_ko<<EOF" >> $GITHUB_OUTPUT
200+
echo "$RELEASE_NOTES_KO" >> $GITHUB_OUTPUT
201+
echo "EOF" >> $GITHUB_OUTPUT
202+
203+
echo "release_notes_en<<EOF" >> $GITHUB_OUTPUT
204+
echo "$RELEASE_NOTES_EN" >> $GITHUB_OUTPUT
205+
echo "EOF" >> $GITHUB_OUTPUT
206+
207+
echo "📄 한국어 릴리즈 노트:"
208+
echo "$RELEASE_NOTES_KO"
209+
echo "📄 영어 릴리즈 노트:"
210+
echo "$RELEASE_NOTES_EN"
200211
else
201212
echo "⚠️ $RELEASE_NOTE_FILE 파일을 찾을 수 없습니다."
202-
echo "release_notes=" >> $GITHUB_OUTPUT
213+
echo "release_notes_ko=" >> $GITHUB_OUTPUT
214+
echo "release_notes_en=" >> $GITHUB_OUTPUT
203215
fi
204216
205217
- name: Find previous tag for release notes
@@ -311,9 +323,13 @@ jobs:
311323
312324
버전명: ${{ steps.extract_info.outputs.version }}
313325
314-
PlayStore 릴리즈 노트
326+
PlayStore 릴리즈 노트 (KO)
315327
316-
${{ steps.check_release_notes.outputs.release_notes != '' && steps.check_release_notes.outputs.release_notes || '릴리즈 노트가 없습니다.' }}
328+
${{ steps.check_release_notes.outputs.release_notes_ko != '' && steps.check_release_notes.outputs.release_notes_ko || '한국어 릴리즈 노트가 없습니다.' }}
329+
330+
PlayStore 릴리즈 노트 (EN)
331+
332+
${{ steps.check_release_notes.outputs.release_notes_en != '' && steps.check_release_notes.outputs.release_notes_en || 'English release notes are missing.' }}
317333
318334
GitHub 릴리즈노트
319335
@@ -322,7 +338,7 @@ jobs:
322338
{
323339
attachments: [{
324340
color: '${{ job.status }}' === 'success' ? 'good' : 'danger',
325-
text: '[Android 업데이트 올렸습니다!]\n\n버전명: ${{ steps.extract_info.outputs.version }}\n\nPlayStore 릴리즈 노트\n\n${{ steps.check_release_notes.outputs.release_notes != '' && steps.check_release_notes.outputs.release_notes || '릴리즈 노트가 없습니다.' }}\n\nGitHub 릴리즈노트\n\n${{ steps.github_release_notes.outputs.github_notes != '' && steps.github_release_notes.outputs.github_notes || '- [Fix] 릴리즈 노트를 가져올 수 없습니다.' }}'
341+
text: '[Android 업데이트 올렸습니다!]\n\n버전명: ${{ steps.extract_info.outputs.version }}\n\nPlayStore 릴리즈 노트 (KO)\n\n${{ steps.check_release_notes.outputs.release_notes_ko != '' && steps.check_release_notes.outputs.release_notes_ko || '한국어 릴리즈 노트가 없습니다.' }}\n\nPlayStore 릴리즈 노트 (EN)\n\n${{ steps.check_release_notes.outputs.release_notes_en != '' && steps.check_release_notes.outputs.release_notes_en || 'English release notes are missing.' }}\n\nGitHub 릴리즈노트\n\n${{ steps.github_release_notes.outputs.github_notes != '' && steps.github_release_notes.outputs.github_notes || '- [Fix] 릴리즈 노트를 가져올 수 없습니다.' }}'
326342
}]
327343
}
328344
env:

app/build.gradle.kts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import java.util.Properties
33
plugins {
44
alias(libs.plugins.android.application)
55
alias(libs.plugins.kotlin.android)
6+
alias(libs.plugins.kotlin.serialization)
67
alias(libs.plugins.compose.compiler)
78
alias(libs.plugins.google.services)
89
alias(libs.plugins.firebase.crashlytics)
@@ -29,8 +30,8 @@ android {
2930
applicationId = "com.eatssu.android"
3031
minSdk = 28
3132
targetSdk = 35
32-
versionCode = 53
33-
versionName = "3.2.2"
33+
versionCode = 58
34+
versionName = "3.2.4"
3435

3536
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
3637
}
@@ -197,10 +198,7 @@ dependencies {
197198

198199
//retrofit2: 서버통신
199200
implementation(libs.retrofit)
200-
implementation(libs.retrofit.converter.gson)
201201

202-
// Gson for JSON parsing
203-
implementation(libs.gson)
204202

205203
//OkHttp: 통신 로그 확인하기 위함
206204
implementation(libs.okhttp)
@@ -277,6 +275,10 @@ dependencies {
277275
// Paging3
278276
implementation(libs.androidx.paging.runtime)
279277
implementation(libs.androidx.paging.compose)
278+
279+
// Kotlin Serialization
280+
implementation(libs.kotlinx.serialization.json)
281+
implementation(libs.retrofit.kotlinx.serialization.converter)
280282
}
281283

282284
configurations.all {
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.eatssu.android.data.remote.dto.request
22

3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
36
data class ChangeNicknameRequest(
47
val nickname: String,
5-
)
8+
)
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.eatssu.android.data.remote.dto.request
22

3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
36
data class CheckValidTokenRequest(
47
val token: String,
5-
)
8+
)
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package com.eatssu.android.data.remote.dto.request
22

33
import com.eatssu.common.enums.DeviceType
4-
import com.google.gson.annotations.SerializedName
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
56

7+
@Serializable
68
data class LoginWithKakaoRequest(
7-
@SerializedName("email")
9+
@SerialName("email")
810
val email: String,
911

10-
@SerializedName("providerId")
12+
@SerialName("providerId")
1113
val providerId: String,
1214

13-
@SerializedName("deviceType")
15+
@SerialName("deviceType")
1416
val deviceType: DeviceType,
1517
)
Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package com.eatssu.android.data.remote.dto.request
22

3-
import com.google.gson.annotations.SerializedName
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
45

6+
@Serializable
57
data class ModifyReviewRequest(
6-
@SerializedName("rating") val rating: Int? = null,
7-
@SerializedName("menuLikes") val menuLikes: List<MenuLikes> = arrayListOf(),
8-
@SerializedName("content") val content: String? = null
8+
@SerialName("rating") val rating: Int? = null,
9+
@SerialName("menuLikes") val menuLikes: List<MenuLikes> = arrayListOf(),
10+
@SerialName("content") val content: String? = null
911
) {
12+
@Serializable
1013
data class MenuLikes(
1114

12-
@SerializedName("menuId") val menuId: Long? = null,
13-
@SerializedName("isLike") val isLike: Boolean? = null
15+
@SerialName("menuId") val menuId: Long? = null,
16+
@SerialName("isLike") val isLike: Boolean? = null
1417

1518
)
1619
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package com.eatssu.android.data.remote.dto.request
22

3-
import com.google.gson.annotations.SerializedName
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
45

6+
@Serializable
57
data class ReportRequest(
6-
@SerializedName("reviewId")
8+
@SerialName("reviewId")
79
val reviewId: Long,
810

9-
@SerializedName("reportType")
11+
@SerialName("reportType")
1012
val reportType: String,
1113

12-
@SerializedName("content")
14+
@SerialName("content")
1315
val content: String,
1416
)

0 commit comments

Comments
 (0)