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,689 @@
1
+ # ==========================================================================================
2
+ # Flutter CI (코드 분석 및 빌드 검증) 워크플로우
3
+ # ==========================================================================================
4
+ #
5
+ # 이 워크플로우는 PR 생성 또는 {{MAIN_BRANCH}} 브랜치 푸시 시
6
+ # 코드 정적 분석(flutter analyze)과 Android/iOS 앱 빌드를 검증합니다.
7
+ #
8
+ # 주요 특징:
9
+ # - PR 생성/업데이트 시 자동 실행
10
+ # - {{MAIN_BRANCH}} 브랜치 푸시 시 자동 실행
11
+ # - flutter analyze로 코드 품질 검사
12
+ # - Android/iOS 빌드 개별 활성화/비활성화 가능
13
+ # - Analyze Only 모드: 빌드 없이 코드 분석만 실행 가능
14
+ # - PR인 경우 진행 상황을 댓글로 실시간 업데이트
15
+ # - 빌드 실패 시 상세 에러 정보 제공
16
+ # - Analyze와 빌드 병렬 실행으로 시간 단축
17
+ #
18
+ # 사용 방법:
19
+ # 1. 프로젝트에 맞게 상단 env 섹션의 값들을 수정하세요
20
+ # 2. ANALYZE_ONLY를 true로 설정하면 빌드 없이 코드 분석만 실행
21
+ # 3. ENABLE_ANDROID, ENABLE_IOS로 빌드 대상 플랫폼 선택
22
+ # 4. ENV_FILE_PATH로 환경변수 파일 경로 커스터마이징
23
+ #
24
+ # ==========================================================================================
25
+ # 🔧 프로젝트별 설정
26
+ # ==========================================================================================
27
+ #
28
+ # ANALYZE_ONLY : true면 analyze만 실행, 빌드 스킵 (기본: false)
29
+ # ENABLE_ANDROID : Android 빌드 활성화 여부 (ANALYZE_ONLY=false일 때)
30
+ # ENABLE_IOS : iOS 빌드 활성화 여부 (ANALYZE_ONLY=false일 때)
31
+ # FLUTTER_VERSION : Flutter SDK 버전
32
+ # JAVA_VERSION : Java 버전 (Android 빌드용)
33
+ # XCODE_VERSION : Xcode 버전 (iOS 빌드용)
34
+ # ENV_FILE_PATH : .env 파일 경로 (기본값: 루트의 .env)
35
+ #
36
+ # ==========================================================================================
37
+ # 🔑 필수 GitHub Secrets
38
+ # ==========================================================================================
39
+ # ENV_FILE (선택): .env 파일 내용
40
+ #
41
+ # ※ CI는 빌드 검증 목적이므로 서명/배포 관련 Secrets 불필요
42
+ # ==========================================================================================
43
+
44
+ name: PROJECT-Flutter-CI
45
+
46
+ on:
47
+ pull_request:
48
+ branches: ["{{DEVELOP_BRANCH}}"]
49
+ types: [opened, synchronize, reopened]
50
+ push:
51
+ branches: ["{{DEVELOP_BRANCH}}"]
52
+ workflow_dispatch:
53
+ inputs:
54
+ analyze_only:
55
+ description: 'Analyze Only 모드 (빌드 없이 코드 분석만)'
56
+ type: boolean
57
+ default: false
58
+ enable_android:
59
+ description: 'Android 빌드 활성화 (Analyze Only=false일 때)'
60
+ type: boolean
61
+ default: true
62
+ enable_ios:
63
+ description: 'iOS 빌드 활성화 (Analyze Only=false일 때)'
64
+ type: boolean
65
+ default: true
66
+
67
+ permissions:
68
+ contents: read
69
+ pull-requests: write
70
+
71
+ # ==========================================================================================
72
+ # 🔧 프로젝트별 설정 (아래 값들을 수정하세요)
73
+ # ==========================================================================================
74
+ env:
75
+ # 🎯 CI 모드 설정
76
+ ANALYZE_ONLY: "false" # true: analyze만 실행 (빌드 스킵), false: analyze + 빌드
77
+
78
+ # 🎯 빌드 대상 플랫폼 설정 (ANALYZE_ONLY=false일 때만 적용)
79
+ ENABLE_ANDROID: "true" # Android 빌드 활성화 (true/false)
80
+ ENABLE_IOS: "true" # iOS 빌드 활성화 (true/false)
81
+
82
+ # 🔧 프로젝트별 설정
83
+ FLUTTER_VERSION: "3.35.5"
84
+ JAVA_VERSION: "__JAVA_VERSION__" # @wizard ask:17
85
+ XCODE_VERSION: "26.0"
86
+ ENV_FILE_PATH: ".env" # 환경변수 파일 경로 (커스터마이징 가능)
87
+
88
+ concurrency:
89
+ group: flutter-ci-${{ github.ref }}
90
+ cancel-in-progress: true
91
+
92
+ jobs:
93
+ # ===================================================================
94
+ # Job 1: 준비 단계
95
+ # ===================================================================
96
+ prepare:
97
+ name: CI 준비
98
+ runs-on: ubuntu-latest
99
+
100
+ outputs:
101
+ comment_id: ${{ steps.comment.outputs.comment_id }}
102
+ start_time: ${{ steps.comment.outputs.start_time }}
103
+ branch_name: ${{ steps.info.outputs.branch_name }}
104
+ commit_hash: ${{ steps.info.outputs.commit_hash }}
105
+ analyze_only: ${{ steps.config.outputs.analyze_only }}
106
+ enable_android: ${{ steps.config.outputs.enable_android }}
107
+ enable_ios: ${{ steps.config.outputs.enable_ios }}
108
+
109
+ steps:
110
+ # 설정 값 outputs으로 전달
111
+ # workflow_dispatch 입력값이 있으면 우선 사용, 없으면 env 값 사용
112
+ - name: 설정 값 전달
113
+ id: config
114
+ run: |
115
+ # workflow_dispatch 입력값 확인
116
+ if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
117
+ ANALYZE_ONLY="${{ github.event.inputs.analyze_only }}"
118
+ ENABLE_ANDROID="${{ github.event.inputs.enable_android }}"
119
+ ENABLE_IOS="${{ github.event.inputs.enable_ios }}"
120
+ else
121
+ ANALYZE_ONLY="${{ env.ANALYZE_ONLY }}"
122
+ ENABLE_ANDROID="${{ env.ENABLE_ANDROID }}"
123
+ ENABLE_IOS="${{ env.ENABLE_IOS }}"
124
+ fi
125
+
126
+ echo "analyze_only=$ANALYZE_ONLY" >> $GITHUB_OUTPUT
127
+ echo "enable_android=$ENABLE_ANDROID" >> $GITHUB_OUTPUT
128
+ echo "enable_ios=$ENABLE_IOS" >> $GITHUB_OUTPUT
129
+ echo "📋 CI 설정:"
130
+ echo " Analyze Only: $ANALYZE_ONLY"
131
+ echo " Android: $ENABLE_ANDROID"
132
+ echo " iOS: $ENABLE_IOS"
133
+ echo " 트리거: ${{ github.event_name }}"
134
+
135
+ # 빌드 정보 수집
136
+ - name: 빌드 정보 수집
137
+ id: info
138
+ run: |
139
+ if [ "${{ github.event_name }}" == "pull_request" ]; then
140
+ BRANCH_NAME="${{ github.head_ref }}"
141
+ else
142
+ BRANCH_NAME="${{ github.ref_name }}"
143
+ fi
144
+ COMMIT_HASH=$(echo "${{ github.sha }}" | cut -c1-7)
145
+
146
+ echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
147
+ echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT
148
+
149
+ echo "📋 빌드 정보:"
150
+ echo " 브랜치: $BRANCH_NAME"
151
+ echo " 커밋: $COMMIT_HASH"
152
+
153
+ # PR인 경우 진행 상황 댓글 생성
154
+ - name: 진행 상황 댓글 생성
155
+ id: comment
156
+ if: github.event_name == 'pull_request'
157
+ uses: actions/github-script@v7
158
+ with:
159
+ github-token: ${{ secrets.GITHUB_TOKEN }}
160
+ script: |
161
+ const prNumber = context.payload.pull_request.number;
162
+ const branchName = '${{ steps.info.outputs.branch_name }}';
163
+ const commitHash = '${{ steps.info.outputs.commit_hash }}';
164
+ const analyzeOnly = '${{ steps.config.outputs.analyze_only }}' === 'true';
165
+ const enableAndroid = '${{ steps.config.outputs.enable_android }}' === 'true';
166
+ const enableIos = '${{ steps.config.outputs.enable_ios }}' === 'true';
167
+ const runId = '${{ github.run_id }}';
168
+ const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
169
+ const startTime = Date.now();
170
+
171
+ // 헤더 결정
172
+ const header = analyzeOnly
173
+ ? '## 🔍 Flutter CI (Analyze Only) 진행 중...'
174
+ : '## 🔨 Flutter CI 진행 중...';
175
+
176
+ // 플랫폼 상태 결정
177
+ let androidStatus, iosStatus;
178
+ if (analyzeOnly) {
179
+ androidStatus = '⏸️ 스킵';
180
+ iosStatus = '⏸️ 스킵';
181
+ } else {
182
+ androidStatus = enableAndroid ? '⏳ 진행 중...' : '⏸️ 비활성화';
183
+ iosStatus = enableIos ? '⏳ 진행 중...' : '⏸️ 비활성화';
184
+ }
185
+
186
+ const bodyParts = [
187
+ header,
188
+ '',
189
+ '| 검사 항목 | 상태 | 소요 시간 |',
190
+ '|----------|------|----------|',
191
+ '| 🔍 Analyze | ⏳ 진행 중... | - |',
192
+ `| 🤖 Android 빌드 | ${androidStatus} | - |`,
193
+ `| 🍎 iOS 빌드 | ${iosStatus} | - |`,
194
+ '',
195
+ '| 항목 | 값 |',
196
+ '|------|-----|',
197
+ `| **브랜치** | \`${branchName}\` |`,
198
+ `| **커밋** | \`${commitHash}\` |`
199
+ ];
200
+
201
+ if (analyzeOnly) {
202
+ bodyParts.push('');
203
+ bodyParts.push('ℹ️ **Analyze Only 모드**: 빌드 없이 코드 분석만 실행됩니다.');
204
+ }
205
+
206
+ bodyParts.push('');
207
+ bodyParts.push(`**[📋 실시간 로그 보기](${runUrl})**`);
208
+ bodyParts.push('');
209
+ bodyParts.push('---');
210
+ bodyParts.push('*🤖 이 댓글은 자동으로 업데이트됩니다.*');
211
+
212
+ const body = bodyParts.join('\n');
213
+
214
+ const { data: comment } = await github.rest.issues.createComment({
215
+ owner: context.repo.owner,
216
+ repo: context.repo.repo,
217
+ issue_number: prNumber,
218
+ body: body
219
+ });
220
+
221
+ console.log(`✅ 진행 상황 댓글 생성 완료: #${comment.id}`);
222
+ core.setOutput('comment_id', comment.id);
223
+ core.setOutput('start_time', startTime);
224
+
225
+ # ===================================================================
226
+ # Job 2: 코드 분석 (병렬)
227
+ # ===================================================================
228
+ analyze:
229
+ name: 코드 분석
230
+ runs-on: ubuntu-latest
231
+ needs: prepare
232
+
233
+ outputs:
234
+ status: ${{ steps.result.outputs.status }}
235
+ duration: ${{ steps.result.outputs.duration }}
236
+
237
+ steps:
238
+ - name: 분석 시작 시간 기록
239
+ id: analyze_start
240
+ uses: actions/github-script@v7
241
+ with:
242
+ script: |
243
+ core.setOutput('time', Date.now().toString());
244
+
245
+ - name: Checkout repository
246
+ uses: actions/checkout@v5
247
+
248
+ # .env 파일 생성
249
+ - name: Create .env file
250
+ run: |
251
+ cat << 'EOF' > ${{ env.ENV_FILE_PATH }}
252
+ ${{ secrets.ENV_FILE || secrets.ENV }}
253
+ EOF
254
+ echo "✅ ${{ env.ENV_FILE_PATH }} file created"
255
+
256
+ # Flutter 설정
257
+ - name: Set up Flutter
258
+ uses: subosito/flutter-action@v2
259
+ with:
260
+ flutter-version: ${{ env.FLUTTER_VERSION }}
261
+ cache: true
262
+
263
+ - name: Verify Flutter version
264
+ run: |
265
+ echo "✅ Flutter setup completed"
266
+ flutter --version
267
+
268
+ # Flutter 캐시
269
+ - name: Cache Flutter dependencies
270
+ uses: actions/cache@v4
271
+ with:
272
+ path: ~/.pub-cache
273
+ key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }}
274
+ restore-keys: ${{ runner.os }}-flutter-pub-
275
+
276
+ # 프로젝트 의존성 설치
277
+ - name: Install dependencies
278
+ run: |
279
+ flutter pub get
280
+ echo "✅ Dependencies installed"
281
+
282
+ # Flutter Analyze 실행
283
+ - name: Run Flutter Analyze
284
+ id: analyze
285
+ run: |
286
+ echo "🔍 Flutter 코드 분석 시작..."
287
+ flutter analyze --no-fatal-infos
288
+ echo "✅ 코드 분석 완료"
289
+
290
+ # 분석 결과 기록
291
+ - name: 분석 결과 기록
292
+ id: result
293
+ if: always()
294
+ uses: actions/github-script@v7
295
+ with:
296
+ script: |
297
+ const analyzeStart = parseInt('${{ steps.analyze_start.outputs.time }}');
298
+ const now = Date.now();
299
+ const elapsed = now - analyzeStart;
300
+ const minutes = Math.floor(elapsed / 60000);
301
+ const seconds = Math.floor((elapsed % 60000) / 1000);
302
+ const duration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
303
+
304
+ const status = '${{ steps.analyze.outcome }}' === 'success' ? 'success' : 'failure';
305
+
306
+ core.setOutput('status', status);
307
+ core.setOutput('duration', duration);
308
+ console.log(`📋 코드 분석 결과: ${status} (${duration})`);
309
+
310
+ # ===================================================================
311
+ # Job 3: Android 빌드 (조건부, 병렬)
312
+ # ===================================================================
313
+ build-android:
314
+ name: Android 빌드
315
+ if: |
316
+ needs.prepare.outputs.analyze_only != 'true' &&
317
+ needs.prepare.outputs.enable_android == 'true'
318
+ runs-on: ubuntu-latest
319
+ needs: prepare
320
+
321
+ outputs:
322
+ status: ${{ steps.result.outputs.status }}
323
+ duration: ${{ steps.result.outputs.duration }}
324
+
325
+ steps:
326
+ - name: 빌드 시작 시간 기록
327
+ id: build_start
328
+ uses: actions/github-script@v7
329
+ with:
330
+ script: |
331
+ core.setOutput('time', Date.now().toString());
332
+
333
+ - name: Checkout repository
334
+ uses: actions/checkout@v5
335
+
336
+ # .env 파일 생성
337
+ - name: Create .env file
338
+ run: |
339
+ cat << 'EOF' > ${{ env.ENV_FILE_PATH }}
340
+ ${{ secrets.ENV_FILE || secrets.ENV }}
341
+ EOF
342
+ echo "✅ ${{ env.ENV_FILE_PATH }} file created"
343
+
344
+ # Flutter 설정
345
+ - name: Set up Flutter
346
+ uses: subosito/flutter-action@v2
347
+ with:
348
+ flutter-version: ${{ env.FLUTTER_VERSION }}
349
+ cache: true
350
+
351
+ - name: Verify Flutter version
352
+ run: |
353
+ echo "✅ Flutter setup completed"
354
+ flutter --version
355
+
356
+ # Flutter 캐시
357
+ - name: Cache Flutter dependencies
358
+ uses: actions/cache@v4
359
+ with:
360
+ path: ~/.pub-cache
361
+ key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }}
362
+ restore-keys: ${{ runner.os }}-flutter-pub-
363
+
364
+ # Gradle 캐시
365
+ - name: Cache Gradle dependencies
366
+ uses: actions/cache@v4
367
+ with:
368
+ path: |
369
+ ~/.gradle/caches
370
+ ~/.gradle/wrapper
371
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/build.gradle', '**/gradle-wrapper.properties') }}
372
+ restore-keys: |
373
+ ${{ runner.os }}-gradle-
374
+
375
+ # 프로젝트 의존성 설치
376
+ - name: Install dependencies
377
+ run: |
378
+ flutter pub get
379
+ echo "✅ Dependencies installed"
380
+
381
+ # Java 설정
382
+ - name: Set up Java
383
+ uses: actions/setup-java@v4
384
+ with:
385
+ distribution: "temurin"
386
+ java-version: ${{ env.JAVA_VERSION }}
387
+
388
+ - name: Verify Java version
389
+ run: |
390
+ echo "✅ Java setup completed"
391
+ java -version
392
+
393
+ # Gradle 셋업
394
+ - name: Setup Gradle
395
+ working-directory: android
396
+ run: |
397
+ chmod +x gradlew
398
+ echo "✅ Gradle wrapper permissions set"
399
+
400
+ # APK 빌드
401
+ - name: Build APK
402
+ id: build
403
+ run: |
404
+ echo "📦 Flutter APK 빌드 시작..."
405
+ flutter build apk --release
406
+ echo "✅ APK 빌드 완료"
407
+ ls -la ./build/app/outputs/flutter-apk/ || true
408
+
409
+ # 빌드 결과 기록
410
+ - name: 빌드 결과 기록
411
+ id: result
412
+ if: always()
413
+ uses: actions/github-script@v7
414
+ with:
415
+ script: |
416
+ const buildStart = parseInt('${{ steps.build_start.outputs.time }}');
417
+ const now = Date.now();
418
+ const elapsed = now - buildStart;
419
+ const minutes = Math.floor(elapsed / 60000);
420
+ const seconds = Math.floor((elapsed % 60000) / 1000);
421
+ const duration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
422
+
423
+ const status = '${{ steps.build.outcome }}' === 'success' ? 'success' : 'failure';
424
+
425
+ core.setOutput('status', status);
426
+ core.setOutput('duration', duration);
427
+ console.log(`📋 Android 빌드 결과: ${status} (${duration})`);
428
+
429
+ # ===================================================================
430
+ # Job 4: iOS 빌드 (조건부, 병렬)
431
+ # ===================================================================
432
+ build-ios:
433
+ name: iOS 빌드
434
+ if: |
435
+ needs.prepare.outputs.analyze_only != 'true' &&
436
+ needs.prepare.outputs.enable_ios == 'true'
437
+ runs-on: macos-26
438
+ needs: prepare
439
+
440
+ outputs:
441
+ status: ${{ steps.result.outputs.status }}
442
+ duration: ${{ steps.result.outputs.duration }}
443
+
444
+ steps:
445
+ - name: 빌드 시작 시간 기록
446
+ id: build_start
447
+ uses: actions/github-script@v7
448
+ with:
449
+ script: |
450
+ core.setOutput('time', Date.now().toString());
451
+
452
+ - name: Checkout repository
453
+ uses: actions/checkout@v5
454
+
455
+ - name: Select Xcode version
456
+ run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app/Contents/Developer
457
+
458
+ # .env 파일 생성
459
+ - name: Create .env file
460
+ run: |
461
+ cat << 'EOF' > ${{ env.ENV_FILE_PATH }}
462
+ ${{ secrets.ENV_FILE || secrets.ENV }}
463
+ EOF
464
+ echo "✅ ${{ env.ENV_FILE_PATH }} file created"
465
+
466
+ # Flutter 설정
467
+ - name: Set up Flutter
468
+ uses: subosito/flutter-action@v2
469
+ with:
470
+ flutter-version: ${{ env.FLUTTER_VERSION }}
471
+ cache: true
472
+
473
+ - name: Verify Flutter version
474
+ run: |
475
+ echo "✅ Flutter setup completed"
476
+ flutter --version
477
+
478
+ # Flutter 캐시
479
+ - name: Cache Flutter dependencies
480
+ uses: actions/cache@v4
481
+ with:
482
+ path: ~/.pub-cache
483
+ key: ${{ runner.os }}-flutter-pub-${{ hashFiles('**/pubspec.lock') }}
484
+ restore-keys: ${{ runner.os }}-flutter-pub-
485
+
486
+ # 프로젝트 의존성 설치
487
+ - name: Install dependencies
488
+ run: |
489
+ flutter pub get
490
+ echo "✅ Dependencies installed"
491
+
492
+ # Ruby 및 CocoaPods 설정
493
+ - name: Set up Ruby
494
+ uses: ruby/setup-ruby@v1
495
+ with:
496
+ ruby-version: "3.1"
497
+ bundler-cache: true
498
+
499
+ - name: Install CocoaPods
500
+ run: |
501
+ gem install cocoapods
502
+ cd ios && pod install --repo-update || pod install
503
+ echo "✅ CocoaPods installed"
504
+
505
+ # iOS 빌드 (서명 없이)
506
+ - name: Build iOS
507
+ id: build
508
+ run: |
509
+ echo "📦 Flutter iOS 빌드 시작..."
510
+ flutter build ios --release --no-codesign
511
+ echo "✅ iOS 빌드 완료"
512
+
513
+ # 빌드 결과 기록
514
+ - name: 빌드 결과 기록
515
+ id: result
516
+ if: always()
517
+ uses: actions/github-script@v7
518
+ with:
519
+ script: |
520
+ const buildStart = parseInt('${{ steps.build_start.outputs.time }}');
521
+ const now = Date.now();
522
+ const elapsed = now - buildStart;
523
+ const minutes = Math.floor(elapsed / 60000);
524
+ const seconds = Math.floor((elapsed % 60000) / 1000);
525
+ const duration = minutes > 0 ? `${minutes}분 ${seconds}초` : `${seconds}초`;
526
+
527
+ const status = '${{ steps.build.outcome }}' === 'success' ? 'success' : 'failure';
528
+
529
+ core.setOutput('status', status);
530
+ core.setOutput('duration', duration);
531
+ console.log(`📋 iOS 빌드 결과: ${status} (${duration})`);
532
+
533
+ # ===================================================================
534
+ # Job 5: 결과 보고
535
+ # ===================================================================
536
+ report:
537
+ name: 결과 보고
538
+ if: always() && github.event_name == 'pull_request'
539
+ runs-on: ubuntu-latest
540
+ needs: [prepare, analyze, build-android, build-ios]
541
+
542
+ steps:
543
+ - name: 최종 결과 댓글 업데이트
544
+ uses: actions/github-script@v7
545
+ with:
546
+ github-token: ${{ secrets.GITHUB_TOKEN }}
547
+ script: |
548
+ const commentId = parseInt('${{ needs.prepare.outputs.comment_id }}');
549
+ const startTime = parseInt('${{ needs.prepare.outputs.start_time }}');
550
+ const branchName = '${{ needs.prepare.outputs.branch_name }}';
551
+ const commitHash = '${{ needs.prepare.outputs.commit_hash }}';
552
+ const analyzeOnly = '${{ needs.prepare.outputs.analyze_only }}' === 'true';
553
+ const enableAndroid = '${{ needs.prepare.outputs.enable_android }}' === 'true';
554
+ const enableIos = '${{ needs.prepare.outputs.enable_ios }}' === 'true';
555
+ const runId = '${{ github.run_id }}';
556
+ const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
557
+
558
+ // 결과 수집
559
+ const analyzeStatus = '${{ needs.analyze.outputs.status }}';
560
+ const analyzeDuration = '${{ needs.analyze.outputs.duration }}' || '-';
561
+ const androidStatus = '${{ needs.build-android.outputs.status }}';
562
+ const androidDuration = '${{ needs.build-android.outputs.duration }}' || '-';
563
+ const iosStatus = '${{ needs.build-ios.outputs.status }}';
564
+ const iosDuration = '${{ needs.build-ios.outputs.duration }}' || '-';
565
+
566
+ // 전체 소요 시간 계산
567
+ const now = Date.now();
568
+ const totalElapsed = now - startTime;
569
+ const totalMin = Math.floor(totalElapsed / 60000);
570
+ const totalSec = Math.floor((totalElapsed % 60000) / 1000);
571
+ const totalDuration = totalMin > 0 ? `${totalMin}분 ${totalSec}초` : `${totalSec}초`;
572
+
573
+ // 상태 결정
574
+ let overallSuccess = true;
575
+ let analyzeDisplay, androidDisplay, iosDisplay;
576
+
577
+ // Analyze 상태
578
+ if (analyzeStatus === 'success') {
579
+ analyzeDisplay = '✅ 성공';
580
+ } else {
581
+ analyzeDisplay = '❌ 실패';
582
+ overallSuccess = false;
583
+ }
584
+
585
+ // Android 상태
586
+ if (analyzeOnly) {
587
+ androidDisplay = '⏸️ 스킵';
588
+ } else if (!enableAndroid) {
589
+ androidDisplay = '⏸️ 비활성화';
590
+ } else if (androidStatus === 'success') {
591
+ androidDisplay = '✅ 성공';
592
+ } else {
593
+ androidDisplay = '❌ 실패';
594
+ overallSuccess = false;
595
+ }
596
+
597
+ // iOS 상태
598
+ if (analyzeOnly) {
599
+ iosDisplay = '⏸️ 스킵';
600
+ } else if (!enableIos) {
601
+ iosDisplay = '⏸️ 비활성화';
602
+ } else if (iosStatus === 'success') {
603
+ iosDisplay = '✅ 성공';
604
+ } else {
605
+ iosDisplay = '❌ 실패';
606
+ overallSuccess = false;
607
+ }
608
+
609
+ // 헤더 결정
610
+ let header;
611
+ if (analyzeOnly) {
612
+ header = overallSuccess
613
+ ? '## ✅ Flutter CI (Analyze Only) 성공!'
614
+ : '## ❌ Flutter CI (Analyze Only) 실패';
615
+ } else {
616
+ header = overallSuccess
617
+ ? '## ✅ Flutter CI 성공!'
618
+ : '## ❌ Flutter CI 실패';
619
+ }
620
+
621
+ // 댓글 본문 생성
622
+ const bodyParts = [
623
+ header,
624
+ '',
625
+ '| 검사 항목 | 상태 | 소요 시간 |',
626
+ '|----------|------|----------|',
627
+ `| 🔍 Analyze | ${analyzeDisplay} | ${analyzeDuration} |`,
628
+ `| 🤖 Android 빌드 | ${androidDisplay} | ${analyzeOnly || !enableAndroid ? '-' : androidDuration} |`,
629
+ `| 🍎 iOS 빌드 | ${iosDisplay} | ${analyzeOnly || !enableIos ? '-' : iosDuration} |`,
630
+ '',
631
+ '| 항목 | 값 |',
632
+ '|------|-----|',
633
+ `| **브랜치** | \`${branchName}\` |`,
634
+ `| **커밋** | \`${commitHash}\` |`,
635
+ `| **총 소요 시간** | ${totalDuration} |`,
636
+ ''
637
+ ];
638
+
639
+ // Analyze Only 모드 안내
640
+ if (analyzeOnly && overallSuccess) {
641
+ bodyParts.push('ℹ️ **Analyze Only 모드**: 빌드 없이 코드 분석만 실행되었습니다.');
642
+ bodyParts.push('');
643
+ }
644
+
645
+ // 실패 시 추가 안내
646
+ if (!overallSuccess) {
647
+ bodyParts.push(
648
+ '### 💡 확인 사항',
649
+ ''
650
+ );
651
+
652
+ if (analyzeStatus !== 'success') {
653
+ bodyParts.push('**Analyze 실패 시:**');
654
+ bodyParts.push('- `flutter analyze` 로컬에서 실행하여 lint 오류 확인');
655
+ bodyParts.push('- 코드 스타일 및 타입 오류 수정');
656
+ bodyParts.push('');
657
+ }
658
+
659
+ if (!analyzeOnly && enableAndroid && androidStatus !== 'success') {
660
+ bodyParts.push('**Android 빌드 실패 시:**');
661
+ bodyParts.push('- Gradle 빌드 로그에서 에러 확인');
662
+ bodyParts.push('- 의존성 버전 호환성 확인');
663
+ bodyParts.push('');
664
+ }
665
+
666
+ if (!analyzeOnly && enableIos && iosStatus !== 'success') {
667
+ bodyParts.push('**iOS 빌드 실패 시:**');
668
+ bodyParts.push('- Xcode 빌드 로그에서 에러 확인');
669
+ bodyParts.push('- CocoaPods 의존성 확인');
670
+ bodyParts.push('');
671
+ }
672
+ }
673
+
674
+ bodyParts.push(`**[📋 워크플로우 로그](${runUrl})**`);
675
+
676
+ const body = bodyParts.join('\n');
677
+
678
+ // 댓글 업데이트
679
+ if (commentId) {
680
+ await github.rest.issues.updateComment({
681
+ owner: context.repo.owner,
682
+ repo: context.repo.repo,
683
+ comment_id: commentId,
684
+ body: body
685
+ });
686
+ console.log(`✅ 결과 댓글 업데이트 완료`);
687
+ } else {
688
+ console.log('⚠️ 댓글 ID가 없어 업데이트를 건너뜁니다.');
689
+ }