From 70d8149ea1a6debaf4d63c7607aaf1b13a85d868 Mon Sep 17 00:00:00 2001 From: muhammadhammad2005 Date: Fri, 24 Apr 2026 21:33:22 +0500 Subject: [PATCH 1/3] updated everything --- .github/CODEOWNERS | 1 + .github/pull_request_template.md | 11 ++ .github/workflows/ci-cd.yml | 162 +++-------------- .github/workflows/release.yml | 287 +++++++++++++++++++++++++++++++ CICD_SETUP.md | 48 ------ CONTRIBUTING.md | 33 ++++ Dockerfile | 1 - README.md | 106 +++++++++--- logo/logo.html | 19 -- logo/logo.png | Bin 16066 -> 0 bytes scripts/validate-project.mjs | 20 ++- 11 files changed, 447 insertions(+), 241 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/release.yml delete mode 100644 CICD_SETUP.md create mode 100644 CONTRIBUTING.md delete mode 100644 logo/logo.html delete mode 100644 logo/logo.png diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..e6aea70 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @muhammadhammad2005 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..9f9dae8 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## Summary + +- describe what changed +- explain why this change is needed + +## Checklist + +- [ ] I created a branch for this work and did not push directly to `main` +- [ ] I ran local checks if needed +- [ ] I updated docs if behavior changed +- [ ] This change is ready for review diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 70b7685..bfed0d2 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,16 +1,24 @@ -name: Production CI/CD +name: Continuous Integration on: push: branches: - main + - develop + - feature/** + - fix/** + - hotfix/** + - chore/** + - refactor/** + tags-ignore: + - "v*.*.*" pull_request: branches: - main workflow_dispatch: concurrency: - group: production-cicd-${{ github.ref }} + group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: @@ -18,8 +26,6 @@ permissions: env: NODE_VERSION: "20" - IMAGE_NAME: resume-matcher - REGISTRY_IMAGE: ghcr.io/${{ github.repository }} jobs: quality: @@ -74,6 +80,11 @@ jobs: - name: Install dependencies run: npm ci + - name: Run gitleaks secret scan + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run npm audit run: npm audit --audit-level=high @@ -90,6 +101,7 @@ jobs: run: | echo "# Security Checks" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" + echo "- gitleaks secret scan executed" >> "$GITHUB_STEP_SUMMARY" echo "- npm audit executed at high severity threshold" >> "$GITHUB_STEP_SUMMARY" echo "- Snyk dependency scan executed" >> "$GITHUB_STEP_SUMMARY" @@ -128,132 +140,6 @@ jobs: if: always() run: docker rm -f resume-matcher-ci - docker-publish: - name: Publish Container Images - runs-on: ubuntu-latest - needs: - - docker-smoke - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - timeout-minutes: 20 - permissions: - contents: read - packages: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Generate image metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.REGISTRY_IMAGE }} - ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=sha,prefix=sha- - type=ref,event=branch - - - name: Build and push multi-platform images - uses: docker/build-push-action@v6 - with: - context: . - target: production - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Container publish summary - if: always() - run: | - echo "# Container Publish" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "- GHCR image: \`${{ env.REGISTRY_IMAGE }}\`" >> "$GITHUB_STEP_SUMMARY" - echo "- Docker Hub image: \`${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}\`" >> "$GITHUB_STEP_SUMMARY" - echo "- Tags: ${{ steps.meta.outputs.tags }}" >> "$GITHUB_STEP_SUMMARY" - - deploy-vercel: - name: Deploy to Vercel Production - runs-on: ubuntu-latest - needs: - - quality - - security - - docker-publish - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - timeout-minutes: 15 - environment: - name: production - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Install Vercel CLI - run: npm install --global vercel@latest - - - name: Pull Vercel environment - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - - - name: Build Vercel output - run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - - - name: Deploy prebuilt output - id: deploy - run: | - deployment_url="$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})" - echo "deployment_url=$deployment_url" >> "$GITHUB_OUTPUT" - echo "Deployment URL: $deployment_url" - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - - - name: Verify production deployment - run: | - deployment_url="${{ steps.deploy.outputs.deployment_url }}" - vercel curl --deployment "$deployment_url" / --token=${{ secrets.VERCEL_TOKEN }} > /dev/null - vercel curl --deployment "$deployment_url" /health --token=${{ secrets.VERCEL_TOKEN }} > /dev/null - - - name: Deployment summary - if: always() - run: | - echo "# Vercel Deployment" >> "$GITHUB_STEP_SUMMARY" - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "- Production URL: ${{ steps.deploy.outputs.deployment_url }}" >> "$GITHUB_STEP_SUMMARY" - echo "- Deployment strategy: CLI prebuilt deploy after successful pipeline gates" >> "$GITHUB_STEP_SUMMARY" - notify: name: Notification Stage runs-on: ubuntu-latest @@ -264,8 +150,6 @@ jobs: - quality - security - docker-smoke - - docker-publish - - deploy-vercel if: always() steps: @@ -274,14 +158,12 @@ jobs: run: | if [[ "${{ needs.quality.result }}" == "success" && \ "${{ needs.security.result }}" == "success" && \ - "${{ needs.docker-smoke.result }}" == "success" && \ - ( "${{ needs.docker-publish.result }}" == "success" || "${{ needs.docker-publish.result }}" == "skipped" ) && \ - ( "${{ needs.deploy-vercel.result }}" == "success" || "${{ needs.deploy-vercel.result }}" == "skipped" ) ]]; then + "${{ needs.docker-smoke.result }}" == "success" ]]; then echo "state=success" >> "$GITHUB_OUTPUT" - echo "message=Pipeline completed successfully." >> "$GITHUB_OUTPUT" + echo "message=CI pipeline completed successfully. No production deployment was triggered." >> "$GITHUB_OUTPUT" else echo "state=failure" >> "$GITHUB_OUTPUT" - echo "message=Pipeline failed. Review the workflow logs before deploying again." >> "$GITHUB_OUTPUT" + echo "message=CI pipeline failed. Review the workflow logs before merging." >> "$GITHUB_OUTPUT" fi - name: Send pipeline email notification @@ -292,7 +174,7 @@ jobs: server_port: 587 username: ${{ env.SMTP_USER }} password: ${{ env.SMTP_PASS }} - subject: "[${{ steps.status.outputs.state }}] ${{ github.repository }} CI/CD" + subject: "[${{ steps.status.outputs.state }}] ${{ github.repository }} CI" to: ${{ env.SMTP_USER }} from: GitHub Actions <${{ env.SMTP_USER }}> body: | @@ -303,8 +185,6 @@ jobs: Quality Gate: ${{ needs.quality.result }} Security Checks: ${{ needs.security.result }} Docker Smoke Test: ${{ needs.docker-smoke.result }} - Docker Publish: ${{ needs.docker-publish.result }} - Vercel Deploy: ${{ needs.deploy-vercel.result }} ${{ steps.status.outputs.message }} Run URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -317,7 +197,5 @@ jobs: echo "- Quality Gate: ${{ needs.quality.result }}" >> "$GITHUB_STEP_SUMMARY" echo "- Security Checks: ${{ needs.security.result }}" >> "$GITHUB_STEP_SUMMARY" echo "- Docker Smoke Test: ${{ needs.docker-smoke.result }}" >> "$GITHUB_STEP_SUMMARY" - echo "- Docker Publish: ${{ needs.docker-publish.result }}" >> "$GITHUB_STEP_SUMMARY" - echo "- Vercel Deploy: ${{ needs.deploy-vercel.result }}" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY" echo "${{ steps.status.outputs.message }}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6a57cee --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,287 @@ +name: Production Release + +on: + push: + tags: + - "v*.*.*" + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + packages: write + +env: + NODE_VERSION: "20" + IMAGE_NAME: resume-matcher + REGISTRY_IMAGE: ghcr.io/${{ github.repository }} + +jobs: + release-quality: + name: Release Quality Gate + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout release code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Validate release build + run: npm run ci:validate + + release-security: + name: Release Security Checks + runs-on: ubuntu-latest + needs: release-quality + timeout-minutes: 15 + + steps: + - name: Checkout release code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run gitleaks secret scan + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run npm audit + run: npm audit --audit-level=high + + - name: Run Snyk dependency scan + uses: snyk/actions/node@v1.0.0 + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + with: + command: test + args: --severity-threshold=high --all-projects + + release-docker-smoke: + name: Release Docker Smoke Test + runs-on: ubuntu-latest + needs: + - release-quality + - release-security + timeout-minutes: 15 + + steps: + - name: Checkout release code + uses: actions/checkout@v4 + + - name: Build production container + run: docker build --target production --tag resume-matcher:release . + + - name: Run container + run: docker run -d --name resume-matcher-release -p 8080:8080 resume-matcher:release + + - name: Verify container health endpoint + run: | + for attempt in {1..15}; do + if curl -fsS http://127.0.0.1:8080/health > /dev/null; then + echo "Release container health endpoint is reachable." + exit 0 + fi + sleep 2 + done + echo "Release container health endpoint did not become ready in time." + docker logs resume-matcher-release + exit 1 + + - name: Stop container + if: always() + run: docker rm -f resume-matcher-release + + docker-publish: + name: Publish Release Images + runs-on: ubuntu-latest + needs: + - release-docker-smoke + timeout-minutes: 20 + + steps: + - name: Checkout release code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Generate image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY_IMAGE }} + ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ github.ref_name }} + type=raw,value=latest + + - name: Build and push multi-platform images + uses: docker/build-push-action@v6 + with: + context: . + target: production + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + deploy-vercel: + name: Deploy Release to Vercel Production + runs-on: ubuntu-latest + needs: + - docker-publish + timeout-minutes: 15 + environment: + name: production + + steps: + - name: Checkout release code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel environment + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + - name: Build Vercel output + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + - name: Deploy prebuilt output + id: deploy + run: | + deployment_url="$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }})" + echo "deployment_url=$deployment_url" >> "$GITHUB_OUTPUT" + echo "Deployment URL: $deployment_url" + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + - name: Verify production deployment + run: | + deployment_url="${{ steps.deploy.outputs.deployment_url }}" + vercel curl --deployment "$deployment_url" / --token=${{ secrets.VERCEL_TOKEN }} > /dev/null + vercel curl --deployment "$deployment_url" /health --token=${{ secrets.VERCEL_TOKEN }} > /dev/null + + create-release: + name: Publish GitHub Release + runs-on: ubuntu-latest + needs: + - deploy-vercel + steps: + - name: Create GitHub release entry + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + generate_release_notes: true + name: ${{ github.ref_name }} + body: | + Production release for MatchlyPro. + + Container images and Vercel production deployment were published successfully for this version. + + notify: + name: Release Notification Stage + runs-on: ubuntu-latest + env: + SMTP_USER: ${{ secrets.SMTP_USER }} + SMTP_PASS: ${{ secrets.SMTP_PASS }} + needs: + - release-quality + - release-security + - release-docker-smoke + - docker-publish + - deploy-vercel + - create-release + if: always() + + steps: + - name: Compute release notification status + id: status + run: | + if [[ "${{ needs.release-quality.result }}" == "success" && \ + "${{ needs.release-security.result }}" == "success" && \ + "${{ needs.release-docker-smoke.result }}" == "success" && \ + "${{ needs.docker-publish.result }}" == "success" && \ + "${{ needs.deploy-vercel.result }}" == "success" && \ + "${{ needs.create-release.result }}" == "success" ]]; then + echo "state=success" >> "$GITHUB_OUTPUT" + echo "message=Release pipeline completed successfully." >> "$GITHUB_OUTPUT" + else + echo "state=failure" >> "$GITHUB_OUTPUT" + echo "message=Release pipeline failed. Production promotion stopped." >> "$GITHUB_OUTPUT" + fi + + - name: Send release email notification + if: always() && env.SMTP_USER != '' && env.SMTP_PASS != '' + uses: dawidd6/action-send-mail@v3 + with: + server_address: smtp.gmail.com + server_port: 587 + username: ${{ env.SMTP_USER }} + password: ${{ env.SMTP_PASS }} + subject: "[${{ steps.status.outputs.state }}] ${{ github.repository }} release ${{ github.ref_name }}" + to: ${{ env.SMTP_USER }} + from: GitHub Actions <${{ env.SMTP_USER }}> + body: | + Repository: ${{ github.repository }} + Release Tag: ${{ github.ref_name }} + Commit: ${{ github.sha }} + + Release Quality Gate: ${{ needs.release-quality.result }} + Release Security Checks: ${{ needs.release-security.result }} + Release Docker Smoke Test: ${{ needs.release-docker-smoke.result }} + Publish Release Images: ${{ needs.docker-publish.result }} + Vercel Production Deploy: ${{ needs.deploy-vercel.result }} + GitHub Release Publish: ${{ needs.create-release.result }} + + ${{ steps.status.outputs.message }} + Run URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/CICD_SETUP.md b/CICD_SETUP.md deleted file mode 100644 index 5bcae9d..0000000 --- a/CICD_SETUP.md +++ /dev/null @@ -1,48 +0,0 @@ -# Production CI/CD Setup - -This project now uses a gated production pipeline: - -`Quality Gate -> Security Checks -> Docker Smoke Test -> Docker Publish -> Vercel Production Deploy -> Notification Stage` - -The important behavior is: - -- `main` par push hote hi Vercel khud se direct deploy nahi karega -- deployment sirf GitHub Actions pipeline ke successful hone ke baad hogi -- Vercel deploy `vercel deploy --prebuilt --prod` se hota hai -- Docker image pehle locally smoke-test hoti hai, phir GHCR aur Docker Hub par push hoti hai - -## Required Repository Secrets - -Ye secrets workflow use karta hai: - -- `DOCKERHUB_USERNAME` -- `DOCKERHUB_TOKEN` -- `SNYK_TOKEN` -- `VERCEL_TOKEN` -- `VERCEL_ORG_ID` -- `VERCEL_PROJECT_ID` -- `SMTP_USER` -- `SMTP_PASS` - -## What Changed - -- `vercel.json` me `git.deploymentEnabled: false` add kiya gaya hai taa ke Vercel Git auto-deploy band rahe -- workflow ko production-style gated jobs me split kiya gaya hai -- fake/no-op test commands ki jagah real validation aur smoke checks add kiye gaye hain -- duplicate validation scripts remove karke Node-based reusable scripts add kiye gaye hain -- Dockerfile ko static-site optimized banaya gaya hai - -## Local Validation - -Local machine par ye commands chala sakte ho: - -```bash -npm ci -npm run ci:validate -docker build --target production -t resume-matcher:local . -docker run --rm -p 8080:8080 resume-matcher:local -``` - -## Vercel Note - -`vercel.json` auto deployments ko disable karta hai. Agar aapka Vercel project abhi bhi connected Git integration ke through deploy kar raha ho, to Vercel dashboard me project settings -> Git me ja kar verify kar lo ke automatic Git deployments off hon. CLI-based production deploy workflow already configured hai. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f2d134f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +## Branch Strategy + +- `main` is the production-ready branch +- make changes in a feature branch such as `feature/...`, `fix/...`, or `chore/...` +- open a pull request into `main` +- wait for CI to pass +- get review before merge + +## Deployment Policy + +- branch pushes and pull requests run CI only +- production deployment does not happen from normal code pushes +- production deployment happens only from a version tag such as `v1.0.0` +- version tags are intended to be created only after approved code is already merged into `main` + +## Release Flow + +1. Merge approved changes into `main` +2. Create a version tag like `v1.0.0` +3. Push the tag +4. The release workflow publishes container images, deploys to Vercel production, and creates the GitHub Release entry + +## Recommended GitHub Settings + +For full enforcement, enable these repository rules in GitHub Settings: + +- require a pull request before merging into `main` +- require status checks to pass before merging +- require review from code owners +- block force pushes to `main` +- restrict who can push directly to `main` diff --git a/Dockerfile b/Dockerfile index 2c94e26..04fb80a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,6 @@ RUN apk add --no-cache curl COPY nginx.conf /etc/nginx/nginx.conf COPY index.html /usr/share/nginx/html/index.html COPY health.html /usr/share/nginx/html/health.html -COPY logo /usr/share/nginx/html/logo RUN chown -R nginx:nginx /usr/share/nginx/html /var/cache/nginx /var/log/nginx /etc/nginx/conf.d \ && touch /var/run/nginx.pid \ diff --git a/README.md b/README.md index 7ab2d5e..22d9449 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,8 @@ This project is part of my portfolio and showcases end-to-end ownership: - Docker containerization with Nginx - production deployment on Vercel - CI/CD automation with GitHub Actions -- security checks with `npm audit` and Snyk +- security checks with `npm audit`, gitleaks, and Snyk +- release-based production promotion ## Live Project @@ -36,10 +37,11 @@ This is not just a frontend page. It is a complete portfolio project where I han - built the tool myself - deployed it to Vercel myself - containerized it with Docker myself -- configured production CI/CD myself -- integrated security scanning and release gates myself +- configured CI myself +- configured release automation myself +- controlled production promotion through tags/releases myself -If you are reviewing this repository for hiring or portfolio purposes, the repo demonstrates practical ownership across development, deployment, and operations. +If you are reviewing this repository for hiring or portfolio purposes, the repo demonstrates practical ownership across development, deployment, release management, and operations. ## Tech Stack @@ -51,8 +53,10 @@ If you are reviewing this repository for hiring or portfolio purposes, the repo - Docker - Docker Hub - GitHub Actions +- GitHub Releases - Vercel - Snyk +- gitleaks ## Application Highlights @@ -62,7 +66,7 @@ If you are reviewing this repository for hiring or portfolio purposes, the repo - weighted scoring logic - section-aware analysis - role-family insights -- risk detection for weak resume coverage +- ATS-oriented risk detection ### Frontend experience @@ -71,7 +75,7 @@ If you are reviewing this repository for hiring or portfolio purposes, the repo - drag-and-drop PDF upload - live character counters - session history using local storage -- copy/export/save utilities +- copy, export, and save utilities ### Privacy-first approach @@ -80,31 +84,64 @@ If you are reviewing this repository for hiring or portfolio purposes, the repo - no database - no user account requirement -## DevOps And Production Setup +## DevOps And Release Model -The project includes a real CI/CD flow instead of a basic demo pipeline. +The project uses a two-stage workflow that separates CI from production release. -### Current pipeline +### CI pipeline -`Quality Gate -> Security Checks -> Docker Smoke Test -> Publish Container Images -> Deploy to Vercel Production -> Notification Stage` +`Quality Gate -> Security Checks -> Docker Smoke Test -> Notification Stage` -### What the pipeline does +What CI does: +- runs on branches and pull requests - installs dependencies with `npm ci` - validates project structure - runs static smoke checks +- runs gitleaks - runs `npm audit` - runs a Snyk dependency scan - builds and smoke-tests the production Docker image -- publishes container images to GHCR and Docker Hub -- deploys to Vercel only after earlier gates succeed -- sends a notification stage summary at the end + +### Release pipeline + +`Release Quality Gate -> Release Security Checks -> Release Docker Smoke Test -> Publish Release Images -> Deploy Release to Vercel Production -> Publish GitHub Release -> Release Notification Stage` + +What release does: + +- runs only when a version tag such as `v1.0.0` is pushed +- publishes release images to GHCR and Docker Hub +- deploys the tagged version to Vercel production +- creates a GitHub Release entry automatically ### Deployment rule Vercel does not auto-deploy directly from Git pushes for this project. -The repository uses CLI-based deployment after pipeline success, and `vercel.json` disables automatic Git deployments for tighter release control. +The repository uses CLI-based deployment, and `vercel.json` disables automatic Git deployments for tighter release control. + +Normal pushes and pull requests never deploy production. + +Production deployment happens only when I create and push a version tag like `v1.0.0`. + +## Branch And Review Workflow + +The intended workflow is: + +1. Create a branch such as `feature/...`, `fix/...`, or `chore/...` +2. Open a pull request into `main` +3. Let CI pass +4. Review the changes +5. Merge into `main` +6. Create a release tag when the version is ready for production + +Repo-side controls included here: + +- `.github/CODEOWNERS` +- `.github/pull_request_template.md` +- `CONTRIBUTING.md` + +For strict PR-only enforcement, GitHub branch protection should also be enabled in repository settings. ## Containerization @@ -148,22 +185,39 @@ This runs: - project validation - static smoke testing +## Release Usage + +To publish a real production release: + +```bash +git checkout main +git pull origin main +git tag v1.0.0 +git push origin v1.0.0 +``` + +That tag will trigger the release workflow, publish the production deployment, and create the GitHub Release entry. + ## Project Structure ```text . +├─ .github/CODEOWNERS +├─ .github/pull_request_template.md ├─ .github/workflows/ci-cd.yml +├─ .github/workflows/release.yml +├─ CONTRIBUTING.md ├─ Dockerfile ├─ docker-compose.yml +├─ LICENSE ├─ nginx.conf -├─ vercel.json ├─ package.json +├─ vercel.json ├─ index.html ├─ health.html -├─ scripts/ -│ ├─ smoke-static-site.mjs -│ └─ validate-project.mjs -└─ logo/ +└─ scripts/ + ├─ smoke-static-site.mjs + └─ validate-project.mjs ``` ## Vercel Note @@ -183,10 +237,11 @@ Relevant references: This repository is in a strong portfolio-ready state for a static production app: - working production deployment -- gated CI/CD pipeline +- CI separated from release promotion +- release-based production deployment +- PR review ownership with CODEOWNERS - containerized runtime - security scanning integrated -- release flow separated from direct Vercel Git auto-deploy - health checks for container and deployment verification - clean documentation for reviewers @@ -195,18 +250,19 @@ This repository is in a strong portfolio-ready state for a static production app - there is no backend or persistent database because the tool is intentionally browser-first - screenshot availability inside Vercel dashboard is a platform-side generated-URL behavior, not an application outage - local Docker verification depends on Docker Engine being available on the machine running the checks +- GitHub branch protection still needs to be enabled in repository settings for strict PR-only enforcement ## Future Improvements - custom domain -- Lighthouse performance report badge +- Lighthouse performance reporting - automated accessibility audit in CI - Playwright end-to-end tests - Dependabot for dependency maintenance -- versioned releases and changelog +- merge queue and stricter branch protection rules ## Author Muhammad Hammad -Built, containerized, deployed, and automated by me as a portfolio project to demonstrate product engineering plus practical DevOps ownership. +Built, containerized, deployed, released, and automated by me as a portfolio project to demonstrate product engineering plus practical DevOps ownership. diff --git a/logo/logo.html b/logo/logo.html deleted file mode 100644 index ec642d6..0000000 --- a/logo/logo.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/logo/logo.png b/logo/logo.png deleted file mode 100644 index fb08516fe6799c40f9cfb34df5b400efed5f99ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16066 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
)Fc?rV1ZYq#!KMAA8Fi zH;IR?rYWTEcuUZ8lUjZAhkeEvP$yN`J*dw?5w2o@)dtxw{*IEz&Gh>)dj zM5d2<;g1sPU6ElTyW#(H4te)%Y)>DRg17##I8IZYJfKFx*No9OGQzU9F832HKl9ez zK8uqdi(kgG%Jpc}EJ(p4u>b!%$Ha4Xno_9zikP2Jx*9j}aCJSoa=6+f-ccMF4C4*3 zlZ}4*w4@&wz6x9AS route.src === "/health"), "vercel.json must expose /health."); -assert(workflow.includes("deploy-vercel"), "Workflow must include a deploy-vercel job."); -assert(workflow.includes("docker-publish"), "Workflow must include a docker-publish job."); -assert(workflow.includes("SNYK_TOKEN"), "Workflow must reference the SNYK token."); -assert(workflow.includes("DOCKERHUB_USERNAME"), "Workflow must reference Docker Hub credentials."); -assert(workflow.includes("vercel deploy --prebuilt --prod"), "Workflow must use prebuilt Vercel deployments."); +assert(ciWorkflow.includes("Notification Stage"), "CI workflow must include a notification stage."); +assert(ciWorkflow.includes("gitleaks"), "CI workflow must include gitleaks scanning."); +assert(ciWorkflow.includes("SNYK_TOKEN"), "CI workflow must reference the SNYK token."); + +assert(releaseWorkflow.includes("docker-publish"), "Release workflow must include a docker-publish job."); +assert(releaseWorkflow.includes("deploy-vercel"), "Release workflow must include a deploy-vercel job."); +assert(releaseWorkflow.includes("softprops/action-gh-release"), "Release workflow must create a GitHub release."); +assert(releaseWorkflow.includes("DOCKERHUB_USERNAME"), "Release workflow must reference Docker Hub credentials."); +assert(releaseWorkflow.includes("vercel deploy --prebuilt --prod"), "Release workflow must use prebuilt Vercel deployments."); +assert(releaseWorkflow.includes('v*.*.*'), "Release workflow must trigger from version tags."); assert(indexHtml.includes(""), "index.html must contain a title tag."); assert(indexHtml.toLowerCase().includes("resume"), "index.html should still describe the resume matcher app."); From fc45d085d0de30f6abd25083588384122b46bfda Mon Sep 17 00:00:00 2001 From: muhammadhammad2005 <youyt.manager@gmail.com> Date: Fri, 24 Apr 2026 21:41:50 +0500 Subject: [PATCH 2/3] fix: stabilize gitleaks config for PR checks --- .gitleaks.toml | 85 +++++++++----------------------------------------- 1 file changed, 15 insertions(+), 70 deletions(-) diff --git a/.gitleaks.toml b/.gitleaks.toml index c11f6a6..a035dd4 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -1,81 +1,26 @@ -# Gitleaks configuration for Resume Matcher project -title = "Resume Matcher Secret Detection" -version = "2.0.0" +title = "MatchlyPro Secret Detection" -[[rules]] -id = "generic-api-key" -description = "Generic API Key" -regex = '''(?i)(?:api[_-]?key|apikey|secret)[\s]*[=:][\s]*["']?([a-z0-9_\-]{20,60})["']?''' -tags = ["key", "API"] - -[[rules]] -id = "github-pat" -description = "GitHub Personal Access Token" -regex = '''(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36}''' -tags = ["key", "GitHub"] - -[[rules]] -id = "aws-access-key-id" -description = "AWS Access Key ID" -regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}''' -tags = ["key", "AWS"] - -[[rules]] -id = "aws-secret-access-key" -description = "AWS Secret Access Key" -regex = '''(?i)aws[_-]?secret[_-]?access[_-]?key[\s]*[=:][\s]*["']?([a-z0-9/+]{40})["']?''' -tags = ["key", "AWS"] - -[[rules]] -id = "slack-token" -description = "Slack Token" -regex = '''(xox[pboa]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})''' -tags = ["key", "Slack"] +[extend] +useDefault = true [[rules]] id = "vercel-api-token" description = "Vercel API Token" -regex = '''[a-z0-9]{24}_[a-z0-9]{27}''' +regex = '''\b[a-z0-9]{24}_[a-z0-9]{27}\b''' tags = ["key", "Vercel"] -[[rules]] -id = "npm-access-token" -description = "NPM Access Token" -regex = '''npm_[a-zA-Z0-9]{36}''' -tags = ["key", "NPM"] - -[[rules]] -id = "stripe-api-key" -description = "Stripe API Key" -regex = '''(sk|pk)_(test|live)_[0-9a-zA-Z]{24,99}''' -tags = ["key", "Stripe"] - -[[rules]] -id = "email-address" -description = "Email Address (for notification purposes - allowlisted)" -regex = '''[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}''' -tags = ["email"] - -# Allowlist specific patterns -[[allowlist]] -description = "Allowlisted email for notifications" -regexes = [ - "muhammadhammad\\.020050@gmail\\.com" -] - -[[allowlist]] -description = "Example/test patterns" +[[allowlists]] +description = "Ignore gitleaks config and markdown documentation" paths = [ - "test/", - "examples/", - "*.test.*", - "*.spec.*" + '''^\.gitleaks\.toml$''', + '''^README\.md$''', + '''^CONTRIBUTING\.md$''', + '''^LICENSE$''' ] -[[allowlist]] -description = "Documentation files" +[[allowlists]] +description = "Ignore lockfiles and generated metadata that may trigger false positives" paths = [ - "README.md", - "*.txt", - "*.md" -] \ No newline at end of file + '''^package-lock\.json$''', + '''^node_modules/.*''' +] From 2553e44f75f24b97af20bffe39961c02a6476206 Mon Sep 17 00:00:00 2001 From: muhammadhammad2005 <youyt.manager@gmail.com> Date: Fri, 24 Apr 2026 21:44:53 +0500 Subject: [PATCH 3/3] fix: fetch full git history for security scans --- .github/workflows/ci-cd.yml | 4 ++++ .github/workflows/release.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index bfed0d2..1900942 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -36,6 +36,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -70,6 +72,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a57cee..d0e28ee 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,8 @@ jobs: steps: - name: Checkout release code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 @@ -49,6 +51,8 @@ jobs: steps: - name: Checkout release code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4