feat(web, server): 회원 프로필 및 로그인·마이페이지 구현 #51
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: CI | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| - develop | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| jobs: | |
| changes: | |
| name: detect changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| ai: ${{ steps.filter.outputs.ai }} | |
| server: ${{ steps.filter.outputs.server }} | |
| web: ${{ steps.filter.outputs.web }} | |
| steps: | |
| - name: Detect changed parts | |
| id: filter | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.payload.pull_request.number, | |
| per_page: 100, | |
| }); | |
| const paths = files.flatMap((file) => | |
| [file.filename, file.previous_filename].filter(Boolean), | |
| ); | |
| const shared = paths.some((path) => | |
| [".editorconfig", ".gitattributes", ".github/workflows/ci.yml"].includes(path) || | |
| path.startsWith(".github/scripts/") || | |
| path.startsWith(".github/prompts/"), | |
| ); | |
| core.setOutput("ai", shared || paths.some((path) => path.startsWith("ai/"))); | |
| core.setOutput("server", shared || paths.some((path) => path.startsWith("server/"))); | |
| core.setOutput("web", shared || paths.some((path) => path.startsWith("web/"))); | |
| ai: | |
| name: ai quality | |
| needs: changes | |
| if: needs.changes.outputs.ai == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: ai | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v9.0.0 | |
| with: | |
| enable-cache: true | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version-file: ai/pyproject.toml | |
| - name: Install dependencies | |
| run: uv sync --frozen --dev | |
| - name: Lint | |
| run: uv run ruff check . | |
| - name: Format check | |
| run: uv run ruff format --check . | |
| - name: Build Docker image | |
| run: docker build -t whylog-ai . | |
| server: | |
| name: server quality | |
| needs: changes | |
| if: needs.changes.outputs.server == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: server | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: 17 | |
| - name: Grant execute permission for Gradle | |
| run: chmod +x ./gradlew | |
| - name: Build with Gradle | |
| run: ./gradlew clean build | |
| web: | |
| name: web quality | |
| needs: changes | |
| if: needs.changes.outputs.web == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: web | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@v6 | |
| with: | |
| version: 10 | |
| - name: Set up Node | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| cache-dependency-path: web/pnpm-lock.yaml | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Lint and format check | |
| run: pnpm exec biome ci . | |
| - name: Type check and build | |
| run: pnpm build | |
| review_harness: | |
| name: AI review tooling tests | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout pull request revision | |
| uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.13" | |
| - name: Test review harness | |
| run: | | |
| python -m unittest discover -s .github/scripts -p "test_*.py" | |
| python -m compileall -q .github/scripts | |
| ai-review: | |
| name: AI review | |
| needs: | |
| - changes | |
| - ai | |
| - server | |
| - web | |
| - review_harness | |
| if: >- | |
| always() && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| needs.changes.result == 'success' && | |
| (needs.ai.result == 'success' || needs.ai.result == 'skipped') && | |
| (needs.server.result == 'success' || needs.server.result == 'skipped') && | |
| (needs.web.result == 'success' || needs.web.result == 'skipped') && | |
| needs.review_harness.result == 'success' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout trusted base revision | |
| uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha }} | |
| persist-credentials: false | |
| - name: Detect trusted reviewer | |
| id: trusted-reviewer | |
| run: | | |
| if [ -f .github/scripts/ai_review.py ]; then | |
| echo "available=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "available=false" >> "$GITHUB_OUTPUT" | |
| echo "Trusted base does not contain the AI reviewer; skipping bootstrap review." | |
| fi | |
| - name: Set up Python | |
| if: steps.trusted-reviewer.outputs.available == 'true' | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.13" | |
| - name: Review pull request | |
| if: steps.trusted-reviewer.outputs.available == 'true' | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| AI_REVIEW_PUSH_TOKEN: ${{ secrets.AI_REVIEW_PUSH_TOKEN }} | |
| AI_REVIEW_ACTOR_LOGIN: whylog-dev | |
| GITHUB_TOKEN: ${{ secrets.AI_REVIEW_PUSH_TOKEN }} | |
| REPOSITORY_IS_PRIVATE: ${{ github.event.repository.private }} | |
| PYTHONUNBUFFERED: "1" | |
| run: python .github/scripts/ai_review.py | |
| - name: Upload PR review document | |
| if: always() && steps.trusted-reviewer.outputs.available == 'true' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: pr-review-${{ github.event.pull_request.number }} | |
| path: docs/pr-reviews/PR-${{ github.event.pull_request.number }}.md | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| quality: | |
| name: quality gate | |
| if: always() | |
| needs: | |
| - changes | |
| - ai | |
| - server | |
| - web | |
| - review_harness | |
| - ai-review | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify machine checks | |
| env: | |
| CHANGES_RESULT: ${{ needs.changes.result }} | |
| AI_RESULT: ${{ needs.ai.result }} | |
| SERVER_RESULT: ${{ needs.server.result }} | |
| WEB_RESULT: ${{ needs.web.result }} | |
| REVIEW_HARNESS_RESULT: ${{ needs.review_harness.result }} | |
| AI_REVIEW_RESULT: ${{ needs.ai-review.result }} | |
| run: | | |
| if [ "$CHANGES_RESULT" != "success" ]; then | |
| echo "Change detection failed: $CHANGES_RESULT" | |
| exit 1 | |
| fi | |
| for result in "$AI_RESULT" "$SERVER_RESULT" "$WEB_RESULT"; do | |
| if [ "$result" != "success" ] && [ "$result" != "skipped" ]; then | |
| echo "Machine check failed: $result" | |
| exit 1 | |
| fi | |
| done | |
| if [ "$REVIEW_HARNESS_RESULT" != "success" ]; then | |
| echo "Review harness check failed: $REVIEW_HARNESS_RESULT" | |
| exit 1 | |
| fi | |
| if [ "$AI_REVIEW_RESULT" != "success" ]; then | |
| echo "AI review failed or did not run: $AI_REVIEW_RESULT" | |
| exit 1 | |
| fi | |
| echo "All required machine checks and AI review passed." |