From c95812f66adb1886bfbba11887cc55cf2ce8817c Mon Sep 17 00:00:00 2001 From: firatyll Date: Tue, 15 Jul 2025 16:30:26 +0200 Subject: [PATCH 1/2] feat: add CI/CD pipeline for dev branch with code quality checks and AI-generated pull requests --- .github/workflows/dev-to-main-pr.yml | 258 +++++++++++++++++++++++++++ README.md | 12 ++ package.json | 4 +- 3 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/dev-to-main-pr.yml diff --git a/.github/workflows/dev-to-main-pr.yml b/.github/workflows/dev-to-main-pr.yml new file mode 100644 index 0000000..c4b405a --- /dev/null +++ b/.github/workflows/dev-to-main-pr.yml @@ -0,0 +1,258 @@ +name: 🚀 Dev Branch CI/CD Pipeline + +on: + push: + branches: [ dev ] + workflow_dispatch: + +env: + NODE_VERSION: '18' + +jobs: + code-quality-check: + name: 🔍 Code Quality & Syntax Check + runs-on: ubuntu-latest + + outputs: + checks-passed: ${{ steps.quality-check.outputs.passed }} + check-results: ${{ steps.quality-check.outputs.results }} + + steps: + - name: 📥 Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 📦 Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: 📥 Install dependencies + run: npm ci + + - name: 🏗️ Generate Prisma Client + run: npx prisma generate + + - name: 🔍 TypeScript Compilation Check + id: typescript-check + run: | + echo "🔍 Running TypeScript compilation check..." + if npm run type-check; then + echo "✅ TypeScript compilation successful" + echo "typescript=passed" >> $GITHUB_OUTPUT + else + echo "❌ TypeScript compilation failed" + echo "typescript=failed" >> $GITHUB_OUTPUT + exit 1 + fi + + - name: 🏗️ Build Check + id: build-check + run: | + echo "🏗️ Running build check..." + if npm run build; then + echo "✅ Build successful" + echo "build=passed" >> $GITHUB_OUTPUT + else + echo "❌ Build failed" + echo "build=failed" >> $GITHUB_OUTPUT + exit 1 + fi + + - name: 📋 Code Quality Summary + id: quality-check + run: | + echo "📋 Generating code quality summary..." + + # Get commit info + COMMIT_SHA="${{ github.sha }}" + COMMIT_MSG=$(git log -1 --pretty=format:"%s" $COMMIT_SHA) + COMMIT_AUTHOR=$(git log -1 --pretty=format:"%an" $COMMIT_SHA) + + # Get changed files + CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD | tr '\n' ',' | sed 's/,$//') + + # Create summary + SUMMARY="✅ All code quality checks passed! + + 📊 **Check Results:** + - ✅ TypeScript Compilation: Passed + - ✅ Build Process: Passed + + 📝 **Commit Info:** + - SHA: \`${COMMIT_SHA:0:7}\` + - Message: ${COMMIT_MSG} + - Author: ${COMMIT_AUTHOR} + + 📁 **Changed Files:** ${CHANGED_FILES}" + + echo "passed=true" >> $GITHUB_OUTPUT + echo "results<> $GITHUB_OUTPUT + echo "$SUMMARY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + create-pull-request: + name: 🤖 Create AI-Generated Pull Request + runs-on: ubuntu-latest + needs: code-quality-check + if: needs.code-quality-check.outputs.checks-passed == 'true' + + steps: + - name: 📥 Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: 🤖 Generate PR Content with AI + id: generate-pr + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + echo "🤖 Generating AI-powered PR content..." + + # Get commit info and diff + COMMIT_SHA="${{ github.sha }}" + COMMIT_MSG=$(git log -1 --pretty=format:"%s" $COMMIT_SHA) + COMMIT_AUTHOR=$(git log -1 --pretty=format:"%an" $COMMIT_SHA) + COMMIT_DATE=$(git log -1 --pretty=format:"%ci" $COMMIT_SHA) + + # Get detailed diff + GIT_DIFF=$(git diff HEAD~1 HEAD --unified=3 | head -c 4000) + + # Get changed files with stats + CHANGED_FILES=$(git diff --stat HEAD~1 HEAD) + + # Create AI prompt + AI_PROMPT="You are a senior software engineer reviewing code changes for a Node.js/TypeScript candidate matching API. + + Generate a professional pull request description based on this information: + + **Commit Information:** + - Message: ${COMMIT_MSG} + - Author: ${COMMIT_AUTHOR} + - Date: ${COMMIT_DATE} + - SHA: ${COMMIT_SHA:0:7} + + **Files Changed:** + ${CHANGED_FILES} + + **Code Diff (truncated):** + ${GIT_DIFF} + + Please generate: + 1. A clear, professional PR title + 2. A comprehensive PR description with: + - Summary of changes + - Technical details + - Testing status + - Any breaking changes + - Code quality improvements + + Format the response as JSON with 'title' and 'body' fields. Make it professional and detailed." + + # Call OpenAI API + PR_CONTENT=$(curl -s -X POST "https://api.openai.com/v1/chat/completions" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\": \"gpt-4\", + \"messages\": [ + { + \"role\": \"system\", + \"content\": \"You are a senior software engineer who writes excellent pull request descriptions. Always respond with valid JSON containing 'title' and 'body' fields.\" + }, + { + \"role\": \"user\", + \"content\": $(echo "$AI_PROMPT" | jq -R -s .) + } + ], + \"max_tokens\": 1000, + \"temperature\": 0.3 + }") + + echo "📝 AI Response received" + + # Extract title and body from AI response + PR_TITLE=$(echo "$PR_CONTENT" | jq -r '.choices[0].message.content' | jq -r '.title' 2>/dev/null || echo "🚀 Dev → Main: ${COMMIT_MSG}") + PR_BODY=$(echo "$PR_CONTENT" | jq -r '.choices[0].message.content' | jq -r '.body' 2>/dev/null || echo "**Summary:** ${COMMIT_MSG} + + **Code Quality Status:** + ${{ needs.code-quality-check.outputs.check-results }} + + **Auto-generated PR from dev branch** 🤖") + + # Set outputs + echo "title<> $GITHUB_OUTPUT + echo "$PR_TITLE" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "body<> $GITHUB_OUTPUT + echo "$PR_BODY" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: 🔄 Create Pull Request + id: create-pr + uses: peter-evans/create-pull-request@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: auto-pr-from-dev-${{ github.run_number }} + base: main + title: ${{ steps.generate-pr.outputs.title }} + body: | + ${{ steps.generate-pr.outputs.body }} + + --- + + 🤖 **Auto-generated Pull Request** + - 📊 **Pipeline Run:** #${{ github.run_number }} + - 🔗 **Workflow:** [${{ github.workflow }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + - 📅 **Created:** ${{ github.event.head_commit.timestamp }} + - 👤 **Triggered by:** ${{ github.event.head_commit.author.name }} + + **Code Quality Checks:** + ${{ needs.code-quality-check.outputs.check-results }} + draft: false + delete-branch: true + + - name: 👥 Assign Reviewers + if: steps.create-pr.outputs.pull-request-number + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "👥 Assigning reviewers to PR #${{ steps.create-pr.outputs.pull-request-number }}" + + # Try to assign github-actions bot as reviewer (if available) + # Note: You may need to replace this with actual usernames that have access to your repo + gh pr edit ${{ steps.create-pr.outputs.pull-request-number }} \ + --add-reviewer firatyll \ + --add-label "auto-generated,dev-to-main,code-review-needed" || echo "Could not assign reviewers" + + - name: 📝 Comment on PR + if: steps.create-pr.outputs.pull-request-number + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr comment ${{ steps.create-pr.outputs.pull-request-number }} --body "🤖 **Automated PR Creation Complete!** + + ✅ **Code Quality Checks Passed:** + - TypeScript compilation + - Build process + - Syntax validation + + 🔍 **Review Checklist:** + - [ ] Code changes are logical and well-structured + - [ ] No breaking changes introduced + - [ ] API endpoints function correctly + - [ ] Database schema changes are safe + - [ ] Error handling is appropriate + + 🚀 **Ready for review!** @firatyll" + + - name: 📢 Summary + run: | + echo "🎉 Workflow completed successfully!" + echo "📝 Pull Request: ${{ steps.create-pr.outputs.pull-request-url }}" + echo "🔢 PR Number: ${{ steps.create-pr.outputs.pull-request-number }}" diff --git a/README.md b/README.md index fe8797e..3e67a8f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ # Candidate Matching API +[![Dev to Main PR](https://github.com/firatyll/candidate-matching/actions/workflows/dev-to-main-pr.yml/badge.svg)](https://github.com/firatyll/candidate-matching/actions/workflows/dev-to-main-pr.yml) + > **Note: This application is currently under active development. Features and API endpoints may change.** A RESTful API service for candidate and job position matching built with Node.js, Express, TypeScript, Prisma, and PostgreSQL. +## 🚀 CI/CD Pipeline + +This repository includes an automated GitHub Actions pipeline that: +- ✅ Runs code quality checks on every push to `dev` branch +- 🤖 Generates AI-powered pull requests using OpenAI GPT-4 +- 📋 Automatically assigns reviewers and adds helpful labels +- 🔍 Includes comprehensive review checklists + +See [Pipeline Setup Guide](./.github/PIPELINE_SETUP.md) for configuration details. + ## Features - **Candidate Management**: Create, read, update, and delete candidate profiles diff --git a/package.json b/package.json index 679080c..f1ae3ff 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "prestart": "npm run build", "serve": "npm run build && npm start", "type-check": "tsc --noEmit", - "lint": "echo 'No linter configured yet'", + "lint": "tsc --noEmit --strict && echo '✅ TypeScript strict mode check passed'", + "lint:check": "npm run type-check", + "validate": "npm run lint && npm run build", "test": "echo 'No tests configured yet'" }, "keywords": [], From 4f6c5aee4c1b7e76e7bd56b574f33a950f9be6ad Mon Sep 17 00:00:00 2001 From: firatyll Date: Tue, 15 Jul 2025 16:42:21 +0200 Subject: [PATCH 2/2] feat: enhance AI-generated pull request feature with error handling and fallback content --- .github/workflows/dev-to-main-pr.yml | 212 ++++++++++++++++++--------- 1 file changed, 142 insertions(+), 70 deletions(-) diff --git a/.github/workflows/dev-to-main-pr.yml b/.github/workflows/dev-to-main-pr.yml index c4b405a..587b2b4 100644 --- a/.github/workflows/dev-to-main-pr.yml +++ b/.github/workflows/dev-to-main-pr.yml @@ -119,70 +119,113 @@ jobs: COMMIT_AUTHOR=$(git log -1 --pretty=format:"%an" $COMMIT_SHA) COMMIT_DATE=$(git log -1 --pretty=format:"%ci" $COMMIT_SHA) - # Get detailed diff - GIT_DIFF=$(git diff HEAD~1 HEAD --unified=3 | head -c 4000) + # Get detailed diff (limited to avoid too large payload) + GIT_DIFF=$(git diff HEAD~1 HEAD --unified=3 | head -c 3000) # Get changed files with stats CHANGED_FILES=$(git diff --stat HEAD~1 HEAD) - # Create AI prompt - AI_PROMPT="You are a senior software engineer reviewing code changes for a Node.js/TypeScript candidate matching API. - - Generate a professional pull request description based on this information: - - **Commit Information:** - - Message: ${COMMIT_MSG} - - Author: ${COMMIT_AUTHOR} - - Date: ${COMMIT_DATE} - - SHA: ${COMMIT_SHA:0:7} + # Check if OpenAI API key is available + if [ -z "$OPENAI_API_KEY" ]; then + echo "⚠️ OpenAI API key not found, using fallback PR content" + PR_TITLE="🚀 Dev → Main: ${COMMIT_MSG}" + PR_BODY="**Summary:** ${COMMIT_MSG} + + **Code Quality Status:** + ${{ needs.code-quality-check.outputs.check-results }} - **Files Changed:** + **Changed Files:** ${CHANGED_FILES} - **Code Diff (truncated):** - ${GIT_DIFF} - - Please generate: - 1. A clear, professional PR title - 2. A comprehensive PR description with: - - Summary of changes - - Technical details - - Testing status - - Any breaking changes - - Code quality improvements - - Format the response as JSON with 'title' and 'body' fields. Make it professional and detailed." + **Auto-generated PR from dev branch** 🤖 - # Call OpenAI API - PR_CONTENT=$(curl -s -X POST "https://api.openai.com/v1/chat/completions" \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"model\": \"gpt-4\", - \"messages\": [ - { - \"role\": \"system\", - \"content\": \"You are a senior software engineer who writes excellent pull request descriptions. Always respond with valid JSON containing 'title' and 'body' fields.\" - }, - { - \"role\": \"user\", - \"content\": $(echo "$AI_PROMPT" | jq -R -s .) - } - ], - \"max_tokens\": 1000, - \"temperature\": 0.3 - }") - - echo "📝 AI Response received" - - # Extract title and body from AI response - PR_TITLE=$(echo "$PR_CONTENT" | jq -r '.choices[0].message.content' | jq -r '.title' 2>/dev/null || echo "🚀 Dev → Main: ${COMMIT_MSG}") - PR_BODY=$(echo "$PR_CONTENT" | jq -r '.choices[0].message.content' | jq -r '.body' 2>/dev/null || echo "**Summary:** ${COMMIT_MSG} + > Note: Add OPENAI_API_KEY secret for AI-generated descriptions" + else + echo "🔑 OpenAI API key found, generating AI content..." + + # Create AI prompt + AI_PROMPT="You are a senior software engineer reviewing code changes for a Node.js/TypeScript candidate matching API. + + Generate a professional pull request description based on this information: + + **Commit Information:** + - Message: ${COMMIT_MSG} + - Author: ${COMMIT_AUTHOR} + - Date: ${COMMIT_DATE} + - SHA: ${COMMIT_SHA:0:7} + + **Files Changed:** + ${CHANGED_FILES} + + **Code Diff (truncated):** + ${GIT_DIFF} + + Please generate: + 1. A clear, professional PR title + 2. A comprehensive PR description with: + - Summary of changes + - Technical details + - Testing status + - Any breaking changes + - Code quality improvements + + Format the response as JSON with 'title' and 'body' fields. Make it professional and detailed." + + # Call OpenAI API with error handling + PR_CONTENT=$(curl -s -w "%{http_code}" -X POST "https://api.openai.com/v1/chat/completions" \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"model\": \"gpt-4\", + \"messages\": [ + { + \"role\": \"system\", + \"content\": \"You are a senior software engineer who writes excellent pull request descriptions. Always respond with valid JSON containing 'title' and 'body' fields.\" + }, + { + \"role\": \"user\", + \"content\": $(echo "$AI_PROMPT" | jq -R -s .) + } + ], + \"max_tokens\": 1000, + \"temperature\": 0.3 + }") + + # Extract HTTP status code + HTTP_STATUS=$(echo "$PR_CONTENT" | tail -c 4) + PR_RESPONSE=$(echo "$PR_CONTENT" | head -c -4) + + echo "📝 OpenAI API Response Status: $HTTP_STATUS" + + if [ "$HTTP_STATUS" = "200" ]; then + echo "✅ AI Response received successfully" + # Extract title and body from AI response + AI_CONTENT=$(echo "$PR_RESPONSE" | jq -r '.choices[0].message.content' 2>/dev/null) + PR_TITLE=$(echo "$AI_CONTENT" | jq -r '.title' 2>/dev/null || echo "🚀 Dev → Main: ${COMMIT_MSG}") + PR_BODY=$(echo "$AI_CONTENT" | jq -r '.body' 2>/dev/null || echo "**Summary:** ${COMMIT_MSG} **Code Quality Status:** ${{ needs.code-quality-check.outputs.check-results }} **Auto-generated PR from dev branch** 🤖") + else + echo "❌ OpenAI API call failed with status $HTTP_STATUS" + echo "Response: $PR_RESPONSE" + # Fallback content + PR_TITLE="🚀 Dev → Main: ${COMMIT_MSG}" + PR_BODY="**Summary:** ${COMMIT_MSG} + + **Code Quality Status:** + ${{ needs.code-quality-check.outputs.check-results }} + + **Changed Files:** + ${CHANGED_FILES} + + **Auto-generated PR from dev branch** 🤖 + + > Note: AI generation failed, using fallback content" + fi + fi # Set outputs echo "title<> $GITHUB_OUTPUT @@ -195,27 +238,56 @@ jobs: - name: 🔄 Create Pull Request id: create-pr - uses: peter-evans/create-pull-request@v5 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: auto-pr-from-dev-${{ github.run_number }} - base: main - title: ${{ steps.generate-pr.outputs.title }} - body: | - ${{ steps.generate-pr.outputs.body }} - - --- + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "🔄 Creating pull request from dev to main..." + + # Check if PR already exists + EXISTING_PR=$(gh pr list --base main --head dev --json number --jq '.[0].number' 2>/dev/null || echo "") + + if [ -n "$EXISTING_PR" ]; then + echo "📝 Updating existing PR #$EXISTING_PR" + gh pr edit $EXISTING_PR \ + --title "${{ steps.generate-pr.outputs.title }}" \ + --body "${{ steps.generate-pr.outputs.body }} + + --- + + 🤖 **Auto-generated Pull Request** + - 📊 **Pipeline Run:** #${{ github.run_number }} + - 🔗 **Workflow:** [${{ github.workflow }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + - 📅 **Updated:** $(date -u) + - 👤 **Triggered by:** ${{ github.event.head_commit.author.name }} + + **Code Quality Checks:** + ${{ needs.code-quality-check.outputs.check-results }}" - 🤖 **Auto-generated Pull Request** - - 📊 **Pipeline Run:** #${{ github.run_number }} - - 🔗 **Workflow:** [${{ github.workflow }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - 📅 **Created:** ${{ github.event.head_commit.timestamp }} - - 👤 **Triggered by:** ${{ github.event.head_commit.author.name }} + echo "pull-request-number=$EXISTING_PR" >> $GITHUB_OUTPUT + echo "pull-request-url=$(gh pr view $EXISTING_PR --json url --jq '.url')" >> $GITHUB_OUTPUT + else + echo "🆕 Creating new pull request" + PR_URL=$(gh pr create \ + --base main \ + --head dev \ + --title "${{ steps.generate-pr.outputs.title }}" \ + --body "${{ steps.generate-pr.outputs.body }} + + --- + + 🤖 **Auto-generated Pull Request** + - 📊 **Pipeline Run:** #${{ github.run_number }} + - 🔗 **Workflow:** [${{ github.workflow }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + - 📅 **Created:** $(date -u) + - 👤 **Triggered by:** ${{ github.event.head_commit.author.name }} + + **Code Quality Checks:** + ${{ needs.code-quality-check.outputs.check-results }}") - **Code Quality Checks:** - ${{ needs.code-quality-check.outputs.check-results }} - draft: false - delete-branch: true + PR_NUMBER=$(echo "$PR_URL" | sed 's/.*\/pull\///') + echo "pull-request-number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "pull-request-url=$PR_URL" >> $GITHUB_OUTPUT + fi - name: 👥 Assign Reviewers if: steps.create-pr.outputs.pull-request-number