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.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/bin/project-auto-wizard.js +19 -0
- package/package.json +36 -0
- package/payload/coderabbit.yaml +19 -0
- package/payload/config/breaking-changes.json +1 -0
- package/payload/config/wizard-prompts.yml +62 -0
- package/payload/scripts/changelog_manager.py +726 -0
- package/payload/scripts/version_manager.py +617 -0
- package/payload/version.yml.template +48 -0
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +303 -0
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +293 -0
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +289 -0
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +192 -0
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +209 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +591 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +700 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +308 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +992 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +689 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +987 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +471 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml +500 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +185 -0
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +271 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +81 -0
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +2191 -0
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +386 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +194 -0
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +255 -0
- package/payload/workflows/spring/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +84 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +316 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +58 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +535 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +437 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +2277 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +425 -0
- package/src/cli/args.js +95 -0
- package/src/cli/help.js +27 -0
- package/src/commands/full.js +53 -0
- package/src/commands/interactive.js +276 -0
- package/src/commands/revert.js +60 -0
- package/src/commands/version.js +31 -0
- package/src/commands/workflows.js +49 -0
- package/src/context.js +26 -0
- package/src/core/assets.js +47 -0
- package/src/core/branches.js +60 -0
- package/src/core/branding.js +15 -0
- package/src/core/breaking-check.js +65 -0
- package/src/core/breaking.js +26 -0
- package/src/core/copy/coderabbit.js +26 -0
- package/src/core/copy/gitignore.js +55 -0
- package/src/core/copy/readme.js +30 -0
- package/src/core/copy/simple.js +23 -0
- package/src/core/copy/workflows.js +233 -0
- package/src/core/detect-fs.js +106 -0
- package/src/core/detect.js +62 -0
- package/src/core/fsutil.js +46 -0
- package/src/core/options-ask.js +99 -0
- package/src/core/paths-resolve.js +261 -0
- package/src/core/paths.js +16 -0
- package/src/core/version-yml.js +190 -0
- package/src/core/wizard-env.js +101 -0
- package/src/core/wizard-labels.js +107 -0
- package/src/index.js +162 -0
- package/src/ui/ansi.js +26 -0
- package/src/ui/banner.js +29 -0
- package/src/ui/env-plan.js +207 -0
- package/src/ui/prompts.js +99 -0
- package/src/ui/readline-engine.js +257 -0
- package/src/ui/status-cards.js +56 -0
- 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
|
+
}
|