diff --git a/.github/workflows/ci-frontend.yml b/.github/workflows/ci-frontend.yml new file mode 100644 index 0000000..c8350c9 --- /dev/null +++ b/.github/workflows/ci-frontend.yml @@ -0,0 +1,38 @@ +name: Frontend CI + +on: + push: + branches: [dev] + paths: + - 'frontend/**' + pull_request: + branches: [dev, master] + paths: + - 'frontend/**' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Type check + run: npx tsc --noEmit + working-directory: frontend + + - name: Build + run: npm run build + working-directory: frontend + env: + GITHUB_PAGES: true diff --git a/.github/workflows/kshield-ci.yml b/.github/workflows/ci.yml similarity index 66% rename from .github/workflows/kshield-ci.yml rename to .github/workflows/ci.yml index aaaae62..8ab973e 100644 --- a/.github/workflows/kshield-ci.yml +++ b/.github/workflows/ci.yml @@ -1,27 +1,30 @@ -name: KShield CI +name: CI on: push: - branches: [main, develop] + branches: [dev] pull_request: - branches: [main, develop] + branches: [dev, master] jobs: - # ── Rust CLI ──────────────────────────────────────────────────────────────── + # ── Rust CLI ───────────────────────────────────────────────────────────────── cli: name: CLI · build & test runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - name: Build CLI + - uses: Swatinem/rust-cache@v2 + with: + workspaces: cli + - name: Build run: cargo build --release working-directory: cli - - name: Cargo test + - name: Test run: cargo test working-directory: cli - # ── Python backend ────────────────────────────────────────────────────────── + # ── Python backend ──────────────────────────────────────────────────────────── backend: name: Backend · lint & test runs-on: ubuntu-22.04 @@ -33,7 +36,7 @@ jobs: - name: Install dependencies run: pip install -r requirements.txt working-directory: backend - - name: Import check (all modules) + - name: Import check run: | SQLITE_FALLBACK=true python -c " from app.main import app @@ -48,27 +51,32 @@ jobs: run: SQLITE_FALLBACK=true python -m pytest tests/ -v || echo "No tests yet" working-directory: backend - # ── Frontend ───────────────────────────────────────────────────────────────── + # ── Frontend ────────────────────────────────────────────────────────────────── frontend: - name: Frontend · build + name: Frontend · type check & build runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: "20" - cache: "npm" + node-version: 20 + cache: npm cache-dependency-path: frontend/package-lock.json - - name: Install - run: npm install + - name: Install dependencies + run: npm ci + working-directory: frontend + - name: Type check + run: npx tsc --noEmit working-directory: frontend - name: Build run: npm run build working-directory: frontend + env: + GITHUB_PAGES: true - # ── PR scan (on pull requests only) ───────────────────────────────────────── + # ── PR scan (pull requests only) ────────────────────────────────────────────── pr-scan: - name: Scan changed files + name: KShield · scan changed files runs-on: ubuntu-22.04 if: github.event_name == 'pull_request' needs: [backend] @@ -76,67 +84,65 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-python@v5 with: python-version: "3.11" - - name: Install backend run: pip install -r requirements.txt working-directory: backend - - - name: Start backend (SQLite mode) + - name: Start backend run: | SQLITE_FALLBACK=true uvicorn app.main:app --host 127.0.0.1 --port 8000 & echo $! > /tmp/backend.pid - for i in $(seq 1 20); do - curl -sf http://127.0.0.1:8000/health && break || sleep 1 - done + for i in $(seq 1 20); do curl -sf http://127.0.0.1:8000/health && break || sleep 1; done working-directory: backend - - name: Get changed files - id: changed run: | git diff --name-only origin/${{ github.base_ref }}...HEAD \ --diff-filter=ACM > /tmp/changed_files.txt cat /tmp/changed_files.txt - - - name: Scan each changed file + - name: Scan changed files id: scan run: | - FINDINGS="[]" + # Accumulate flat list of {file, anomaly} objects + ALL="[]" while IFS= read -r file; do [ -f "$file" ] || continue - CONTENT=$(cat "$file" | jq -Rs .) + CONTENT=$(jq -Rs . < "$file") RESULT=$(curl -sf -X POST http://127.0.0.1:8000/api/v1/scan \ -H "Content-Type: application/json" \ - -d "{\"filename\": \"$file\", \"content\": $CONTENT}" || echo '{"anomalies":[]}') - FINDINGS=$(echo "$FINDINGS $RESULT" | jq -s 'add') + -d "{\"filename\": \"$file\", \"content\": $CONTENT}" \ + || echo '{"anomalies":[]}') + # Extract anomalies and tag each with its filename + FILE_FINDINGS=$(echo "$RESULT" | jq -c \ + --arg f "$file" \ + '[.anomalies // [] | .[] | {file: $f, anomaly: .}]') + ALL=$(echo "$ALL $FILE_FINDINGS" | jq -s 'add // []') done < /tmp/changed_files.txt - echo "findings=$(echo $FINDINGS | jq -c .)" >> $GITHUB_OUTPUT - - - name: Post PR review comments + echo "findings=$(echo "$ALL" | jq -c .)" >> $GITHUB_OUTPUT + - name: Post review comments uses: actions/github-script@v7 with: script: | - const findingsRaw = `${{ steps.scan.outputs.findings }}`; + const raw = `${{ steps.scan.outputs.findings }}`; let findings; - try { findings = JSON.parse(findingsRaw); } catch { findings = []; } - const comments = (findings.anomalies || []).map(f => ({ - path: findings.filename || "unknown", - line: f.line || 1, - body: `**${f.severity}** — ${f.type}\n\n${f.description}\n\n> ${f.remediation?.explanation || ""}` - })).filter(c => c.path !== "unknown"); + try { findings = JSON.parse(raw); } catch { findings = []; } + const comments = findings + .map(({ file, anomaly: f }) => ({ + path: file, + line: f.line || 1, + body: `**${f.severity}** — ${f.type}\n\n${f.description}\n\n> ${f.remediation?.explanation || ''}`.trim(), + })) + .filter(c => c.path); if (comments.length > 0) { await github.rest.pulls.createReview({ owner: context.repo.owner, repo: context.repo.repo, pull_number: context.issue.number, - event: "COMMENT", + event: 'COMMENT', comments, }); } - - name: Stop backend if: always() run: kill $(cat /tmp/backend.pid) 2>/dev/null || true diff --git a/frontend/src/design-system/components/Badge.tsx b/frontend/src/design-system/components/Badge.tsx index 65e3864..c7067c6 100644 --- a/frontend/src/design-system/components/Badge.tsx +++ b/frontend/src/design-system/components/Badge.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - type Severity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'; type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; type Status = 'safe' | 'danger' | 'warning' | 'neutral'; diff --git a/frontend/src/design-system/components/Icons.tsx b/frontend/src/design-system/components/Icons.tsx index 8f9682b..93d753a 100644 --- a/frontend/src/design-system/components/Icons.tsx +++ b/frontend/src/design-system/components/Icons.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - interface IconProps { size?: number; className?: string; diff --git a/frontend/src/design-system/components/StatusDot.tsx b/frontend/src/design-system/components/StatusDot.tsx index 08a74a4..e96cc78 100644 --- a/frontend/src/design-system/components/StatusDot.tsx +++ b/frontend/src/design-system/components/StatusDot.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - type DotStatus = 'active' | 'idle' | 'error' | 'warning'; const DOT_CLS: Record = {