🚀[기능개선][로그인][스플래시]스플래시 및 로그인 화면 UI를 맵시 브랜드에 맞게 리디자인 #153
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
| # =================================================================== | |
| # Flutter 테스트 앱 빌드 트리거 워크플로우 (SUH-LAB) | |
| # =================================================================== | |
| # | |
| # 이 워크플로우는 PR 또는 이슈에 댓글로 빌드 명령어를 작성하면 | |
| # Android와 iOS 테스트 앱 빌드를 자동으로 트리거합니다. | |
| # | |
| # 주요 특징: | |
| # - PR/이슈 댓글 감지: "@suh-lab build app/apk build/ios build" 키워드 감지 | |
| # - repository_dispatch 이벤트로 빌드 워크플로우 트리거 | |
| # - PR/이슈 번호 + 빌드 횟수로 고유한 빌드 번호 생성 (예: 38700, 38701...) | |
| # - 브랜치명에서 이슈 번호 자동 추출 (YYYYMMDD_#이슈번호_내용 형식) | |
| # - 테스트 버전 0.0.0 고정으로 운영 버전과 분리 | |
| # | |
| # 사용 방법: | |
| # - PR에 댓글 작성: "@suh-lab build app" (Android + iOS 모두 빌드) | |
| # - PR에 댓글 작성: "@suh-lab apk build" (Android만 빌드) | |
| # - PR에 댓글 작성: "@suh-lab ios build" (iOS만 빌드) | |
| # - 이슈에 댓글 작성: 위 명령어 동일 (Guide by SUH-LAB 댓글 필요) | |
| # | |
| # 트리거되는 워크플로우: | |
| # - PROJECT-FLUTTER-ANDROID-TEST-APK.yaml (event_type: build-android-app) | |
| # - PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml (event_type: build-ios-app) | |
| # | |
| # =================================================================== | |
| name: PROJECT-Flutter-SUH-LAB-App-Build-Trigger | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| jobs: | |
| trigger-builds: | |
| name: 빌드 워크플로우 트리거 | |
| # @suh-lab build app / @suh-lab apk build / @suh-lab ios build 키워드 감지 | |
| if: | | |
| contains(github.event.comment.body, '@suh-lab') && | |
| ( | |
| (contains(github.event.comment.body, 'build') && contains(github.event.comment.body, 'app')) || | |
| (contains(github.event.comment.body, 'apk') && contains(github.event.comment.body, 'build')) || | |
| (contains(github.event.comment.body, 'ios') && contains(github.event.comment.body, 'build')) | |
| ) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 댓글에 👀 리액션 추가 | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const commentId = context.payload.comment.id; | |
| await github.rest.reactions.createForIssueComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: commentId, | |
| content: 'eyes' | |
| }); | |
| console.log('👀 댓글에 확인 리액션 추가 완료'); | |
| - name: 빌드 타입 판별 | |
| id: build_type | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const comment = context.payload.comment.body.toLowerCase(); | |
| let buildAndroid = false; | |
| let buildIos = false; | |
| let buildType = ''; | |
| if (comment.includes('build') && comment.includes('app')) { | |
| // @suh-lab build app → 양쪽 모두 | |
| buildAndroid = true; | |
| buildIos = true; | |
| buildType = 'app'; | |
| } else if (comment.includes('apk') && comment.includes('build')) { | |
| // @suh-lab apk build → Android만 | |
| buildAndroid = true; | |
| buildType = 'apk'; | |
| } else if (comment.includes('ios') && comment.includes('build')) { | |
| // @suh-lab ios build → iOS만 | |
| buildIos = true; | |
| buildType = 'ios'; | |
| } | |
| core.setOutput('buildAndroid', buildAndroid.toString()); | |
| core.setOutput('buildIos', buildIos.toString()); | |
| core.setOutput('buildType', buildType); | |
| console.log(`📱 빌드 타입: ${buildType} (Android=${buildAndroid}, iOS=${buildIos})`); | |
| - name: PR/이슈 정보 확인 및 추출 | |
| id: source_info | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const issueNumber = context.payload.issue.number; | |
| let sourceType = ''; | |
| let sourceNumber = ''; | |
| let branchName = ''; | |
| let headSha = ''; | |
| let relatedIssueNumber = ''; | |
| // 1. 먼저 PR인지 확인 | |
| try { | |
| const pr = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: issueNumber | |
| }); | |
| sourceType = 'PR'; | |
| sourceNumber = pr.data.number.toString(); | |
| branchName = pr.data.head.ref; | |
| headSha = pr.data.head.sha; | |
| console.log(`✅ PR 확인: #${sourceNumber}`); | |
| console.log(`🌿 브랜치명: ${branchName}`); | |
| console.log(`📝 커밋 해시: ${headSha}`); | |
| // 브랜치명에서 이슈 번호 추출 (#280 형식) | |
| const issueMatch = branchName.match(/#(\d+)/); | |
| relatedIssueNumber = issueMatch ? issueMatch[1] : ''; | |
| if (relatedIssueNumber) { | |
| console.log(`📌 추출된 이슈 번호: #${relatedIssueNumber}`); | |
| } | |
| } catch (error) { | |
| console.log('ℹ️ PR이 아닙니다. 이슈에서 브랜치 정보를 찾습니다...'); | |
| // 2. PR이 아니면 이슈에서 "Guide by SUH-LAB" 댓글 찾기 | |
| sourceType = 'ISSUE'; | |
| sourceNumber = issueNumber.toString(); | |
| relatedIssueNumber = issueNumber.toString(); | |
| try { | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| per_page: 100 | |
| }); | |
| // "Guide by SUH-LAB" 댓글 찾기 | |
| const guideComment = comments.data.find(c => | |
| c.body.includes('Guide by SUH-LAB') | |
| ); | |
| if (!guideComment) { | |
| console.log('❌ "Guide by SUH-LAB" 댓글을 찾을 수 없습니다.'); | |
| core.setOutput('found', 'false'); | |
| core.setOutput('errorMessage', '이슈에서 "Guide by SUH-LAB" 댓글을 찾을 수 없습니다. 브랜치 정보가 포함된 댓글이 필요합니다.'); | |
| return; | |
| } | |
| console.log('✅ "Guide by SUH-LAB" 댓글 발견'); | |
| // 브랜치명 추출 (### 브랜치 다음 ``` 블록 내용) | |
| const branchMatch = guideComment.body.match(/### 브랜치\s*```\s*([\s\S]*?)\s*```/); | |
| if (!branchMatch) { | |
| console.log('❌ 브랜치 정보를 파싱할 수 없습니다.'); | |
| core.setOutput('found', 'false'); | |
| core.setOutput('errorMessage', '"Guide by SUH-LAB" 댓글에서 브랜치 정보를 파싱할 수 없습니다. 댓글 형식을 확인해주세요.'); | |
| return; | |
| } | |
| branchName = branchMatch[1].trim(); | |
| console.log(`🌿 추출된 브랜치명: ${branchName}`); | |
| console.log(`✅ 이슈 #${sourceNumber}에서 빌드 정보 추출 완료`); | |
| } catch (commentError) { | |
| console.log('❌ 이슈 댓글 조회 중 오류 발생:', commentError.message); | |
| core.setOutput('found', 'false'); | |
| core.setOutput('errorMessage', `이슈 댓글 조회 중 오류가 발생했습니다: ${commentError.message}`); | |
| return; | |
| } | |
| } | |
| // 결과 출력 | |
| core.setOutput('found', 'true'); | |
| core.setOutput('sourceType', sourceType); | |
| core.setOutput('sourceNumber', sourceNumber); | |
| core.setOutput('branchName', branchName); | |
| core.setOutput('headSha', headSha); | |
| core.setOutput('relatedIssueNumber', relatedIssueNumber); | |
| - name: 브랜치 정보를 찾을 수 없는 경우 에러 댓글 작성 | |
| if: steps.source_info.outputs.found != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const issueNumber = context.payload.issue.number; | |
| const errorMessage = '${{ steps.source_info.outputs.errorMessage }}'; | |
| const body = `❌ **앱 빌드 트리거 실패** | |
| ${errorMessage} | |
| **해결 방법:** | |
| - PR에서 빌드하는 경우: PR 페이지에서 댓글을 작성해주세요. | |
| - 이슈에서 빌드하는 경우: "Guide by SUH-LAB" 댓글이 있어야 합니다. | |
| - 댓글에 \`### 브랜치\` 섹션과 브랜치명이 포함되어 있어야 합니다.`; | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| body: body | |
| }); | |
| core.setFailed(errorMessage); | |
| - name: 브랜치 존재 여부 확인 | |
| id: check_branch | |
| if: steps.source_info.outputs.found == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const branchName = '${{ steps.source_info.outputs.branchName }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| console.log(`🔍 브랜치 존재 여부 확인: ${branchName}`); | |
| try { | |
| await github.rest.repos.getBranch({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| branch: branchName | |
| }); | |
| console.log(`✅ 브랜치 존재 확인됨: ${branchName}`); | |
| core.setOutput('exists', 'true'); | |
| } catch (error) { | |
| if (error.status === 404) { | |
| console.log(`❌ 브랜치를 찾을 수 없음: ${branchName}`); | |
| core.setOutput('exists', 'false'); | |
| core.setOutput('errorMessage', `브랜치 \`${branchName}\`를 찾을 수 없습니다.`); | |
| } else { | |
| console.log(`❌ 브랜치 확인 중 오류: ${error.message}`); | |
| core.setOutput('exists', 'false'); | |
| core.setOutput('errorMessage', `브랜치 확인 중 오류가 발생했습니다: ${error.message}`); | |
| } | |
| } | |
| - name: 브랜치가 존재하지 않는 경우 에러 댓글 작성 | |
| if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'false' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const issueNumber = context.payload.issue.number; | |
| const branchName = '${{ steps.source_info.outputs.branchName }}'; | |
| const runId = '${{ github.run_id }}'; | |
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`; | |
| const body = [ | |
| '❌ **앱 빌드 트리거 실패 - 브랜치를 찾을 수 없습니다**', | |
| '', | |
| '| 항목 | 값 |', | |
| '|------|-----|', | |
| `| **요청된 브랜치** | \`${branchName}\` |`, | |
| `| **이슈/PR** | #${issueNumber} |`, | |
| '', | |
| '### 💡 확인 사항', | |
| '1. 브랜치가 원격 저장소에 push되었는지 확인하세요', | |
| '2. "Guide by SUH-LAB" 댓글의 브랜치명이 올바른지 확인하세요', | |
| '3. 브랜치명에 오타가 없는지 확인하세요', | |
| '', | |
| `🔗 [워크플로우 로그](${runUrl})` | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| body: body | |
| }); | |
| core.setFailed(`브랜치를 찾을 수 없습니다: ${branchName}`); | |
| - name: 빌드 횟수 조회 및 빌드 번호 생성 | |
| id: build_number_calc | |
| if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const sourceNumber = parseInt('${{ steps.source_info.outputs.sourceNumber }}'); | |
| const sourceType = '${{ steps.source_info.outputs.sourceType }}'; | |
| const buildType = '${{ steps.build_type.outputs.buildType }}'; | |
| console.log(`📊 ${sourceType} #${sourceNumber}의 빌드 횟수 조회 중...`); | |
| console.log(`📱 빌드 타입: ${buildType}`); | |
| // PR/이슈의 모든 댓글 조회 | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: sourceNumber, | |
| per_page: 100 | |
| }); | |
| // 빌드 타입에 따라 플랫폼별 빌드 카운트 (app build는 iOS/Android 모두 포함하므로 통합 카운트) | |
| // - iOS 빌드 번호: 'ios build' + 'build app' (app build도 iOS 빌드 포함) | |
| // - Android 빌드 번호: 'apk build' + 'build app' (app build도 Android 빌드 포함) | |
| // - App 빌드 번호: 'build app' + 'ios build' + 'apk build' (모든 빌드 통합) | |
| let buildCount = 0; | |
| if (buildType === 'ios') { | |
| // iOS 빌드: 'ios build' + 'build app' 모두 카운트 | |
| buildCount = comments.data.filter(c => { | |
| const body = c.body.toLowerCase(); | |
| return body.includes('@suh-lab') && ( | |
| body.includes('ios build') || | |
| (body.includes('build') && body.includes('app')) | |
| ); | |
| }).length; | |
| console.log(`📊 iOS 빌드 카운트: 'ios build' + 'build app' = ${buildCount}개`); | |
| } else if (buildType === 'apk') { | |
| // Android 빌드: 'apk build' + 'build app' 모두 카운트 | |
| buildCount = comments.data.filter(c => { | |
| const body = c.body.toLowerCase(); | |
| return body.includes('@suh-lab') && ( | |
| body.includes('apk build') || | |
| (body.includes('build') && body.includes('app')) | |
| ); | |
| }).length; | |
| console.log(`📊 Android 빌드 카운트: 'apk build' + 'build app' = ${buildCount}개`); | |
| } else if (buildType === 'app') { | |
| // App 빌드 (iOS + Android 동시): 모든 빌드 명령어 통합 카운트 | |
| buildCount = comments.data.filter(c => { | |
| const body = c.body.toLowerCase(); | |
| return body.includes('@suh-lab') && ( | |
| body.includes('ios build') || | |
| body.includes('apk build') || | |
| (body.includes('build') && body.includes('app')) | |
| ); | |
| }).length; | |
| console.log(`📊 App 빌드 카운트: 'build app' + 'ios build' + 'apk build' = ${buildCount}개`); | |
| } | |
| // 빌드 번호: 소스번호 + 2자리 카운트 (38800, 38801, 38802...) | |
| const buildNumber = `${sourceNumber}${String(buildCount).padStart(2, '0')}`; | |
| console.log(`📊 ${sourceType} #${sourceNumber}: ${buildType} ${buildCount}번째 빌드`); | |
| console.log(`📦 생성된 빌드 번호: ${buildNumber}`); | |
| core.setOutput('buildCount', buildCount.toString()); | |
| core.setOutput('buildNumber', buildNumber); | |
| - name: Android 빌드 워크플로우 트리거 | |
| if: | | |
| steps.source_info.outputs.found == 'true' && | |
| steps.check_branch.outputs.exists == 'true' && | |
| steps.build_type.outputs.buildAndroid == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const sourceType = '${{ steps.source_info.outputs.sourceType }}'; | |
| const sourceNumber = '${{ steps.source_info.outputs.sourceNumber }}'; | |
| const branchName = '${{ steps.source_info.outputs.branchName }}'; | |
| const relatedIssueNumber = '${{ steps.source_info.outputs.relatedIssueNumber }}'; | |
| const buildNumber = '${{ steps.build_number_calc.outputs.buildNumber }}'; | |
| console.log(`🚀 Android 빌드 워크플로우 트리거 시작...`); | |
| console.log(` ${sourceType} 번호: #${sourceNumber}`); | |
| console.log(` 빌드 번호: ${buildNumber}`); | |
| console.log(` 브랜치: ${branchName}`); | |
| console.log(` 관련 이슈 번호: ${relatedIssueNumber || '없음'}`); | |
| await github.rest.repos.createDispatchEvent({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| event_type: 'build-android-app', | |
| client_payload: { | |
| pr_number: sourceNumber, | |
| build_number: buildNumber, | |
| branch_name: branchName, | |
| issue_number: relatedIssueNumber || '', | |
| triggered_by: `${sourceType.toLowerCase()}-comment`, | |
| comment_id: context.payload.comment.id.toString(), | |
| source_type: sourceType | |
| } | |
| }); | |
| console.log('✅ Android 빌드 워크플로우 트리거 완료'); | |
| - name: iOS 빌드 워크플로우 트리거 | |
| if: | | |
| steps.source_info.outputs.found == 'true' && | |
| steps.check_branch.outputs.exists == 'true' && | |
| steps.build_type.outputs.buildIos == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const sourceType = '${{ steps.source_info.outputs.sourceType }}'; | |
| const sourceNumber = '${{ steps.source_info.outputs.sourceNumber }}'; | |
| const branchName = '${{ steps.source_info.outputs.branchName }}'; | |
| const relatedIssueNumber = '${{ steps.source_info.outputs.relatedIssueNumber }}'; | |
| const buildNumber = '${{ steps.build_number_calc.outputs.buildNumber }}'; | |
| console.log(`🚀 iOS 빌드 워크플로우 트리거 시작...`); | |
| console.log(` ${sourceType} 번호: #${sourceNumber}`); | |
| console.log(` 빌드 번호: ${buildNumber}`); | |
| console.log(` 브랜치: ${branchName}`); | |
| console.log(` 관련 이슈 번호: ${relatedIssueNumber || '없음'}`); | |
| await github.rest.repos.createDispatchEvent({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| event_type: 'build-ios-app', | |
| client_payload: { | |
| pr_number: sourceNumber, | |
| build_number: buildNumber, | |
| branch_name: branchName, | |
| issue_number: relatedIssueNumber || '', | |
| triggered_by: `${sourceType.toLowerCase()}-comment`, | |
| comment_id: context.payload.comment.id.toString(), | |
| source_type: sourceType | |
| } | |
| }); | |
| console.log('✅ iOS 빌드 워크플로우 트리거 완료'); |