project-auto-wizard 0.12.2 → 0.13.0

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 (86) hide show
  1. package/README.ja.md +165 -0
  2. package/README.ko.md +165 -0
  3. package/README.md +107 -286
  4. package/README.zh-CN.md +165 -0
  5. package/bin/project-auto-wizard.js +1 -2
  6. package/package.json +7 -3
  7. package/payload/config/breaking-changes.json +24 -0
  8. package/payload/config/wizard-prompts.yml +45 -0
  9. package/payload/scripts/changelog_manager.py +141 -46
  10. package/payload/scripts/issue_helper.py +34 -5
  11. package/payload/scripts/version_manager.py +397 -97
  12. package/payload/version.yml.template +4 -2
  13. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +39 -9
  14. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +165 -38
  15. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +14 -5
  16. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +220 -6
  17. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +89 -3
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +78 -16
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +72 -16
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +62 -20
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +51 -13
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +75 -11
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +52 -3
  24. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +60 -11
  25. package/payload/workflows/go/PROJECT-GO-CI.yaml +10 -1
  26. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +148 -28
  27. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +45 -4
  28. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +25 -3
  29. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +83 -9
  30. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +70 -6
  31. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +148 -28
  32. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +45 -4
  33. package/payload/workflows/react/PROJECT-REACT-CI.yaml +25 -3
  34. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +84 -10
  35. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +13 -3
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +47 -5
  37. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +47 -5
  38. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +153 -30
  39. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +47 -5
  40. package/src/cli/args.js +33 -24
  41. package/src/cli/help.js +16 -4
  42. package/src/commands/doctor.js +17 -10
  43. package/src/commands/dry-run.js +54 -1
  44. package/src/commands/full.js +127 -50
  45. package/src/commands/interactive-flutter.js +1 -1
  46. package/src/commands/interactive.js +124 -56
  47. package/src/commands/purge.js +59 -17
  48. package/src/commands/status.js +16 -1
  49. package/src/commands/uninstall.js +58 -13
  50. package/src/context.js +5 -5
  51. package/src/core/baseline.js +20 -5
  52. package/src/core/branches.js +19 -1
  53. package/src/core/branding.js +1 -1
  54. package/src/core/breaking-check.js +14 -19
  55. package/src/core/breaking.js +7 -3
  56. package/src/core/copy/gitignore.js +20 -12
  57. package/src/core/copy/readme.js +24 -5
  58. package/src/core/copy/simple.js +52 -13
  59. package/src/core/copy/workflows.js +90 -47
  60. package/src/core/deploy-style.js +83 -11
  61. package/src/core/detect-fs.js +127 -24
  62. package/src/core/detect.js +131 -47
  63. package/src/core/errors.js +3 -0
  64. package/src/core/flutter-options.js +4 -3
  65. package/src/core/fsutil.js +23 -6
  66. package/src/core/logger.js +55 -19
  67. package/src/core/paths-resolve.js +50 -47
  68. package/src/core/paths.js +26 -1
  69. package/src/core/release-options.js +23 -0
  70. package/src/core/removal-plan.js +75 -19
  71. package/src/core/types.js +113 -0
  72. package/src/core/verify.js +85 -19
  73. package/src/core/version-yml.js +42 -8
  74. package/src/core/wizard-env.js +21 -14
  75. package/src/core/wizard-labels.js +34 -25
  76. package/src/index.js +122 -49
  77. package/src/ui/ansi.js +3 -1
  78. package/src/ui/banner.js +12 -8
  79. package/src/ui/env-plan.js +68 -44
  80. package/src/ui/prompts.js +22 -12
  81. package/src/ui/readline-engine.js +109 -22
  82. package/src/ui/status-cards.js +10 -5
  83. package/src/ui/summary.js +71 -33
  84. package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
  85. package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
  86. package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
@@ -10,7 +10,7 @@
10
10
  # RELEASE_KEYSTORE_PASSWORD: keystore 비밀번호
11
11
  # RELEASE_KEY_ALIAS: key alias
12
12
  # RELEASE_KEY_PASSWORD: key 비밀번호
13
- # GOOGLE_SERVICES_JSON: Firebase google-services.json
13
+ # GOOGLE_SERVICES_JSON (선택): Firebase google-services.json
14
14
  # GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64: Play Console 서비스 계정 (base64)
15
15
  # ANDROID_PACKAGE_NAME: 앱 패키지명 (Secret 우선, 없으면 Variable) — fastlane에 PACKAGE_NAME으로 전달
16
16
 
@@ -25,7 +25,7 @@ on:
25
25
  deploy_mode:
26
26
  description: "배포 모드 (store_only=internal까지 / store_prepare=production draft / store_submit=심사 자동등록)"
27
27
  required: false
28
- default: "store_only"
28
+ default: "store_only" # @wizard auto:android-deploy-mode
29
29
  type: choice
30
30
  options:
31
31
  - store_only
@@ -37,7 +37,9 @@ env:
37
37
  # 프로젝트 이름
38
38
  APP_ARTIFACT_NAME: "__APP_ARTIFACT_NAME__" # @wizard ask:@repo
39
39
  # Flutter/Java 버전
40
- FLUTTER_VERSION: "3.35.5"
40
+ # Flutter SDK 버전 — 비워 두면 stable 채널 최신을 쓴다(최신 flutter create 프로젝트의 SDK 제약과 맞음).
41
+ # 특정 버전에 고정하려면 "3.38.9"처럼 적는다. pubspec.yaml의 environment.sdk 범위를 만족해야 한다.
42
+ FLUTTER_VERSION: ""
41
43
  JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:17
42
44
  PROJECT_TYPE: "flutter"
43
45
  # Flutter 루트 경로(레포 루트 기준). 단일레포면 ".", 모노레포면 "app" 등.
@@ -67,11 +69,34 @@ jobs:
67
69
  - name: Checkout repository
68
70
  uses: actions/checkout@v7
69
71
  with:
70
- ref: main
72
+ ref: "{{MAIN_BRANCH}}"
71
73
  fetch-depth: 0
72
74
 
75
+ # 서명·업로드 Secret이 없으면 빌드를 한참 진행한 뒤에야 실패하므로, 빠진 이름을 먼저 모두 알려 주고 멈춘다
76
+ # GOOGLE_SERVICES_JSON은 선택이라 점검하지 않는다
77
+ - name: 배포 사전 점검 (Secret)
78
+ env:
79
+ RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
80
+ RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
81
+ RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
82
+ RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
83
+ GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}
84
+ ANDROID_PACKAGE_NAME: ${{ secrets.ANDROID_PACKAGE_NAME || vars.ANDROID_PACKAGE_NAME }}
85
+ run: |
86
+ MISSING=""
87
+ for NAME in RELEASE_KEYSTORE_BASE64 RELEASE_KEYSTORE_PASSWORD RELEASE_KEY_ALIAS RELEASE_KEY_PASSWORD GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64; do
88
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
89
+ done
90
+ # 패키지명은 Secret 대신 Variable로 등록해도 된다
91
+ [ -n "$ANDROID_PACKAGE_NAME" ] || MISSING="$MISSING ANDROID_PACKAGE_NAME(Secret 또는 Variable)"
92
+ if [ -n "$MISSING" ]; then
93
+ echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
94
+ exit 1
95
+ fi
96
+ echo "✅ 사전 점검 통과: 필수 Secret이 모두 있습니다"
97
+
73
98
  - name: Pull latest changes
74
- run: git pull origin main
99
+ run: git pull origin {{MAIN_BRANCH}}
75
100
 
76
101
  - name: Prepare env file
77
102
  env:
@@ -90,6 +115,7 @@ jobs:
90
115
  - name: Set up Flutter
91
116
  uses: subosito/flutter-action@v2
92
117
  with:
118
+ channel: stable
93
119
  flutter-version: ${{ env.FLUTTER_VERSION }}
94
120
  cache: true
95
121
 
@@ -199,11 +225,11 @@ jobs:
199
225
  - name: Checkout repository
200
226
  uses: actions/checkout@v7
201
227
  with:
202
- ref: main
228
+ ref: "{{MAIN_BRANCH}}"
203
229
  fetch-depth: 0
204
230
 
205
231
  - name: Pull latest changes
206
- run: git pull origin main
232
+ run: git pull origin {{MAIN_BRANCH}}
207
233
 
208
234
  - name: Download project files
209
235
  uses: actions/download-artifact@v8
@@ -227,9 +253,27 @@ jobs:
227
253
  fi
228
254
 
229
255
  - name: Setup Release Keystore
256
+ env:
257
+ RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
258
+ RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
259
+ RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
260
+ RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
230
261
  run: |
262
+ # 빈 Secret으로 빈 keystore를 만들면 어떤 키로 서명됐는지 알 수 없거나 뒤 단계에서 엉뚱한 오류로 끝난다
263
+ MISSING=""
264
+ for name in RELEASE_KEYSTORE_BASE64 RELEASE_KEYSTORE_PASSWORD RELEASE_KEY_ALIAS RELEASE_KEY_PASSWORD; do
265
+ [ -n "${!name}" ] || MISSING="$MISSING $name"
266
+ done
267
+ if [ -n "$MISSING" ]; then
268
+ echo "::error::서명 Secret이 비어 있습니다:$MISSING (워크플로우 상단 주석 참고)"
269
+ exit 1
270
+ fi
231
271
  mkdir -p android/app/keystore
232
- echo "${{ secrets.RELEASE_KEYSTORE_BASE64 }}" | base64 -d > android/app/keystore/key.jks
272
+ printf '%s' "$RELEASE_KEYSTORE_BASE64" | base64 -d > android/app/keystore/key.jks
273
+ if [ ! -s android/app/keystore/key.jks ]; then
274
+ echo "::error::RELEASE_KEYSTORE_BASE64를 디코딩한 keystore가 비어 있습니다"
275
+ exit 1
276
+ fi
233
277
  echo "✅ Release Keystore 생성 완료"
234
278
  ls -la android/app/keystore/
235
279
 
@@ -242,18 +286,24 @@ jobs:
242
286
  keyPassword=${{ secrets.RELEASE_KEY_PASSWORD }}
243
287
  EOF
244
288
  echo "✅ key.properties 생성 완료"
245
- cat android/key.properties
246
289
 
247
- - name: Create Google-services.json
290
+ # google-services.json 생성 (선택) — 비어 있으면 빈 파일을 만들지 않는다
291
+ - name: Create google-services.json
292
+ env:
293
+ GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
248
294
  run: |
249
- cat << 'EOF' > android/app/google-services.json
250
- ${{ secrets.GOOGLE_SERVICES_JSON }}
251
- EOF
252
- echo "✅ Google-services.json 생성 완료"
295
+ if [ -n "$GOOGLE_SERVICES_JSON" ]; then
296
+ mkdir -p android/app
297
+ printf '%s' "$GOOGLE_SERVICES_JSON" > android/app/google-services.json
298
+ echo "✅ google-services.json created"
299
+ else
300
+ echo "ℹ️ GOOGLE_SERVICES_JSON secret not provided, skipping"
301
+ fi
253
302
 
254
303
  - name: Set up Flutter
255
304
  uses: subosito/flutter-action@v2
256
305
  with:
306
+ channel: stable
257
307
  flutter-version: ${{ env.FLUTTER_VERSION }}
258
308
  cache: true
259
309
 
@@ -534,13 +584,19 @@ jobs:
534
584
  - name: Checkout repository
535
585
  uses: actions/checkout@v7
536
586
  with:
537
- ref: main
587
+ ref: "{{MAIN_BRANCH}}"
538
588
  fetch-depth: 0
539
589
 
540
590
  - name: Setup Google Play Service Account
591
+ env:
592
+ SERVICE_ACCOUNT_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}
541
593
  run: |
594
+ if [ -z "$SERVICE_ACCOUNT_BASE64" ]; then
595
+ echo "::error::GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 Secret이 비어 있습니다"
596
+ exit 1
597
+ fi
542
598
  mkdir -p ~/.config/gcloud
543
- echo "${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}" | base64 --decode > ~/.config/gcloud/service-account.json
599
+ printf '%s' "$SERVICE_ACCOUNT_BASE64" | base64 --decode > ~/.config/gcloud/service-account.json
544
600
  echo "✅ Service Account JSON 설정 완료"
545
601
 
546
602
  - name: Download AAB artifact
@@ -6,10 +6,7 @@
6
6
  #
7
7
  # 필수 GitHub Secrets
8
8
  # ENV_FILE 또는 ENV: .env 파일 내용 (둘 중 하나, ENV_FILE 우선)
9
- # RELEASE_KEYSTORE_BASE64: 서명용 keystore (base64)
10
- # RELEASE_KEYSTORE_PASSWORD: keystore 비밀번호
11
- # RELEASE_KEY_ALIAS: key alias
12
- # RELEASE_KEY_PASSWORD: key 비밀번호
9
+ # DEBUG_KEYSTORE (선택): 서명용 keystore (base64, 비밀번호 android · alias androiddebugkey) — 없으면 빌드마다 새 debug 키로 서명
13
10
  # GOOGLE_SERVICES_JSON (선택): Firebase google-services.json
14
11
  # SERVER_HOST: 서버 호스트(SMB 접속 주소)
15
12
  # SERVER_USER: SMB 사용자명
@@ -27,7 +24,9 @@ on:
27
24
  env:
28
25
  # 프로젝트 이름 (APK 파일명, 아티팩트명, 히스토리 파일명에 사용)
29
26
  APP_ARTIFACT_NAME: "__APP_ARTIFACT_NAME__" # @wizard ask:@repo
30
- FLUTTER_VERSION: "3.35.5"
27
+ # Flutter SDK 버전 — 비워 두면 stable 채널 최신을 쓴다(최신 flutter create 프로젝트의 SDK 제약과 맞음).
28
+ # 특정 버전에 고정하려면 "3.38.9"처럼 적는다. pubspec.yaml의 environment.sdk 범위를 만족해야 한다.
29
+ FLUTTER_VERSION: ""
31
30
  JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:17
32
31
  # Flutter 루트 경로(레포 루트 기준). 단일레포면 ".", 모노레포면 "app" 등 — 설치 시 자동 설정
33
32
  FLUTTER_PROJECT_DIR: "." # @wizard auto:flutter-root
@@ -53,19 +52,50 @@ jobs:
53
52
  with:
54
53
  fetch-depth: 0
55
54
 
55
+ # 업로드 Secret이 없으면 빌드를 다 마친 뒤 SMB 업로드에서야 실패하므로, 빠진 이름을 먼저 알려 주고 멈춘다
56
+ # 배포 job과 같은 조건 — 배포하지 않는 브랜치의 수동 실행은 빌드만 한다
57
+ # DEBUG_KEYSTORE·GOOGLE_SERVICES_JSON은 선택이라 점검하지 않는다
58
+ - name: 배포 사전 점검 (Secret)
59
+ if: github.ref == 'refs/heads/{{MAIN_BRANCH}}'
60
+ env:
61
+ SERVER_HOST: ${{ secrets.SERVER_HOST }}
62
+ SERVER_USER: ${{ secrets.SERVER_USER }}
63
+ SERVER_PASSWORD: ${{ secrets.SERVER_PASSWORD }}
64
+ run: |
65
+ MISSING=""
66
+ for NAME in SERVER_HOST SERVER_USER SERVER_PASSWORD; do
67
+ [ -n "${!NAME}" ] || MISSING="$MISSING $NAME"
68
+ done
69
+ if [ -n "$MISSING" ]; then
70
+ echo "::error title=필수 Secret 누락::등록되지 않은 GitHub Secret:$MISSING — Settings → Secrets and variables → Actions에서 등록하세요"
71
+ exit 1
72
+ fi
73
+ echo "✅ 사전 점검 통과: 필수 Secret이 모두 있습니다"
74
+
56
75
  - name: List repository contents
57
76
  run: |
58
77
  echo "Repository checked out"
59
78
  ls -la
60
79
 
61
- # Debug Keystore 설정 (디버깅 용도 유지)
80
+ # Debug Keystore 설정 — 이 워크플로우는 DEBUG_KEYSTORE(비밀번호 android, alias androiddebugkey)로 서명한다
62
81
  - name: Setup Debug Keystore
82
+ id: signing
83
+ env:
84
+ DEBUG_KEYSTORE: ${{ secrets.DEBUG_KEYSTORE }}
63
85
  run: |
86
+ # 내부 배포용이라 Secret이 없어도 빌드는 진행한다 — 러너가 새로 만든 debug 키로 서명되므로
87
+ # 빌드마다 서명이 달라져 기존 설치본 위에 업데이트할 수 없다는 점을 경고로 남긴다.
88
+ if [ -z "$DEBUG_KEYSTORE" ]; then
89
+ echo "::warning::DEBUG_KEYSTORE Secret이 비어 있어 러너 기본 debug 키로 서명합니다 (빌드마다 서명이 달라집니다)"
90
+ echo "mode=default" >> "$GITHUB_OUTPUT"
91
+ exit 0
92
+ fi
93
+ # 값이 있는데 깨진 keystore는 조용히 넘기지 않고 여기서 실패한다
64
94
  mkdir -p ~/.android
65
- echo "${{ secrets.DEBUG_KEYSTORE }}" | base64 -d > ~/.android/debug.keystore || echo "Base64 decoding failed"
66
- echo "Debug Keystore created (for debugging)"
67
- ls -la ~/.android/
68
- keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android || echo "Keytool failed"
95
+ printf '%s' "$DEBUG_KEYSTORE" | base64 -d > ~/.android/debug.keystore
96
+ keytool -list -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
97
+ echo "mode=keystore" >> "$GITHUB_OUTPUT"
98
+ echo "Debug Keystore created (DEBUG_KEYSTORE로 서명)"
69
99
 
70
100
  # 환경변수 준비 — ENV_MODE=dotenv면 .env 파일, dart-define이면 프로젝트 밖 임시 파일(--dart-define-from-file)
71
101
  - name: Prepare env file
@@ -83,9 +113,11 @@ jobs:
83
113
  fi
84
114
 
85
115
  - name: Setup Keystore and key.properties
116
+ if: steps.signing.outputs.mode == 'keystore'
86
117
  run: |
87
118
  mkdir -p android/app/keystore
88
- echo "${{ secrets.DEBUG_KEYSTORE }}" | base64 -d > android/app/keystore/key.jks || echo "Base64 decoding failed"
119
+ # 앞 단계에서 검증한 keystore를 그대로 쓴다
120
+ cp ~/.android/debug.keystore android/app/keystore/key.jks
89
121
  echo "Keystore created from DEBUG_KEYSTORE"
90
122
  ls -la android/app/keystore
91
123
  echo "storeFile=app/keystore/key.jks" > android/key.properties
@@ -95,20 +127,25 @@ jobs:
95
127
  echo "key.properties created"
96
128
  ls -la android/
97
129
 
98
- - name: Create Google-services.json
130
+ # google-services.json 생성 (선택) — 비어 있으면 빈 파일을 만들지 않는다
131
+ - name: Create google-services.json
132
+ env:
133
+ GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }}
99
134
  run: |
100
- cat << 'EOF' > android/app/google-services.json
101
- ${{ secrets.GOOGLE_SERVICES_JSON }}
102
- EOF
103
- echo "✅ Google-services.json 생성 완료"
104
- ls -la android/app/
135
+ if [ -n "$GOOGLE_SERVICES_JSON" ]; then
136
+ mkdir -p android/app
137
+ printf '%s' "$GOOGLE_SERVICES_JSON" > android/app/google-services.json
138
+ echo "✅ google-services.json created"
139
+ else
140
+ echo "ℹ️ GOOGLE_SERVICES_JSON secret not provided, skipping"
141
+ fi
105
142
 
106
143
  - name: Set up Flutter
107
144
  uses: subosito/flutter-action@v2
108
145
  with:
146
+ channel: stable
109
147
  flutter-version: ${{ env.FLUTTER_VERSION }}
110
148
  cache: true
111
- cache-key: flutter-${{ runner.os }}-${{ env.FLUTTER_VERSION }}
112
149
 
113
150
  - name: Verify Flutter version
114
151
  run: |
@@ -144,8 +181,13 @@ jobs:
144
181
  - name: Setup Gradle
145
182
  working-directory: ${{ env.FLUTTER_PROJECT_DIR }}/android
146
183
  run: |
147
- chmod +x gradlew
148
- echo "Gradle wrapper permissions set"
184
+ # flutter create 기본 .gitignore는 gradlew를 커밋하지 않는다 — 없으면 flutter build가 만든다
185
+ if [ -f gradlew ]; then
186
+ chmod +x gradlew
187
+ echo "Gradle wrapper permissions set"
188
+ else
189
+ echo "ℹ️ gradlew가 저장소에 없습니다 — flutter build가 생성합니다"
190
+ fi
149
191
 
150
192
  - name: Set up Java
151
193
  uses: actions/setup-java@v5
@@ -20,20 +20,20 @@
20
20
  # 3. "Run workflow" 버튼 클릭
21
21
  # 4. 빌드 완료 후 아티팩트에서 APK 다운로드
22
22
  #
23
- # 필수 GitHub Secrets
23
+ # GitHub Secrets (모두 선택 — 하나도 없어도 테스트 APK는 빌드됩니다)
24
24
  #
25
- # Release Keystore (Play Store 배포와 동일):
26
- # RELEASE_KEYSTORE_BASE64: 릴리즈 키스토어 파일 (base64)
27
- # RELEASE_KEYSTORE_PASSWORD: 키스토어 비밀번호
28
- # RELEASE_KEY_ALIAS: 키 별칭
29
- # RELEASE_KEY_PASSWORD: 키 비밀번호
25
+ # Release Keystore (Play Store 배포와 동일, 없으면 경고 후 debug 키로 서명):
26
+ # RELEASE_KEYSTORE_BASE64 (선택): 릴리즈 키스토어 파일 (base64)
27
+ # RELEASE_KEYSTORE_PASSWORD (선택): 키스토어 비밀번호
28
+ # RELEASE_KEY_ALIAS (선택): 키 별칭
29
+ # RELEASE_KEY_PASSWORD (선택): 키 비밀번호
30
30
  #
31
31
  # 환경 설정 (선택):
32
32
  # ENV_FILE (선택): .env 파일 내용
33
33
  # GOOGLE_SERVICES_JSON (선택): google-services.json 내용 (Firebase 사용 시)
34
34
  #
35
35
  # Firebase App Distribution (선택):
36
- # FIREBASE_SERVICE_ACCOUNT_JSON_BASE64: Firebase 서비스 계정 JSON (base64)
36
+ # FIREBASE_SERVICE_ACCOUNT_JSON_BASE64 (선택): Firebase 서비스 계정 JSON (base64)
37
37
  #
38
38
  # ※ 위 Secrets는 저장소 Settings → Secrets and variables → Actions에 등록하세요.
39
39
  # Firebase 업로드를 쓰려면 아래 env의 FIREBASE_APP_ID·FIREBASE_TESTER_GROUP도 수정합니다.
@@ -49,9 +49,17 @@ on:
49
49
  repository_dispatch:
50
50
  types: [build-android-app]
51
51
 
52
+ # 레포 기본 권한이 read여도 빌드 진행 댓글을 달 수 있게 필요한 권한을 직접 선언한다
53
+ permissions:
54
+ contents: read
55
+ issues: write
56
+ pull-requests: write
57
+
52
58
  # 프로젝트별 설정 (아래 값들을 수정하세요)
53
59
  env:
54
- FLUTTER_VERSION: "3.35.5"
60
+ # Flutter SDK 버전 — 비워 두면 stable 채널 최신을 쓴다(최신 flutter create 프로젝트의 SDK 제약과 맞음).
61
+ # 특정 버전에 고정하려면 "3.38.9"처럼 적는다. pubspec.yaml의 environment.sdk 범위를 만족해야 한다.
62
+ FLUTTER_VERSION: ""
55
63
  JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:17
56
64
  # Flutter 루트 경로(레포 루트 기준). 단일레포면 ".", 모노레포면 "app" 등 — 설치 시 자동 설정
57
65
  FLUTTER_PROJECT_DIR: "." # @wizard auto:flutter-root
@@ -321,15 +329,39 @@ jobs:
321
329
  fi
322
330
 
323
331
  - name: Setup Release Keystore
332
+ id: signing
324
333
  env:
325
- KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
334
+ RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}
335
+ RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
336
+ RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
337
+ RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
326
338
  run: |
339
+ # 테스트 빌드는 설정 없이도 돌아야 한다 — Secret이 비면 keystore·key.properties를 만들지 않고
340
+ # 프로젝트 기본 서명(flutter create 기본값은 release도 debug 키)으로 빌드한다. 어떤 키인지는 로그에 남긴다.
341
+ MISSING=""
342
+ for name in RELEASE_KEYSTORE_BASE64 RELEASE_KEYSTORE_PASSWORD RELEASE_KEY_ALIAS RELEASE_KEY_PASSWORD; do
343
+ [ -n "${!name}" ] || MISSING="$MISSING $name"
344
+ done
345
+ if [ -n "$MISSING" ]; then
346
+ echo "::warning::서명 Secret이 비어 있어 debug 키로 서명합니다:$MISSING (스토어 업로드용 키가 아닙니다)"
347
+ echo "mode=debug" >> "$GITHUB_OUTPUT"
348
+ exit 0
349
+ fi
350
+ # 값이 있는데 깨진 keystore는 조용히 넘기지 않고 여기서 실패한다
327
351
  mkdir -p android/keystore
328
- echo "$KEYSTORE_BASE64" | base64 -d > android/keystore/key.jks
329
- echo "✅ Release Keystore 생성 완료"
352
+ printf '%s' "$RELEASE_KEYSTORE_BASE64" | base64 -d > android/keystore/key.jks
353
+ if [ ! -s android/keystore/key.jks ]; then
354
+ echo "::error::RELEASE_KEYSTORE_BASE64를 디코딩한 keystore가 비어 있습니다"
355
+ exit 1
356
+ fi
357
+ # 비밀번호·alias가 keystore와 맞지 않으면 여기서 멈춘다 (gradle 서명 단계의 긴 오류 대신)
358
+ keytool -list -keystore android/keystore/key.jks -storepass "$RELEASE_KEYSTORE_PASSWORD" -alias "$RELEASE_KEY_ALIAS" > /dev/null
359
+ echo "mode=release" >> "$GITHUB_OUTPUT"
360
+ echo "✅ Release Keystore 생성 완료 (release 키로 서명)"
330
361
  ls -la android/keystore/
331
362
 
332
363
  - name: Create key.properties
364
+ if: steps.signing.outputs.mode == 'release'
333
365
  env:
334
366
  STORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
335
367
  KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
@@ -360,6 +392,7 @@ jobs:
360
392
  - name: Set up Flutter
361
393
  uses: subosito/flutter-action@v2
362
394
  with:
395
+ channel: stable
363
396
  flutter-version: ${{ env.FLUTTER_VERSION }}
364
397
  cache: true
365
398
 
@@ -396,8 +429,13 @@ jobs:
396
429
  - name: Setup Gradle
397
430
  working-directory: ${{ env.FLUTTER_PROJECT_DIR }}/android
398
431
  run: |
399
- chmod +x gradlew
400
- echo "✅ Gradle wrapper permissions set"
432
+ # flutter create 기본 .gitignore는 gradlew를 커밋하지 않는다 — 없으면 flutter build가 만든다
433
+ if [ -f gradlew ]; then
434
+ chmod +x gradlew
435
+ echo "✅ Gradle wrapper permissions set"
436
+ else
437
+ echo "ℹ️ gradlew가 저장소에 없습니다 — flutter build가 생성합니다"
438
+ fi
401
439
 
402
440
  - name: Set up Java
403
441
  uses: actions/setup-java@v5
@@ -2,12 +2,13 @@
2
2
  # Flutter CI (코드 분석 및 빌드 검증) 워크플로우
3
3
  #
4
4
  # 이 워크플로우는 PR 생성 또는 {{MAIN_BRANCH}} 브랜치 푸시 시
5
- # 코드 정적 분석(flutter analyze)과 Android/iOS 앱 빌드를 검증합니다.
5
+ # 코드 정적 분석(flutter analyze), 테스트(flutter test)와 Android/iOS 앱 빌드를 검증합니다.
6
6
  #
7
7
  # 주요 특징:
8
8
  # - PR 생성/업데이트 시 자동 실행
9
9
  # - {{MAIN_BRANCH}} 브랜치 푸시 시 자동 실행
10
10
  # - flutter analyze로 코드 품질 검사
11
+ # - test/ 폴더에 테스트(*_test.dart)가 있으면 flutter test 실행 (없으면 건너뜀)
11
12
  # - Android/iOS 빌드 개별 활성화/비활성화 가능
12
13
  # - Analyze Only 모드: 빌드 없이 코드 분석만 실행 가능
13
14
  # - PR인 경우 진행 상황을 댓글로 실시간 업데이트
@@ -22,10 +23,10 @@
22
23
  #
23
24
  # 프로젝트별 설정
24
25
  #
25
- # ANALYZE_ONLY : true면 analyze만 실행, 빌드 스킵 (기본: false)
26
+ # ANALYZE_ONLY : true면 analyze·test만 실행, 빌드 스킵 (기본: false)
26
27
  # ENABLE_ANDROID : Android 빌드 활성화 여부 (ANALYZE_ONLY=false일 때)
27
28
  # ENABLE_IOS : iOS 빌드 활성화 여부 (ANALYZE_ONLY=false일 때)
28
- # FLUTTER_VERSION : Flutter SDK 버전
29
+ # FLUTTER_VERSION : Flutter SDK 버전 (빈 값 = stable 최신)
29
30
  # JAVA_VERSION : Java 버전 (Android 빌드용)
30
31
  # XCODE_VERSION : Xcode 버전 (iOS 빌드용)
31
32
  # ENV_FILE_PATH : dotenv 모드에서 .env를 만들 경로 (Flutter 루트 기준, 기본값: .env)
@@ -77,14 +78,16 @@ env:
77
78
  ENV_MODE: "dart-define" # @wizard auto:flutter-env-mode
78
79
 
79
80
  # CI 모드 설정
80
- ANALYZE_ONLY: "false" # true: analyze만 실행 (빌드 스킵), false: analyze + 빌드
81
+ ANALYZE_ONLY: "false" # true: analyze·test만 실행 (빌드 스킵), false: analyze·test + 빌드
81
82
 
82
83
  # 빌드 대상 플랫폼 설정 (ANALYZE_ONLY=false일 때만 적용)
83
84
  ENABLE_ANDROID: "true" # Android 빌드 활성화 (true/false)
84
85
  ENABLE_IOS: "true" # iOS 빌드 활성화 (true/false)
85
86
 
86
87
  # 프로젝트별 설정
87
- FLUTTER_VERSION: "3.35.5"
88
+ # Flutter SDK 버전 — 비워 두면 stable 채널 최신을 쓴다(최신 flutter create 프로젝트의 SDK 제약과 맞음).
89
+ # 특정 버전에 고정하려면 "3.38.9"처럼 적는다. pubspec.yaml의 environment.sdk 범위를 만족해야 한다.
90
+ FLUTTER_VERSION: ""
88
91
  JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:17
89
92
  XCODE_VERSION: "26.0"
90
93
  ENV_FILE_PATH: ".env" # dotenv 모드에서 .env를 만들 경로 (Flutter 루트 기준, 커스터마이징 가능)
@@ -112,6 +115,10 @@ jobs:
112
115
  if: ${{ github.event_name != 'workflow_dispatch' }}
113
116
  uses: dorny/paths-filter@v4
114
117
  with:
118
+ # push는 이번 push로 들어온 커밋만 비교한다(직전 커밋 github.event.before 기준).
119
+ # 비워 두면 기본 브랜치와의 누적 diff로 판정해 skip이 사실상 동작하지 않는다.
120
+ # 새 브랜치 첫 push(before가 0으로만 채워짐)나 PR은 빈 값 → 기본 동작(기본 브랜치·PR base 비교).
121
+ base: ${{ github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000' && github.event.before || '' }}
115
122
  filters: |
116
123
  project:
117
124
  - '${{ env.PROJECT_PATH == '.' && '**' || format('{0}/**', env.PROJECT_PATH) }}'
@@ -212,6 +219,7 @@ jobs:
212
219
  '| 검사 항목 | 상태 | 소요 시간 |',
213
220
  '|----------|------|----------|',
214
221
  '| 🔍 Analyze | ⏳ 진행 중... | - |',
222
+ '| 🧪 Test | ⏳ 진행 중... | - |',
215
223
  `| 🤖 Android 빌드 | ${androidStatus} | - |`,
216
224
  `| 🍎 iOS 빌드 | ${iosStatus} | - |`,
217
225
  '',
@@ -257,6 +265,7 @@ jobs:
257
265
 
258
266
  outputs:
259
267
  status: ${{ steps.result.outputs.status }}
268
+ test_status: ${{ steps.result.outputs.test_status }}
260
269
  duration: ${{ steps.result.outputs.duration }}
261
270
 
262
271
  steps:
@@ -288,6 +297,7 @@ jobs:
288
297
  - name: Set up Flutter
289
298
  uses: subosito/flutter-action@v2
290
299
  with:
300
+ channel: stable
291
301
  flutter-version: ${{ env.FLUTTER_VERSION }}
292
302
  cache: true
293
303
 
@@ -304,6 +314,7 @@ jobs:
304
314
  restore-keys: ${{ runner.os }}-flutter-pub-
305
315
 
306
316
  - name: Install dependencies
317
+ id: deps
307
318
  run: |
308
319
  flutter pub get
309
320
  if grep -q "build_runner" pubspec.yaml; then
@@ -318,6 +329,22 @@ jobs:
318
329
  flutter analyze --no-fatal-infos
319
330
  echo "✅ 코드 분석 완료"
320
331
 
332
+ # analyze가 실패해도 테스트 결과는 따로 보여 준다. 실패하면 job이 실패해 CI Gate도 실패한다.
333
+ - name: Run Flutter Test
334
+ id: test
335
+ if: ${{ !cancelled() && steps.deps.outcome == 'success' }}
336
+ run: |
337
+ # test/에 *_test.dart가 없으면 flutter test 자체가 실패하므로 건너뛴다
338
+ if [ -d test ] && [ -n "$(find test -name '*_test.dart' -print -quit)" ]; then
339
+ echo "🧪 Flutter 테스트 시작..."
340
+ flutter test ${DART_DEFINE_FILE:+--dart-define-from-file="$DART_DEFINE_FILE"}
341
+ echo "ran=true" >> "$GITHUB_OUTPUT"
342
+ echo "✅ 테스트 통과"
343
+ else
344
+ echo "::notice title=Flutter 테스트 건너뜀::test/ 폴더에 *_test.dart 파일이 없어 flutter test를 실행하지 않았습니다"
345
+ echo "ran=false" >> "$GITHUB_OUTPUT"
346
+ fi
347
+
321
348
  - name: 분석 결과 기록
322
349
  id: result
323
350
  if: always()
@@ -332,10 +359,16 @@ jobs:
332
359
  const duration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
333
360
 
334
361
  const status = '${{ steps.analyze.outcome }}' === 'success' ? 'success' : 'failure';
362
+ // none: 테스트 파일 없음, skipped: 의존성 설치 실패로 실행 못 함
363
+ const testOutcome = '${{ steps.test.outcome }}';
364
+ const testStatus = testOutcome === 'success'
365
+ ? ('${{ steps.test.outputs.ran }}' === 'true' ? 'success' : 'none')
366
+ : (testOutcome === 'failure' ? 'failure' : 'skipped');
335
367
 
336
368
  core.setOutput('status', status);
369
+ core.setOutput('test_status', testStatus);
337
370
  core.setOutput('duration', duration);
338
- console.log(`📋 코드 분석 결과: ${status} (${duration})`);
371
+ console.log(`📋 코드 분석 결과: ${status}, 테스트: ${testStatus} (${duration})`);
339
372
 
340
373
  # Job 3: Android 빌드 (조건부, 병렬)
341
374
  build-android:
@@ -383,6 +416,7 @@ jobs:
383
416
  - name: Set up Flutter
384
417
  uses: subosito/flutter-action@v2
385
418
  with:
419
+ channel: stable
386
420
  flutter-version: ${{ env.FLUTTER_VERSION }}
387
421
  cache: true
388
422
 
@@ -430,8 +464,13 @@ jobs:
430
464
  - name: Setup Gradle
431
465
  working-directory: ${{ env.FLUTTER_PROJECT_DIR }}/android
432
466
  run: |
433
- chmod +x gradlew
434
- echo "✅ Gradle wrapper permissions set"
467
+ # flutter create 기본 .gitignore는 gradlew를 커밋하지 않는다 — 없으면 flutter build가 만든다
468
+ if [ -f gradlew ]; then
469
+ chmod +x gradlew
470
+ echo "✅ Gradle wrapper permissions set"
471
+ else
472
+ echo "ℹ️ gradlew가 저장소에 없습니다 — flutter build가 생성합니다"
473
+ fi
435
474
 
436
475
  # APK 빌드 (CI는 빌드 검증 목적이므로 서명 불필요한 debug 빌드 사용)
437
476
  - name: Build APK
@@ -527,6 +566,7 @@ jobs:
527
566
  - name: Set up Flutter
528
567
  uses: subosito/flutter-action@v2
529
568
  with:
569
+ channel: stable
530
570
  flutter-version: ${{ env.FLUTTER_VERSION }}
531
571
  cache: true
532
572
 
@@ -558,9 +598,14 @@ jobs:
558
598
 
559
599
  - name: Install CocoaPods
560
600
  run: |
561
- gem install cocoapods
562
- cd ios && pod install --repo-update || pod install
563
- echo "✅ CocoaPods installed"
601
+ # 플러그인이 없는 기본 프로젝트는 Podfile이 없다 — 플러그인이 생기면 flutter build가 Podfile을 만든다
602
+ if [ -f ios/Podfile ]; then
603
+ gem install cocoapods
604
+ (cd ios && (pod install --repo-update || pod install))
605
+ echo "✅ CocoaPods installed"
606
+ else
607
+ echo "ℹ️ ios/Podfile이 없어 pod install을 건너뜁니다 (CocoaPods 플러그인 없음)"
608
+ fi
564
609
 
565
610
  - name: Build iOS
566
611
  id: build
@@ -614,6 +659,7 @@ jobs:
614
659
  // 결과 수집
615
660
  const analyzeStatus = '${{ needs.analyze.outputs.status }}';
616
661
  const analyzeDuration = '${{ needs.analyze.outputs.duration }}' || '-';
662
+ const testStatus = '${{ needs.analyze.outputs.test_status }}';
617
663
  const androidStatus = '${{ needs.build-android.outputs.status }}';
618
664
  const androidDuration = '${{ needs.build-android.outputs.duration }}' || '-';
619
665
  const iosStatus = '${{ needs.build-ios.outputs.status }}';
@@ -638,6 +684,17 @@ jobs:
638
684
  overallSuccess = false;
639
685
  }
640
686
 
687
+ // Test 상태 (테스트 파일이 없으면 실패로 보지 않는다)
688
+ let testDisplay;
689
+ if (testStatus === 'success') {
690
+ testDisplay = '✅ 성공';
691
+ } else if (testStatus === 'none') {
692
+ testDisplay = '⏸️ 테스트 없음';
693
+ } else {
694
+ testDisplay = testStatus === 'failure' ? '❌ 실패' : '⏸️ 실행 안 됨';
695
+ overallSuccess = false;
696
+ }
697
+
641
698
  // Android 상태
642
699
  if (analyzeOnly) {
643
700
  androidDisplay = '⏸️ 스킵';
@@ -681,6 +738,7 @@ jobs:
681
738
  '| 검사 항목 | 상태 | 소요 시간 |',
682
739
  '|----------|------|----------|',
683
740
  `| 🔍 Analyze | ${analyzeDisplay} | ${analyzeDuration} |`,
741
+ `| 🧪 Test | ${testDisplay} | - |`,
684
742
  `| 🤖 Android 빌드 | ${androidDisplay} | ${analyzeOnly || !enableAndroid ? '-' : androidDuration} |`,
685
743
  `| 🍎 iOS 빌드 | ${iosDisplay} | ${analyzeOnly || !enableIos ? '-' : iosDuration} |`,
686
744
  '',
@@ -712,6 +770,12 @@ jobs:
712
770
  bodyParts.push('');
713
771
  }
714
772
 
773
+ if (testStatus === 'failure') {
774
+ bodyParts.push('**Test 실패 시:**');
775
+ bodyParts.push('- `flutter test` 로컬에서 실행하여 실패한 테스트 확인');
776
+ bodyParts.push('');
777
+ }
778
+
715
779
  if (!analyzeOnly && enableAndroid && androidStatus !== 'success') {
716
780
  bodyParts.push('**Android 빌드 실패 시:**');
717
781
  bodyParts.push('- Gradle 빌드 로그에서 에러 확인');