feat: 공통 위젯 시스템 및 디자인 가이드 구축 #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Project Flutter CI | |
| # PR과 main/develop 브랜치 push 시 자동 실행 | |
| # Android, iOS 플랫폼만 검증 (macOS, Linux, Web 제외) | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: # 수동 실행 허용 | |
| permissions: | |
| contents: read | |
| pull-requests: write # PR에 댓글을 달기 위한 권한 | |
| jobs: | |
| # 코드 품질 검증 작업 | |
| analyze: | |
| name: Code Analysis | |
| runs-on: ubuntu-latest | |
| steps: | |
| # 1. 저장소 코드 체크아웃 | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| # 2. Flutter SDK 설치 | |
| - name: Setup Flutter | |
| uses: subosito/flutter-action@v2 | |
| with: | |
| flutter-version: '3.35.5' | |
| channel: 'stable' | |
| cache: true | |
| # 3. 의존성 설치 | |
| - name: Install dependencies | |
| run: flutter pub get | |
| # 4. 환경 변수 파일 생성 (.env 파일 - analyze 에러 방지) | |
| - name: Create .env file | |
| run: | | |
| cat > .env << 'EOF' | |
| ${{ secrets.ENV }} | |
| EOF | |
| # 5. 코드 포맷팅 검사 | |
| - name: Check formatting | |
| id: format_check | |
| continue-on-error: true | |
| run: | | |
| dart format --set-exit-if-changed . 2>&1 | tee format_output.txt | |
| exit ${PIPESTATUS[0]} | |
| # 6. 정적 분석 | |
| - name: Analyze code | |
| id: analyze_check | |
| continue-on-error: true | |
| run: | | |
| flutter analyze 2>&1 | tee analyze_output.txt | |
| exit ${PIPESTATUS[0]} | |
| # 7. PR에 결과 댓글 달기 (실패 시) | |
| - name: Comment on PR with issues | |
| if: failure() && github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| let commentBody = '## 🚨 Flutter CI 검사 실패\n\n'; | |
| let hasIssues = false; | |
| // 포맷팅 검사 결과 | |
| if ('${{ steps.format_check.outcome }}' === 'failure') { | |
| hasIssues = true; | |
| commentBody += '### ❌ 코드 포맷팅 검사 실패\n\n'; | |
| commentBody += '다음 명령어로 자동 포맷팅을 적용하세요:\n'; | |
| commentBody += '```bash\n'; | |
| commentBody += 'dart format .\n'; | |
| commentBody += '```\n\n'; | |
| try { | |
| const formatOutput = fs.readFileSync('format_output.txt', 'utf8'); | |
| if (formatOutput.trim()) { | |
| commentBody += '<details>\n<summary>포맷팅 오류 상세 내용</summary>\n\n'; | |
| commentBody += '```\n' + formatOutput.trim() + '\n```\n\n'; | |
| commentBody += '</details>\n\n'; | |
| } | |
| } catch (e) { | |
| console.log('포맷팅 출력 파일을 읽을 수 없습니다.'); | |
| } | |
| } | |
| // 정적 분석 결과 | |
| if ('${{ steps.analyze_check.outcome }}' === 'failure') { | |
| hasIssues = true; | |
| commentBody += '### ❌ 정적 분석 검사 실패\n\n'; | |
| try { | |
| const analyzeOutput = fs.readFileSync('analyze_output.txt', 'utf8'); | |
| if (analyzeOutput.trim()) { | |
| // 오류/경고 라인 추출 | |
| const lines = analyzeOutput.split('\n'); | |
| const issues = lines.filter(line => | |
| line.includes('error •') || | |
| line.includes('warning •') || | |
| line.includes('info •') | |
| ); | |
| if (issues.length > 0) { | |
| commentBody += '발견된 문제:\n\n'; | |
| commentBody += '```\n'; | |
| commentBody += issues.slice(0, 20).join('\n'); // 최대 20개만 표시 | |
| if (issues.length > 20) { | |
| commentBody += '\n... 그 외 ' + (issues.length - 20) + '개의 문제'; | |
| } | |
| commentBody += '\n```\n\n'; | |
| } | |
| commentBody += '<details>\n<summary>전체 분석 결과 보기</summary>\n\n'; | |
| commentBody += '```\n' + analyzeOutput.trim() + '\n```\n\n'; | |
| commentBody += '</details>\n\n'; | |
| } | |
| } catch (e) { | |
| console.log('분석 출력 파일을 읽을 수 없습니다.'); | |
| } | |
| } | |
| if (hasIssues) { | |
| commentBody += '---\n'; | |
| commentBody += '💡 **수정 방법:**\n'; | |
| commentBody += '1. 로컬에서 `dart format .` 명령어로 포맷팅 적용\n'; | |
| commentBody += '2. `flutter analyze` 명령어로 오류 확인\n'; | |
| commentBody += '3. 오류를 수정한 후 다시 커밋하세요\n'; | |
| // 기존 봇 댓글 찾기 | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => | |
| comment.user.type === 'Bot' && | |
| comment.body.includes('🚨 Flutter CI 검사 실패') | |
| ); | |
| if (botComment) { | |
| // 기존 댓글 업데이트 | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: commentBody | |
| }); | |
| } else { | |
| // 새 댓글 작성 | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: commentBody | |
| }); | |
| } | |
| } | |
| # 8. 최종 결과 확인 (실패 시 워크플로우 실패) | |
| - name: Check final status | |
| if: steps.format_check.outcome == 'failure' || steps.analyze_check.outcome == 'failure' | |
| run: | | |
| echo "❌ CI 검사 실패!" | |
| echo "포맷팅 검사: ${{ steps.format_check.outcome }}" | |
| echo "정적 분석: ${{ steps.analyze_check.outcome }}" | |
| exit 1 |