From c5fc20da6ef95dbb7b602cd7831f3388792278a2 Mon Sep 17 00:00:00 2001 From: William Hill Date: Sun, 22 Feb 2026 22:23:54 -0500 Subject: [PATCH] chore: add GitHub Actions CI/CD workflows (#83) --- .github/workflows/ci-dashboard.yml | 53 +++++++++++++++++++++++ .github/workflows/ci-python.yml | 44 +++++++++++++++++++ .github/workflows/deploy-preview.yml | 57 +++++++++++++++++++++++++ .github/workflows/deploy-production.yml | 47 ++++++++++++++++++++ .github/workflows/security-audit.yml | 50 ++++++++++++++++++++++ ruff.toml | 23 ++++++++++ 6 files changed, 274 insertions(+) create mode 100644 .github/workflows/ci-dashboard.yml create mode 100644 .github/workflows/ci-python.yml create mode 100644 .github/workflows/deploy-preview.yml create mode 100644 .github/workflows/deploy-production.yml create mode 100644 .github/workflows/security-audit.yml create mode 100644 ruff.toml diff --git a/.github/workflows/ci-dashboard.yml b/.github/workflows/ci-dashboard.yml new file mode 100644 index 0000000..dfcd6d3 --- /dev/null +++ b/.github/workflows/ci-dashboard.yml @@ -0,0 +1,53 @@ +name: Dashboard CI + +on: + push: + branches: [main, rebranding/bishop-state] + paths: + - "codebenders-dashboard/**" + - ".github/workflows/ci-dashboard.yml" + pull_request: + branches: [main, rebranding/bishop-state] + paths: + - "codebenders-dashboard/**" + - ".github/workflows/ci-dashboard.yml" + +defaults: + run: + working-directory: codebenders-dashboard + +jobs: + ci: + name: Type check · Lint · Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: codebenders-dashboard/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: TypeScript type check + run: npx tsc --noEmit + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build + env: + # Provide placeholder values so the build doesn't fail on missing env assertions. + # API routes and Supabase calls are opt-in at runtime; they are not executed during build. + NEXT_PUBLIC_SUPABASE_URL: https://placeholder.supabase.co + NEXT_PUBLIC_SUPABASE_ANON_KEY: placeholder-anon-key + DB_HOST: localhost + DB_PORT: 5432 + DB_USER: postgres + DB_PASSWORD: postgres + DB_NAME: postgres diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml new file mode 100644 index 0000000..f5defab --- /dev/null +++ b/.github/workflows/ci-python.yml @@ -0,0 +1,44 @@ +name: Python CI + +on: + push: + branches: [main, rebranding/bishop-state] + paths: + - "ai_model/**" + - "operations/**" + - "requirements.txt" + - ".github/workflows/ci-python.yml" + pull_request: + branches: [main, rebranding/bishop-state] + paths: + - "ai_model/**" + - "operations/**" + - "requirements.txt" + - ".github/workflows/ci-python.yml" + +jobs: + ci: + name: Lint · Deps · Syntax check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install ruff + run: pip install ruff + + - name: Lint (ruff) + run: ruff check ai_model/ operations/ + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Syntax check — entry points + run: | + python -m py_compile ai_model/complete_ml_pipeline.py + python -m py_compile operations/db_config.py diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml new file mode 100644 index 0000000..eb7674e --- /dev/null +++ b/.github/workflows/deploy-preview.yml @@ -0,0 +1,57 @@ +name: Deploy Preview + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + deploy: + name: Vercel preview deployment + runs-on: ubuntu-latest + # Only run when Vercel secrets are configured (skips forks / contributors without access) + if: ${{ vars.VERCEL_PROJECT_ID != '' }} + + permissions: + pull-requests: write # needed to post the preview URL comment + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel environment (preview) + run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + - name: Build + run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + - name: Deploy + id: deploy + run: | + url=$(vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}) + echo "url=$url" >> "$GITHUB_OUTPUT" + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + - name: Post preview URL to PR + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `## Preview deployment\n\n🚀 **${{ steps.deploy.outputs.url }}**\n\nDeployed from ${context.sha.slice(0, 7)}.`, + }) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml new file mode 100644 index 0000000..8f0c491 --- /dev/null +++ b/.github/workflows/deploy-production.yml @@ -0,0 +1,47 @@ +name: Deploy Production + +on: + push: + branches: [main] + +jobs: + deploy: + name: Vercel production deployment + runs-on: ubuntu-latest + # Only run when Vercel secrets are configured + if: ${{ vars.VERCEL_PROJECT_ID != '' }} + + environment: + name: production + url: ${{ steps.deploy.outputs.url }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Pull Vercel environment (production) + 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 + 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 + id: deploy + run: | + url=$(vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}) + echo "url=$url" >> "$GITHUB_OUTPUT" + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml new file mode 100644 index 0000000..9684534 --- /dev/null +++ b/.github/workflows/security-audit.yml @@ -0,0 +1,50 @@ +name: Security Audit + +on: + schedule: + - cron: "0 9 * * 1" # Every Monday at 09:00 UTC + push: + branches: [main] + workflow_dispatch: + +jobs: + audit-npm: + name: npm audit (dashboard) + runs-on: ubuntu-latest + + defaults: + run: + working-directory: codebenders-dashboard + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: codebenders-dashboard/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Audit + run: npm audit --audit-level=high + + audit-python: + name: pip-audit (ML pipeline) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install pip-audit + run: pip install pip-audit + + - name: Audit + run: pip-audit -r requirements.txt diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..edfbc47 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,23 @@ +# Ruff configuration for the ML pipeline and operations code. +# +# Rule selection is intentionally narrow to give a green CI baseline +# from the existing codebase. Expand select/remove ignores incrementally +# as the code is cleaned up. +# +# Selected rules: +# E9xx — syntax errors (always fatal) +# F — pyflakes: undefined names, bad imports, redefinitions +# +# Ignored rules (existing code baseline): +# F401 — imported but unused (common in ML notebooks-style scripts) +# F541 — f-string without placeholders (style, not a bug) +# F841 — local variable assigned but never used (acceptable in data scripts) + +[lint] +select = ["E9", "F"] + +ignore = [ + "F401", + "F541", + "F841", +]