[Refactor] - admin 배포 설정 및 리팩토링 #109
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: Frontend CI | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| setup: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| format: | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: 'pnpm' | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Format check | |
| run: pnpm format:check | |
| ci: | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - app: web | |
| filter: web | |
| - app: admin | |
| filter: admin | |
| - app: design-system | |
| filter: '@muneo/design-system' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - uses: pnpm/action-setup@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: 'pnpm' | |
| - name: Cache Turbo | |
| uses: actions/cache@v4 | |
| with: | |
| path: .turbo | |
| key: ${{ runner.os }}-turbo-${{ matrix.app }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ matrix.app }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Type check | |
| id: type-check | |
| continue-on-error: true | |
| run: pnpm turbo type-check --filter=${{ matrix.filter }} | |
| - name: Lint check | |
| id: lint | |
| continue-on-error: true | |
| run: pnpm turbo lint --filter=${{ matrix.filter }} | |
| - name: Run tests | |
| id: test | |
| continue-on-error: true | |
| run: pnpm turbo test --filter=${{ matrix.filter }} | |
| - name: Build | |
| id: build | |
| continue-on-error: true | |
| run: pnpm turbo build --filter=${{ matrix.filter }} | |
| - name: Write CI result | |
| if: always() | |
| run: | | |
| mkdir -p ci-results | |
| cat > "ci-results/${{ matrix.app }}.json" <<'JSON' | |
| { | |
| "typeCheck": "${{ steps.type-check.outcome }}", | |
| "lint": "${{ steps.lint.outcome }}", | |
| "test": "${{ steps.test.outcome }}", | |
| "build": "${{ steps.build.outcome }}" | |
| } | |
| JSON | |
| - name: Upload CI result | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ci-result-${{ matrix.app }} | |
| path: ci-results/${{ matrix.app }}.json | |
| if-no-files-found: error | |
| - name: Check results | |
| if: always() | |
| run: | | |
| if [[ "${{ steps.type-check.outcome }}" == "failure" ]] || \ | |
| [[ "${{ steps.lint.outcome }}" == "failure" ]] || \ | |
| [[ "${{ steps.test.outcome }}" == "failure" ]] || \ | |
| [[ "${{ steps.build.outcome }}" == "failure" ]]; then | |
| echo "CI failed" | |
| exit 1 | |
| fi | |
| result: | |
| name: ci | |
| needs: [setup, format, ci] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - name: Check all jobs | |
| run: | | |
| if [[ "${{ needs.setup.result }}" != "success" ]] || \ | |
| [[ "${{ needs.format.result }}" != "success" ]] || \ | |
| [[ "${{ needs.ci.result }}" != "success" ]]; then | |
| exit 1 | |
| fi | |
| comment: | |
| needs: [setup, format, ci] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Download CI results | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ci-result-* | |
| path: ci-results | |
| merge-multiple: true | |
| - name: Comment results on PR | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: context.runId, | |
| }); | |
| const formatJob = jobs.jobs.find(j => j.name === 'format'); | |
| const getStepResult = (job, stepName) => { | |
| if (!job) return 'skipped'; | |
| const step = job.steps?.find(s => s.name === stepName); | |
| return step?.conclusion || 'skipped'; | |
| }; | |
| const readAppResult = (app) => { | |
| const filePath = path.join('ci-results', `${app}.json`); | |
| if (!fs.existsSync(filePath)) { | |
| return { | |
| typeCheck: 'skipped', | |
| lint: 'skipped', | |
| test: 'skipped', | |
| build: 'skipped', | |
| }; | |
| } | |
| return JSON.parse(fs.readFileSync(filePath, 'utf8')); | |
| }; | |
| const icon = (status) => { | |
| if (status === 'success') return '✅'; | |
| if (status === 'skipped') return '⏭️'; | |
| if (status === 'not-applicable') return '➖'; | |
| return '❌'; | |
| }; | |
| const label = (status) => { | |
| if (status === 'success') return '통과'; | |
| if (status === 'skipped') return '스킵'; | |
| if (status === 'not-applicable') return '해당 없음'; | |
| return '실패'; | |
| }; | |
| const formatResult = getStepResult(formatJob, 'Format check'); | |
| const webResult = readAppResult('web'); | |
| const adminResult = readAppResult('admin'); | |
| const dsResult = readAppResult('design-system'); | |
| // 디자인 시스템은 라이브러리라 build 단계가 의도적으로 없음 | |
| dsResult.build = 'not-applicable'; | |
| const rows = [ | |
| ['Type', webResult.typeCheck, adminResult.typeCheck, dsResult.typeCheck], | |
| ['Prettier', formatResult, formatResult, formatResult], | |
| ['Lint', webResult.lint, adminResult.lint, dsResult.lint], | |
| ['tests', webResult.test, adminResult.test, dsResult.test], | |
| ['Build', webResult.build, adminResult.build, dsResult.build], | |
| ]; | |
| let comment = '## 🔍 CI 검증 결과\n\n'; | |
| comment += '| 검증 항목 | web | admin | design-system |\n'; | |
| comment += '|-----------|-----|-------|---------------|\n'; | |
| for (const [name, webStatus, adminStatus, dsStatus] of rows) { | |
| comment += `| **${name}** | ${icon(webStatus)} ${label(webStatus)} | ${icon(adminStatus)} ${label(adminStatus)} | ${icon(dsStatus)} ${label(dsStatus)} |\n`; | |
| } | |
| try { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: comment, | |
| }); | |
| } catch (error) { | |
| console.log('Failed to comment (fork PR may have read-only token):', error.message); | |
| } |