Skip to content

🚀[기능개선][로그인][스플래시]스플래시 및 로그인 화면 UI를 맵시 브랜드에 맞게 리디자인 #153

🚀[기능개선][로그인][스플래시]스플래시 및 로그인 화면 UI를 맵시 브랜드에 맞게 리디자인

🚀[기능개선][로그인][스플래시]스플래시 및 로그인 화면 UI를 맵시 브랜드에 맞게 리디자인 #153

# ===================================================================
# 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 빌드 워크플로우 트리거 완료');