diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..3c2617b --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,137 @@ +name: CI/CD Workflow + +on: + push: + branches: + - main + - develop + - "feat/**" + - "fix/**" + - "refactor/**" + - "style/**" + pull_request: + branches: + - develop + +permissions: + contents: read + pull-requests: write + +jobs: + # 0️⃣ Lint + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Run lint + run: pnpm lint + + # 1️⃣ Build & Test + build: + runs-on: ubuntu-latest + needs: lint + steps: + - uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build + run: pnpm run build + - name: Test + run: pnpm test || true + + # Build/Test 실패 시 PR 코멘트 + 닫기 + - name: Fail PR if build/test fails + if: ${{ failure() && github.event_name == 'pull_request' }} + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = ${{ github.event.pull_request.number }}; + const updated_title = `[CI FAIL] ${{ github.event.pull_request.title }}`; + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr, + body: '빌드 또는 테스트에 실패했습니다.', + event: 'REQUEST_CHANGES' + }); + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr, + title: updated_title, + state: 'closed' + }); + + # 2️⃣ Preview 배포 (feat/*, fix/*, refactor/*, style/*, develop PR) + deploy-preview: + if: ${{ !startsWith(github.ref, 'refs/heads/main') }} + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Vercel CLI 설치 + run: npm install -g vercel@latest + - name: Vercel Preview Deploy + id: deploy + run: | + DEPLOY_URL=$(vercel deploy --token=${{ secrets.VERCEL_TOKEN }} --yes) + echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + - name: PR 코멘트에 Preview URL 작성 + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const deployUrl = '${{ steps.deploy.outputs.url }}'; + const comment = `🚀 **미리보기 배포 완료!**\n\n📝 **배포 URL:** ${deployUrl}\n✅ **브랜치:** \`${{ github.head_ref }}\`\n✅ **커밋:** \`${{ github.sha }}\``; + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); + + # 3️⃣ Production 배포 (main push) + deploy-production: + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Vercel CLI 설치 + run: npm install -g vercel@latest + - name: Vercel Production Deploy + id: deploy-prod + run: | + DEPLOY_URL=$(vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }} --yes) + echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + - name: 운영 배포 완료 알림 + run: | + echo "🎉 운영 환경 배포 완료!" + echo "📝 배포 URL: ${{ steps.deploy-prod.outputs.url }}" diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index 3d7ded3..ecf34c0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,9 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' - function App() { - const [count, setCount] = useState(0) - return ( <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+
테스트 중
- ) + ); } -export default App +export default App;