project-auto-wizard 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +139 -0
  3. package/bin/project-auto-wizard.js +19 -0
  4. package/package.json +36 -0
  5. package/payload/coderabbit.yaml +19 -0
  6. package/payload/config/breaking-changes.json +1 -0
  7. package/payload/config/wizard-prompts.yml +62 -0
  8. package/payload/scripts/changelog_manager.py +726 -0
  9. package/payload/scripts/version_manager.py +617 -0
  10. package/payload/version.yml.template +48 -0
  11. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +303 -0
  12. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +293 -0
  13. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +289 -0
  14. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +192 -0
  15. package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +209 -0
  16. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +591 -0
  17. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +700 -0
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +308 -0
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +992 -0
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +689 -0
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +987 -0
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +471 -0
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml +500 -0
  24. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +185 -0
  25. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +271 -0
  26. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +81 -0
  27. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +2191 -0
  28. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +386 -0
  29. package/payload/workflows/react/PROJECT-REACT-CI.yaml +194 -0
  30. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +255 -0
  31. package/payload/workflows/spring/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +84 -0
  32. package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +316 -0
  33. package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +58 -0
  34. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +535 -0
  35. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +437 -0
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +2277 -0
  37. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +425 -0
  38. package/src/cli/args.js +95 -0
  39. package/src/cli/help.js +27 -0
  40. package/src/commands/full.js +53 -0
  41. package/src/commands/interactive.js +276 -0
  42. package/src/commands/revert.js +60 -0
  43. package/src/commands/version.js +31 -0
  44. package/src/commands/workflows.js +49 -0
  45. package/src/context.js +26 -0
  46. package/src/core/assets.js +47 -0
  47. package/src/core/branches.js +60 -0
  48. package/src/core/branding.js +15 -0
  49. package/src/core/breaking-check.js +65 -0
  50. package/src/core/breaking.js +26 -0
  51. package/src/core/copy/coderabbit.js +26 -0
  52. package/src/core/copy/gitignore.js +55 -0
  53. package/src/core/copy/readme.js +30 -0
  54. package/src/core/copy/simple.js +23 -0
  55. package/src/core/copy/workflows.js +233 -0
  56. package/src/core/detect-fs.js +106 -0
  57. package/src/core/detect.js +62 -0
  58. package/src/core/fsutil.js +46 -0
  59. package/src/core/options-ask.js +99 -0
  60. package/src/core/paths-resolve.js +261 -0
  61. package/src/core/paths.js +16 -0
  62. package/src/core/version-yml.js +190 -0
  63. package/src/core/wizard-env.js +101 -0
  64. package/src/core/wizard-labels.js +107 -0
  65. package/src/index.js +162 -0
  66. package/src/ui/ansi.js +26 -0
  67. package/src/ui/banner.js +29 -0
  68. package/src/ui/env-plan.js +207 -0
  69. package/src/ui/prompts.js +99 -0
  70. package/src/ui/readline-engine.js +257 -0
  71. package/src/ui/status-cards.js +56 -0
  72. package/src/ui/summary.js +127 -0
@@ -0,0 +1,700 @@
1
+ # ===================================================================
2
+ # Flutter Android Play Store 내부 테스트 배포 워크플로우
3
+ # ===================================================================
4
+ #
5
+ # {{MAIN_BRANCH}} 브랜치 푸시 시 Android AAB를 빌드하고
6
+ # Play Store 내부 테스트에 자동 배포합니다.
7
+ #
8
+ # ===================================================================
9
+ # 🔑 필수 GitHub Secrets
10
+ # ===================================================================
11
+ # ENV_FILE 또는 ENV: .env 파일 내용 (둘 중 하나, ENV_FILE 우선)
12
+ # RELEASE_KEYSTORE_BASE64: 서명용 keystore (base64)
13
+ # RELEASE_KEYSTORE_PASSWORD: keystore 비밀번호
14
+ # RELEASE_KEY_ALIAS: key alias
15
+ # RELEASE_KEY_PASSWORD: key 비밀번호
16
+ # GOOGLE_SERVICES_JSON: Firebase google-services.json
17
+ # GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64: Play Console 서비스 계정 (base64)
18
+ # ===================================================================
19
+
20
+ name: PROJECT-FLUTTER-ANDROID-PLAYSTORE-INTERNAL-CICD
21
+
22
+ on:
23
+ push:
24
+ branches: ["{{MAIN_BRANCH}}"]
25
+ workflow_dispatch:
26
+ inputs:
27
+ deploy_mode:
28
+ description: "배포 모드 (store_only=internal까지 / store_prepare=production draft / store_submit=심사 자동등록)"
29
+ required: false
30
+ default: "store_only"
31
+ type: choice
32
+ options:
33
+ - store_only
34
+ - store_prepare
35
+ - store_submit
36
+
37
+ # ============================================
38
+ # 🔧 프로젝트별 설정 (아래 값들을 수정하세요)
39
+ # ============================================
40
+ env:
41
+ # 프로젝트 이름
42
+ APP_ARTIFACT_NAME: "__APP_ARTIFACT_NAME__" # @wizard ask:@repo
43
+ # Flutter/Java 버전
44
+ FLUTTER_VERSION: "3.35.5"
45
+ JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:17
46
+ PROJECT_TYPE: "flutter"
47
+ # Flutter 루트 경로(레포 루트 기준). 단일레포면 ".", 모노레포면 "app" 등.
48
+ # 통합 시점에 integrator의 flutter-root resolver가 project_paths.flutter 값으로 1회 치환.
49
+ FLUTTER_ROOT: "." # @wizard auto:flutter-root
50
+ # 배포 모드 (store_only | store_prepare | store_submit)
51
+ DEPLOY_MODE: ${{ github.event.inputs.deploy_mode || vars.ANDROID_DEPLOY_MODE || 'store_only' }}
52
+
53
+ jobs:
54
+ prepare-build:
55
+ name: 환경 설정 및 준비
56
+ runs-on: ubuntu-latest
57
+ defaults:
58
+ run:
59
+ working-directory: ${{ env.FLUTTER_ROOT }}
60
+ # CHANGELOG 워크플로우가 성공한 경우에만 실행
61
+ if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name != 'workflow_run' }}
62
+
63
+ outputs:
64
+ version: ${{ steps.current_version.outputs.version }}
65
+ version_code: ${{ steps.current_version.outputs.version_code }}
66
+ project_type: ${{ steps.current_version.outputs.project_type }}
67
+
68
+ steps:
69
+ - name: Checkout repository
70
+ uses: actions/checkout@v5
71
+ with:
72
+ ref: main
73
+ fetch-depth: 0
74
+
75
+ - name: Pull latest changes
76
+ run: git pull origin main
77
+
78
+ - name: Create .env file
79
+ env:
80
+ ENV_CONTENT: ${{ secrets.ENV_FILE || secrets.ENV }}
81
+ run: |
82
+ printf '%s\n' "$ENV_CONTENT" > .env
83
+ echo ".env file created"
84
+
85
+ - name: Set up Flutter
86
+ uses: subosito/flutter-action@v2
87
+ with:
88
+ flutter-version: ${{ env.FLUTTER_VERSION }}
89
+ cache: true
90
+
91
+ - name: Cache Flutter dependencies
92
+ uses: actions/cache@v4
93
+ with:
94
+ path: ~/.pub-cache
95
+ key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }}
96
+ restore-keys: ${{ runner.os }}-flutter-pub-
97
+
98
+ - name: Install dependencies
99
+ run: flutter pub get
100
+
101
+ # version_manager.py를 사용하여 현재 버전 정보 가져오기
102
+ - name: 현재 버전 정보 가져오기
103
+ id: current_version
104
+ working-directory: ${{ github.workspace }} # version_manager.py가 레포 루트 version.yml을 읽어야 함
105
+ run: |
106
+ # VERSION 가져오기 (x.y.z 형식)
107
+ VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
108
+
109
+ # VERSION_CODE 가져오기 (version.yml에서 관리되는 단순 증가 번호)
110
+ VERSION_CODE=$(python3 .github/scripts/version_manager.py get-code | tail -n 1)
111
+
112
+ # 결과 출력
113
+ echo "✅ VERSION: $VERSION"
114
+ echo "✅ VERSION_CODE: $VERSION_CODE"
115
+ echo "🔧 프로젝트 타입: ${{ env.PROJECT_TYPE }}"
116
+
117
+ # GitHub outputs 설정
118
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
119
+ echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT
120
+ echo "project_type=${{ env.PROJECT_TYPE }}" >> $GITHUB_OUTPUT
121
+
122
+ # 환경변수로도 설정 (스크립트에서 사용)
123
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
124
+ echo "VERSION_CODE=$VERSION_CODE" >> $GITHUB_ENV
125
+ echo "TODAY=$(date '+%Y-%m-%d')" >> $GITHUB_ENV
126
+ echo "TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV
127
+ echo "PROJECT_TYPE=${{ env.PROJECT_TYPE }}" >> $GITHUB_ENV
128
+
129
+ # CHANGELOG.json에서 릴리즈 노트 추출 - changelog_manager.py 사용
130
+ - name: 릴리즈 노트 생성
131
+ id: release_notes
132
+ working-directory: ${{ github.workspace }} # CHANGELOG.json·changelog_manager.py·final_release_notes.txt(upload 짝)는 레포 루트 기준
133
+ run: |
134
+ VERSION="${{ steps.current_version.outputs.version }}"
135
+
136
+ if [ -f "CHANGELOG.json" ]; then
137
+ echo "📄 CHANGELOG.json에서 v$VERSION 릴리즈 노트 추출 중..."
138
+ # CHANGELOG.md 생성
139
+ python3 ./.github/scripts/changelog_manager.py generate-md
140
+
141
+ # changelog_manager.py export로 릴리즈 노트 추출
142
+ python3 ./.github/scripts/changelog_manager.py export --version $VERSION --output final_release_notes.txt
143
+
144
+ if [ -s final_release_notes.txt ]; then
145
+ echo "✅ 릴리즈 노트 추출 성공!"
146
+ echo "📋 추출된 릴리즈 노트:"
147
+ echo "----------------------------------------"
148
+ cat final_release_notes.txt
149
+ echo "----------------------------------------"
150
+ echo "RELEASE_NOTES_FOUND=true" >> $GITHUB_ENV
151
+ else
152
+ echo "❌ 릴리즈 노트 추출 실패"
153
+ echo "RELEASE_NOTES_FOUND=false" >> $GITHUB_ENV
154
+ echo "v$VERSION 업데이트" > final_release_notes.txt
155
+ fi
156
+ else
157
+ echo "⚠️ CHANGELOG.json 파일이 없습니다. 기본 릴리즈 노트를 사용합니다."
158
+ echo "RELEASE_NOTES_FOUND=false" >> $GITHUB_ENV
159
+ echo "v$VERSION 업데이트" > final_release_notes.txt
160
+ fi
161
+
162
+ # 릴리즈 노트를 아티팩트로 업로드
163
+ - name: Upload release notes
164
+ uses: actions/upload-artifact@v4
165
+ with:
166
+ name: release-notes
167
+ path: final_release_notes.txt
168
+ retention-days: 1
169
+
170
+ # 프로젝트 파일들을 아티팩트로 업로드
171
+ # .env는 보안상 아티팩트에 포함하지 않고, 각 job에서 secrets로 재생성
172
+ - name: Upload project files
173
+ uses: actions/upload-artifact@v4
174
+ with:
175
+ name: project-files
176
+ path: |
177
+ pubspec.yaml
178
+ lib/
179
+ assets/
180
+ retention-days: 1
181
+
182
+ build-android:
183
+ name: Android AAB 빌드
184
+ runs-on: ubuntu-latest
185
+ needs: prepare-build
186
+ defaults:
187
+ run:
188
+ working-directory: ${{ env.FLUTTER_ROOT }}
189
+
190
+ steps:
191
+ - name: Checkout repository
192
+ uses: actions/checkout@v5
193
+ with:
194
+ ref: main
195
+ fetch-depth: 0
196
+
197
+ - name: Pull latest changes
198
+ run: git pull origin main
199
+
200
+ # 프로젝트 파일들 다운로드
201
+ - name: Download project files
202
+ uses: actions/download-artifact@v4
203
+ with:
204
+ name: project-files
205
+ path: .
206
+
207
+ # .env 파일 생성 (보안을 위해 아티팩트가 아닌 시크릿에서 생성)
208
+ - name: Create .env file
209
+ env:
210
+ ENV_CONTENT: ${{ secrets.ENV_FILE || secrets.ENV }}
211
+ run: |
212
+ printf '%s\n' "$ENV_CONTENT" > .env
213
+ echo "✅ .env 파일 생성됨 (크기: $(wc -c < .env) bytes)"
214
+
215
+ # Release Keystore 설정
216
+ - name: Setup Release Keystore
217
+ run: |
218
+ mkdir -p android/app/keystore
219
+ echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore/key.jks
220
+ echo "✅ Release Keystore 생성 완료"
221
+ ls -la android/app/keystore/
222
+
223
+ # key.properties 생성 (Release 서명 정보)
224
+ - name: Create key.properties
225
+ run: |
226
+ cat > android/key.properties << EOF
227
+ storeFile=app/keystore/key.jks
228
+ storePassword=${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
229
+ keyAlias=${{ secrets.RELEASE_KEY_ALIAS }}
230
+ keyPassword=${{ secrets.RELEASE_KEY_PASSWORD }}
231
+ EOF
232
+ echo "✅ key.properties 생성 완료"
233
+ cat android/key.properties
234
+
235
+ # Google-services.json 생성
236
+ - name: Create Google-services.json
237
+ run: |
238
+ cat << 'EOF' > android/app/google-services.json
239
+ ${{ secrets.GOOGLE_SERVICES_JSON }}
240
+ EOF
241
+ echo "✅ Google-services.json 생성 완료"
242
+
243
+ - name: Set up Flutter
244
+ uses: subosito/flutter-action@v2
245
+ with:
246
+ flutter-version: ${{ env.FLUTTER_VERSION }}
247
+ cache: true
248
+
249
+ - name: Cache Flutter dependencies
250
+ uses: actions/cache@v4
251
+ with:
252
+ path: ~/.pub-cache
253
+ key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }}
254
+ restore-keys: ${{ runner.os }}-flutter-pub-
255
+
256
+ - name: Install dependencies
257
+ run: flutter pub get
258
+
259
+ - name: Set up Java
260
+ uses: actions/setup-java@v4
261
+ with:
262
+ distribution: "temurin"
263
+ java-version: ${{ env.JAVA_VERSION }}
264
+
265
+ - name: Cache Gradle dependencies
266
+ uses: actions/cache@v4
267
+ with:
268
+ path: |
269
+ ~/.gradle/caches
270
+ ~/.gradle/wrapper
271
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/build.gradle*', '**/gradle-wrapper.properties') }}
272
+ restore-keys: |
273
+ ${{ runner.os }}-gradle-
274
+
275
+ # Android build.gradle.kts 수정 (release 서명 설정)
276
+ - name: Configure Android build for release
277
+ run: |
278
+ # build.gradle.kts에 release 서명 설정이 없으면 추가
279
+ if ! grep -q "signingConfigs" android/app/build.gradle.kts; then
280
+ echo "⚠️ build.gradle.kts에 서명 설정 추가 필요"
281
+ # 서명 설정은 이미 되어있다고 가정 (수동으로 미리 설정해야 함)
282
+ fi
283
+ echo "✅ Android 빌드 설정 확인 완료"
284
+
285
+ # pubspec.yaml은 x.y.z 형식 그대로 유지
286
+ # Flutter build 명령어의 --build-name, --build-number 플래그가 pubspec.yaml을 오버라이드함
287
+
288
+ # Flutter build appbundle (AAB 생성)
289
+ - name: Build Android App Bundle (AAB)
290
+ run: |
291
+ VERSION_NAME="${{ needs.prepare-build.outputs.version }}"
292
+ VERSION_CODE="${{ needs.prepare-build.outputs.version_code }}"
293
+
294
+ echo "=========================================="
295
+ echo "🚀 AAB 빌드 시작"
296
+ echo "=========================================="
297
+ echo "🎯 빌드 버전: $VERSION_NAME"
298
+ echo "🎯 빌드 코드: $VERSION_CODE"
299
+
300
+ # 입력 검증
301
+ if [ -z "$VERSION_NAME" ] || [ -z "$VERSION_CODE" ]; then
302
+ echo "❌ VERSION_NAME 또는 VERSION_CODE가 비어있습니다!"
303
+ exit 1
304
+ fi
305
+
306
+ # Flutter 환경 정보 출력
307
+ echo "🔍 Flutter 환경 정보:"
308
+ flutter --version
309
+ flutter doctor -v | head -20
310
+
311
+ # 빌드 디렉토리 정리
312
+ echo "🧹 이전 빌드 결과물 정리..."
313
+ rm -rf build/app/outputs/bundle/release/
314
+ mkdir -p build/app/outputs/bundle/release/
315
+
316
+ # Flutter clean 및 pub get
317
+ echo "🔄 Flutter clean 및 의존성 재설치..."
318
+ flutter clean
319
+ flutter pub get
320
+
321
+ # local.properties에 버전 정보 추가 (Flutter 플래그가 무시되는 문제 해결)
322
+ echo "🔧 local.properties에 버전 정보 설정..."
323
+
324
+ # 기존 local.properties가 있으면 버전 정보 제거
325
+ if [ -f "android/local.properties" ]; then
326
+ grep -v "flutter.versionName\|flutter.versionCode" android/local.properties > android/local.properties.tmp || true
327
+ mv android/local.properties.tmp android/local.properties
328
+ else
329
+ touch android/local.properties
330
+ fi
331
+
332
+ # 버전 정보 추가
333
+ echo "flutter.versionName=$VERSION_NAME" >> android/local.properties
334
+ echo "flutter.versionCode=$VERSION_CODE" >> android/local.properties
335
+
336
+ echo "✅ local.properties 업데이트 완료"
337
+ echo "📋 버전 정보: VERSION_NAME=$VERSION_NAME, VERSION_CODE=$VERSION_CODE"
338
+
339
+ # Gradle 성능 최적화 설정
340
+ export GRADLE_OPTS="-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=4"
341
+ export JAVA_OPTS="-Xmx4g"
342
+ echo "✅ Gradle 최적화 설정 완료"
343
+
344
+ # Flutter build 명령어에 버전 정보 전달
345
+ # --build-name과 --build-number 플래그가 pubspec.yaml의 version을 오버라이드함
346
+ # pubspec.yaml은 x.y.z 형식 유지, VERSION_CODE는 version.yml에서 자동 관리
347
+ echo "🔨 Flutter build 실행..."
348
+ echo "📋 명령어: flutter build appbundle --release --build-name=$VERSION_NAME --build-number=$VERSION_CODE --verbose"
349
+ echo "⏱️ 빌드 시작 시간: $(date '+%Y-%m-%d %H:%M:%S')"
350
+
351
+ flutter build appbundle \
352
+ --release \
353
+ --build-name="$VERSION_NAME" \
354
+ --build-number="$VERSION_CODE" \
355
+ --verbose
356
+
357
+ BUILD_EXIT_CODE=$?
358
+
359
+ if [ $BUILD_EXIT_CODE -ne 0 ]; then
360
+ echo "❌ Flutter build 실패! Exit code: $BUILD_EXIT_CODE"
361
+ echo "🔍 빌드 디렉토리 상태:"
362
+ find build/ -name "*.aab" -o -name "*.apk" 2>/dev/null || echo "AAB/APK 파일을 찾을 수 없습니다"
363
+ exit 1
364
+ fi
365
+
366
+ echo "✅ Flutter build 성공!"
367
+
368
+ # 빌드 결과 확인
369
+ echo "🔍 빌드 결과 확인:"
370
+ ls -lah build/app/outputs/bundle/release/
371
+
372
+ # AAB 파일 존재 확인
373
+ AAB_FILE="build/app/outputs/bundle/release/app-release.aab"
374
+ if [ ! -f "$AAB_FILE" ]; then
375
+ echo "❌ AAB 파일이 생성되지 않았습니다!"
376
+ echo "🔍 전체 빌드 디렉토리 내용:"
377
+ find build/ -type f -name "*.aab" -o -name "*.apk" 2>/dev/null || echo "AAB/APK 파일을 찾을 수 없습니다"
378
+ echo "🔍 outputs 디렉토리 구조:"
379
+ find build/app/outputs/ -type f 2>/dev/null || echo "outputs 디렉토리가 없습니다"
380
+ exit 1
381
+ fi
382
+
383
+ echo "✅ AAB 파일 생성 확인"
384
+ echo "📱 AAB 파일 크기: $(du -h "$AAB_FILE" | cut -f1)"
385
+ echo "📋 AAB 파일 정보: $(ls -lah "$AAB_FILE")"
386
+
387
+ # bundletool 다운로드 및 설치
388
+ echo "📦 bundletool 다운로드 중..."
389
+ BUNDLETOOL_VERSION="1.15.6"
390
+ BUNDLETOOL_URL="https://github.com/google/bundletool/releases/download/${BUNDLETOOL_VERSION}/bundletool-all-${BUNDLETOOL_VERSION}.jar"
391
+
392
+ echo "📋 다운로드 URL: $BUNDLETOOL_URL"
393
+ wget -q "$BUNDLETOOL_URL" -O bundletool.jar
394
+
395
+ if [ ! -f "bundletool.jar" ]; then
396
+ echo "❌ bundletool 다운로드 실패"
397
+ echo "🔄 대체 URL로 재시도..."
398
+ wget -q "https://github.com/google/bundletool/releases/latest/download/bundletool-all-1.15.6.jar" -O bundletool.jar
399
+
400
+ if [ ! -f "bundletool.jar" ]; then
401
+ echo "❌ bundletool 다운로드 완전 실패"
402
+ exit 1
403
+ fi
404
+ fi
405
+
406
+ echo "✅ bundletool 다운로드 완료"
407
+ echo "📋 bundletool 파일 크기: $(du -h bundletool.jar | cut -f1)"
408
+
409
+ # Java 환경 확인
410
+ echo "🔍 Java 환경 확인:"
411
+ java -version
412
+
413
+ # AAB 파일 내부의 실제 버전 정보 확인
414
+ # Flutter CLI 플래그로 전달한 VERSION_CODE가 제대로 적용되었는지 검증
415
+ echo "=========================================="
416
+ echo "🔍 AAB 파일 내부 버전 정보 검증"
417
+ echo "=========================================="
418
+
419
+ # AndroidManifest.xml에서 버전 정보 추출
420
+ echo "📋 AAB에서 실제 버전 정보 추출 중..."
421
+ echo "📋 실행할 명령어: java -jar bundletool.jar dump manifest --bundle='$AAB_FILE'"
422
+
423
+ MANIFEST_OUTPUT=$(java -jar bundletool.jar dump manifest --bundle="$AAB_FILE" 2>&1)
424
+ MANIFEST_EXIT_CODE=$?
425
+
426
+ echo "📋 bundletool 실행 결과 (Exit code: $MANIFEST_EXIT_CODE):"
427
+ echo "$MANIFEST_OUTPUT"
428
+
429
+ if [ $MANIFEST_EXIT_CODE -eq 0 ]; then
430
+ echo "✅ Manifest 추출 성공"
431
+
432
+ # 실제 버전 코드와 버전 이름 추출
433
+ ACTUAL_VERSION_CODE=$(echo "$MANIFEST_OUTPUT" | grep -o 'android:versionCode="[0-9]*"' | grep -o '[0-9]*' | head -1)
434
+ ACTUAL_VERSION_NAME=$(echo "$MANIFEST_OUTPUT" | grep -o 'android:versionName="[^"]*"' | sed 's/android:versionName="//;s/"//' | head -1)
435
+
436
+ echo "🎯 버전 정보 비교:"
437
+ echo " 예상 VERSION_CODE: '$VERSION_CODE'"
438
+ echo " 실제 VERSION_CODE: '$ACTUAL_VERSION_CODE'"
439
+ echo " 예상 VERSION_NAME: '$VERSION_NAME'"
440
+ echo " 실제 VERSION_NAME: '$ACTUAL_VERSION_NAME'"
441
+
442
+ # VERSION_CODE 검증 - version.yml에서 관리되는 단순 증가 번호
443
+ if [ -z "$ACTUAL_VERSION_CODE" ]; then
444
+ echo "❌ 치명적 오류: AAB에서 VERSION_CODE를 추출할 수 없습니다!"
445
+ echo "🔍 전체 Manifest 내용:"
446
+ echo "$MANIFEST_OUTPUT"
447
+ exit 1
448
+ elif [ "$ACTUAL_VERSION_CODE" != "$VERSION_CODE" ]; then
449
+ echo "❌ 치명적 오류: AAB의 VERSION_CODE가 예상과 다릅니다!"
450
+ echo " 이는 Google Play에서 '이미 사용된 버전 코드' 오류를 발생시킵니다."
451
+ echo ""
452
+ echo "🔍 디버깅 정보:"
453
+ echo " version.yml의 VERSION_CODE: $VERSION_CODE"
454
+ echo " AAB 파일 내부의 VERSION_CODE: $ACTUAL_VERSION_CODE"
455
+ echo ""
456
+ echo " Flutter build 명령어에 전달된 플래그:"
457
+ echo " --build-name='$VERSION_NAME'"
458
+ echo " --build-number='$VERSION_CODE'"
459
+ echo ""
460
+ echo " 전체 Manifest 내용:"
461
+ echo "$MANIFEST_OUTPUT" | grep -E "versionCode|versionName|package"
462
+ exit 1
463
+ else
464
+ echo "✅ VERSION_CODE 검증 성공: $ACTUAL_VERSION_CODE"
465
+ fi
466
+
467
+ # 버전 이름도 확인 (경고만)
468
+ if [ -z "$ACTUAL_VERSION_NAME" ]; then
469
+ echo "⚠️ 경고: AAB에서 VERSION_NAME을 추출할 수 없습니다"
470
+ elif [ "$ACTUAL_VERSION_NAME" != "$VERSION_NAME" ]; then
471
+ echo "⚠️ 경고: VERSION_NAME이 예상과 다릅니다 (치명적이지 않음)"
472
+ echo " 예상: '$VERSION_NAME', 실제: '$ACTUAL_VERSION_NAME'"
473
+ else
474
+ echo "✅ VERSION_NAME 검증 성공: $ACTUAL_VERSION_NAME"
475
+ fi
476
+
477
+ else
478
+ echo "❌ bundletool로 Manifest 추출 실패"
479
+ echo "🔄 대체 방법으로 검증 시도..."
480
+
481
+ # unzip으로 직접 확인 시도
482
+ echo "📋 unzip을 사용한 직접 추출 시도..."
483
+ UNZIP_OUTPUT=$(unzip -p "$AAB_FILE" base/manifest/AndroidManifest.xml 2>/dev/null | strings | grep -E "versionCode|versionName")
484
+
485
+ if [ -n "$UNZIP_OUTPUT" ]; then
486
+ echo "✅ unzip으로 버전 정보 추출 성공:"
487
+ echo "$UNZIP_OUTPUT"
488
+ else
489
+ echo "❌ unzip을 사용한 직접 추출도 실패"
490
+ echo "⚠️ AAB 검증을 건너뛰고 계속 진행합니다."
491
+ fi
492
+ fi
493
+
494
+ echo "=========================================="
495
+ echo "✅ AAB 빌드 및 검증 완료"
496
+ echo "🎯 최종 AAB: $AAB_FILE"
497
+ echo "📱 파일 크기: $(du -h "$AAB_FILE" | cut -f1)"
498
+ echo "=========================================="
499
+
500
+ # AAB 파일을 아티팩트로 업로드
501
+ - name: Upload AAB artifact
502
+ uses: actions/upload-artifact@v4
503
+ with:
504
+ name: android-aab
505
+ path: ${{ env.FLUTTER_ROOT }}/build/app/outputs/bundle/release/app-release.aab
506
+ retention-days: 1
507
+
508
+ deploy-playstore:
509
+ name: Play Store 내부 테스트 배포
510
+ runs-on: ubuntu-latest
511
+ needs: [prepare-build, build-android]
512
+ defaults:
513
+ run:
514
+ working-directory: ${{ env.FLUTTER_ROOT }}
515
+
516
+ steps:
517
+ - name: Checkout repository
518
+ uses: actions/checkout@v5
519
+ with:
520
+ ref: main
521
+ fetch-depth: 0
522
+
523
+ # Service Account JSON 설정
524
+ - name: Setup Google Play Service Account
525
+ run: |
526
+ mkdir -p ~/.config/gcloud
527
+ echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}" | base64 --decode > ~/.config/gcloud/service-account.json
528
+ echo "✅ Service Account JSON 설정 완료"
529
+
530
+ # AAB 파일 다운로드
531
+ - name: Download AAB artifact
532
+ uses: actions/download-artifact@v4
533
+ with:
534
+ name: android-aab
535
+ path: ${{ env.FLUTTER_ROOT }}/build/app/outputs/bundle/release/
536
+
537
+ # 릴리즈 노트 다운로드
538
+ - name: Download release notes
539
+ uses: actions/download-artifact@v4
540
+ with:
541
+ name: release-notes
542
+ path: .
543
+
544
+ # Ruby 및 Fastlane 설치
545
+ - name: Set up Ruby
546
+ uses: ruby/setup-ruby@v1
547
+ with:
548
+ ruby-version: "3.4.1"
549
+
550
+ - name: Install Fastlane
551
+ working-directory: ${{ env.FLUTTER_ROOT }}/android
552
+ run: |
553
+ # multi_json: google-apis transitive 의존성이 선언 누락한 upstream 버그 회피 (gemspec 미선언 → Gem::LoadError)
554
+ printf 'source "https://rubygems.org"\ngem "fastlane"\ngem "multi_json"\n' > Gemfile
555
+ bundle install
556
+ echo "✅ Fastlane 설치 완료 (Bundler)"
557
+ bundle exec fastlane --version
558
+
559
+ # Fastfile 복사 (Play Store 배포용)
560
+ # android/fastlane/Fastfile.playstore를 사용하여 일관성 유지
561
+ - name: Copy Fastfile for Play Store
562
+ run: |
563
+ mkdir -p android/fastlane
564
+ cp android/fastlane/Fastfile.playstore android/fastlane/Fastfile
565
+ echo "✅ Fastfile 복사 완료 (Fastfile.playstore → Fastfile)"
566
+ echo "📋 Fastfile 내용:"
567
+ cat android/fastlane/Fastfile
568
+
569
+ # Play Store에 업로드
570
+ - name: Upload to Play Store Internal Testing
571
+ env:
572
+ DEPLOY_MODE: ${{ env.DEPLOY_MODE }}
573
+ run: |
574
+ echo "=========================================="
575
+ echo "🚀 Play Store 배포 시작"
576
+ echo "=========================================="
577
+ echo "🕐 배포 시작 시간: $(date '+%Y-%m-%d %H:%M:%S')"
578
+ echo "🔍 GitHub Run Number: ${{ github.run_number }}"
579
+ echo "🔍 GitHub SHA: ${{ github.sha }}"
580
+
581
+ # 환경 변수 초기화 및 검증
582
+ VERSION_CODE="${{ needs.prepare-build.outputs.version_code }}"
583
+ VERSION_NAME="${{ needs.prepare-build.outputs.version }}"
584
+
585
+ echo "📋 배포 대상 버전 정보:"
586
+ echo " VERSION_NAME: '$VERSION_NAME'"
587
+ echo " VERSION_CODE: '$VERSION_CODE'"
588
+
589
+ # 입력 검증
590
+ if [ -z "$VERSION_NAME" ] || [ -z "$VERSION_CODE" ]; then
591
+ echo "❌ 치명적 오류: VERSION_NAME 또는 VERSION_CODE가 비어있습니다!"
592
+ echo "VERSION_NAME: '$VERSION_NAME'"
593
+ echo "VERSION_CODE: '$VERSION_CODE'"
594
+ echo "이는 prepare-build 작업에서 출력이 제대로 전달되지 않았음을 의미합니다."
595
+ exit 1
596
+ fi
597
+
598
+ # AAB 파일 절대 경로 찾기
599
+ AAB_PATH=$(realpath build/app/outputs/bundle/release/app-release.aab)
600
+ echo "📦 AAB 파일 경로: $AAB_PATH"
601
+
602
+ if [ ! -f "$AAB_PATH" ]; then
603
+ echo "❌ AAB 파일을 찾을 수 없습니다!"
604
+ echo "🔍 빌드 디렉토리 내용:"
605
+ ls -la build/app/outputs/bundle/release/ || echo "디렉토리가 존재하지 않습니다"
606
+ echo "🔍 전체 빌드 디렉토리 구조:"
607
+ find build/ -name "*.aab" -o -name "*.apk" 2>/dev/null || echo "AAB/APK 파일을 찾을 수 없습니다"
608
+ exit 1
609
+ fi
610
+
611
+ echo "✅ AAB 파일 존재 확인"
612
+ echo "📋 AAB 파일 정보: $(ls -lah "$AAB_PATH")"
613
+ echo "📱 AAB 파일 크기: $(du -h "$AAB_PATH" | cut -f1)"
614
+
615
+ # 배포 전 AAB 버전 검증
616
+ echo "=========================================="
617
+ echo "🔍 AAB 버전 검증 중..."
618
+ echo "=========================================="
619
+
620
+ # bundletool 다운로드
621
+ if [ ! -f "bundletool.jar" ]; then
622
+ wget -q "https://github.com/google/bundletool/releases/download/1.15.6/bundletool-all-1.15.6.jar" -O bundletool.jar
623
+ fi
624
+
625
+ if [ -f "bundletool.jar" ]; then
626
+ MANIFEST_OUTPUT=$(java -jar bundletool.jar dump manifest --bundle="$AAB_PATH" 2>&1)
627
+
628
+ if [ $? -eq 0 ]; then
629
+ FINAL_VERSION_CODE=$(echo "$MANIFEST_OUTPUT" | grep -o 'android:versionCode="[0-9]*"' | grep -o '[0-9]*' | head -1)
630
+ FINAL_VERSION_NAME=$(echo "$MANIFEST_OUTPUT" | grep -o 'android:versionName="[^"]*"' | sed 's/android:versionName="//;s/"//' | head -1)
631
+
632
+ echo "📋 AAB 버전: $FINAL_VERSION_NAME (코드: $FINAL_VERSION_CODE)"
633
+
634
+ # VERSION_CODE 검증
635
+ if [ "$FINAL_VERSION_CODE" != "$VERSION_CODE" ]; then
636
+ echo "❌ 오류: AAB 버전 코드가 예상과 다릅니다! (예상: $VERSION_CODE, 실제: $FINAL_VERSION_CODE)"
637
+ exit 1
638
+ fi
639
+
640
+ echo "✅ 버전 검증 성공"
641
+ else
642
+ echo "⚠️ 버전 검증 실패, 배포 계속 진행"
643
+ fi
644
+ fi
645
+
646
+ # Release notes 준비
647
+ CHANGELOG_DIR="android/fastlane/metadata/android/ko-KR/changelogs"
648
+ mkdir -p "$CHANGELOG_DIR"
649
+
650
+ if [ -f "$GITHUB_WORKSPACE/final_release_notes.txt" ]; then
651
+ # Google Play release notes 한도(500 글자)에 맞춰 절단 (마진 두어 480자)
652
+ # final_release_notes.txt·truncate 스크립트는 레포 루트 기준 — working-directory가 FLUTTER_ROOT이므로 절대경로 사용
653
+ bash "$GITHUB_WORKSPACE/.github/scripts/truncate_release_notes.sh" "$GITHUB_WORKSPACE/final_release_notes.txt" 480 char
654
+ cp "$GITHUB_WORKSPACE/final_release_notes.txt" "$CHANGELOG_DIR/${VERSION_CODE}.txt"
655
+ echo "✅ Changelog 준비 완료 (버전 코드: $VERSION_CODE)"
656
+ else
657
+ echo "v$VERSION_NAME 업데이트" > "$CHANGELOG_DIR/${VERSION_CODE}.txt"
658
+ echo "⚠️ 기본 Changelog 생성 (버전 코드: $VERSION_CODE)"
659
+ fi
660
+
661
+ # Service Account JSON 파일 확인
662
+ GOOGLE_PLAY_JSON_KEY="$HOME/.config/gcloud/service-account.json"
663
+ if [ ! -f "$GOOGLE_PLAY_JSON_KEY" ]; then
664
+ echo "❌ Service Account JSON 파일을 찾을 수 없습니다"
665
+ exit 1
666
+ fi
667
+
668
+ # 환경변수 설정
669
+ export AAB_PATH="$AAB_PATH"
670
+ export GOOGLE_PLAY_JSON_KEY="$GOOGLE_PLAY_JSON_KEY"
671
+ export VERSION_NAME="$VERSION_NAME"
672
+ export VERSION_CODE="$VERSION_CODE"
673
+
674
+ # 배포 실행
675
+ echo "=========================================="
676
+ echo "🚀 Play Store 배포 실행"
677
+ echo "🎯 버전: $VERSION_NAME (코드: $VERSION_CODE)"
678
+ echo "=========================================="
679
+
680
+ cd android
681
+ bundle exec fastlane deploy_internal
682
+
683
+ # 성공 알림
684
+ - name: Notify Play Store Upload Success
685
+ if: success()
686
+ run: |
687
+ echo "✅ Play Store 내부 테스트 배포 성공!"
688
+ echo "버전: ${{ needs.prepare-build.outputs.version }} (${{ needs.prepare-build.outputs.version_code }})"
689
+ echo "커밋: ${{ github.sha }}"
690
+ echo ""
691
+ echo "📱 테스터 확인 방법:"
692
+ echo "1. Play Console → 테스트 및 출시 → 내부 테스트"
693
+ echo "2. 테스터에게 테스트 링크 공유"
694
+ # Play Console URL은 프로젝트별로 다르므로 별도 설정 필요
695
+
696
+ - name: Notify on Failure
697
+ if: failure()
698
+ run: |
699
+ echo "❌ Play Store 배포 실패!"
700
+ echo "로그를 확인해주세요."