docling-studio/profiles/fastapi-vue/commands.sh
Pier-Jean Malandrino 941028e705 ci: add release-gate pipeline, fix CI duplication, add OSS playbook docs
- Add release-gate.yml: 11-job pipeline (lint, tests, Docker build/smoke,
  Trivy scan, dep audit, audit checks, e2e API+UI, PR summary comment)
- Fix CI double-trigger on release branches (push + PR)
- Add CODE_OF_CONDUCT.md, SECURITY.md, PR template
- Add docs: git-workflow, architecture (ADR), release, operations, community
- Add profiles/fastapi-vue for automated audit checks
- Add docs/PROCESSES.md: index of 19 available processes
2026-04-09 13:20:43 +02:00

267 lines
9.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# ============================================================================
# Docling Studio — Automated Audit Checks (FastAPI + Vue 3 profile)
# ============================================================================
# Runs verification commands for each of the 12 release audits.
# Usage: bash profiles/fastapi-vue/commands.sh
# Run from the repository root.
# ============================================================================
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
PASS=0
WARN=0
FAIL=0
pass() { echo -e " ${GREEN}PASS${NC} $1"; ((PASS++)); }
warn() { echo -e " ${YELLOW}WARN${NC} $1"; ((WARN++)); }
fail() { echo -e " ${RED}FAIL${NC} $1"; ((FAIL++)); }
# ── 01. Clean Architecture ─────────────────────────────────────────────────
echo ""
echo "== 01. Clean Architecture =="
# Domain must not import from api, persistence, infra
if grep -rn "from api\|from persistence\|from infra\|import fastapi\|import aiosqlite" document-parser/domain/ 2>/dev/null; then
fail "Domain layer imports forbidden modules"
else
pass "Domain layer has no forbidden imports"
fi
# API must not import from persistence directly
if grep -rn "from persistence" document-parser/api/ 2>/dev/null; then
fail "API layer imports directly from persistence"
else
pass "API layer does not import from persistence"
fi
# ── 02. DDD ────────────────────────────────────────────────────────────────
echo ""
echo "== 02. DDD =="
# Domain models exist
if [ -f document-parser/domain/models.py ] && [ -f document-parser/domain/ports.py ]; then
pass "Domain models and ports exist"
else
fail "Missing domain/models.py or domain/ports.py"
fi
# Value objects exist
if [ -f document-parser/domain/value_objects.py ]; then
pass "Value objects defined"
else
warn "No value_objects.py in domain"
fi
# ── 03. Clean Code ─────────────────────────────────────────────────────────
echo ""
echo "== 03. Clean Code =="
# Check for files > 300 lines (backend)
LARGE_FILES=$(find document-parser -name "*.py" ! -path "*/.venv/*" ! -path "*/__pycache__/*" ! -path "*/tests/*" -exec awk 'END{if(NR>300) print FILENAME": "NR" lines"}' {} \;)
if [ -n "$LARGE_FILES" ]; then
warn "Large Python files (>300 lines):"
echo "$LARGE_FILES"
else
pass "No Python files exceed 300 lines"
fi
# Check for files > 300 lines (frontend)
LARGE_VUE=$(find frontend/src -name "*.vue" -o -name "*.ts" | xargs awk 'END{if(NR>300) print FILENAME": "NR" lines"}' 2>/dev/null)
if [ -n "$LARGE_VUE" ]; then
warn "Large frontend files (>300 lines):"
echo "$LARGE_VUE"
else
pass "No frontend files exceed 300 lines"
fi
# ── 04. KISS ───────────────────────────────────────────────────────────────
echo ""
echo "== 04. KISS =="
# Check for overly complex patterns
if grep -rn "type:\s*ignore" document-parser/ --include="*.py" ! -path "*/.venv/*" 2>/dev/null; then
warn "Found type: ignore comments (review if justified)"
else
pass "No unjustified type: ignore"
fi
# ── 05. DRY ────────────────────────────────────────────────────────────────
echo ""
echo "== 05. DRY =="
# Check for magic numbers in backend
if grep -rn "[^a-zA-Z_][0-9]\{3,\}[^0-9]" document-parser/ --include="*.py" ! -path "*/.venv/*" ! -path "*/tests/*" 2>/dev/null | grep -v "port\|version\|status\|#\|MAX_\|DEFAULT_\|LIMIT_" | head -5; then
warn "Possible magic numbers found (review above)"
else
pass "No obvious magic numbers"
fi
# ── 06. SOLID ──────────────────────────────────────────────────────────────
echo ""
echo "== 06. SOLID =="
# Check that ports (interfaces) exist
if grep -l "Protocol\|ABC\|abstractmethod" document-parser/domain/ports.py 2>/dev/null; then
pass "Domain ports use Protocol/ABC (Dependency Inversion)"
else
fail "No abstract ports found in domain"
fi
# ── 07. Decoupling ─────────────────────────────────────────────────────────
echo ""
echo "== 07. Decoupling =="
# Frontend should not hardcode backend URLs (except in config)
if grep -rn "localhost:8000\|127.0.0.1:8000" frontend/src/ --include="*.ts" --include="*.vue" 2>/dev/null | grep -v "config\|env\|http.ts"; then
fail "Frontend hardcodes backend URL outside config"
else
pass "Frontend backend URL is configurable"
fi
# ── 08. Security ───────────────────────────────────────────────────────────
echo ""
echo "== 08. Security =="
# Check for hardcoded secrets
if grep -rni "password\s*=\s*['\"].\+['\"\|secret\s*=\s*['\"].\+['\"\|api_key\s*=\s*['\"].\+['\"]" document-parser/ --include="*.py" ! -path "*/.venv/*" ! -path "*/tests/*" 2>/dev/null; then
fail "Possible hardcoded secrets found"
else
pass "No hardcoded secrets detected"
fi
# Check for eval/exec
if grep -rn "\beval(\|exec(" document-parser/ --include="*.py" ! -path "*/.venv/*" 2>/dev/null; then
fail "eval() or exec() found in backend"
else
pass "No eval/exec in backend"
fi
# Check CORS configuration exists
if grep -rn "CORSMiddleware" document-parser/ --include="*.py" ! -path "*/.venv/*" 2>/dev/null > /dev/null; then
pass "CORS middleware is configured"
else
warn "No CORS middleware found"
fi
# ── 09. Tests ──────────────────────────────────────────────────────────────
echo ""
echo "== 09. Tests =="
# Backend tests exist
BACKEND_TESTS=$(find document-parser/tests -name "test_*.py" 2>/dev/null | wc -l)
if [ "$BACKEND_TESTS" -gt 0 ]; then
pass "Backend: $BACKEND_TESTS test files found"
else
fail "No backend test files found"
fi
# Frontend tests exist
FRONTEND_TESTS=$(find frontend/src -name "*.test.*" 2>/dev/null | wc -l)
if [ "$FRONTEND_TESTS" -gt 0 ]; then
pass "Frontend: $FRONTEND_TESTS test files found"
else
fail "No frontend test files found"
fi
# E2E tests exist
E2E_TESTS=$(find e2e -name "*.feature" 2>/dev/null | wc -l)
if [ "$E2E_TESTS" -gt 0 ]; then
pass "E2E: $E2E_TESTS feature files found"
else
warn "No e2e feature files found"
fi
# Check for skipped tests
if grep -rn "@skip\|@ignore\|xit(\|xdescribe(\|pytest.mark.skip" document-parser/tests/ frontend/src/ 2>/dev/null | grep -v "helpers"; then
warn "Skipped tests found (review if intentional)"
else
pass "No skipped tests"
fi
# ── 10. CI / Build ────────────────────────────────────────────────────────
echo ""
echo "== 10. CI / Build =="
# CI workflow exists
if [ -f .github/workflows/ci.yml ]; then
pass "CI workflow exists"
else
fail "No CI workflow found"
fi
# Dockerfile exists
if [ -f Dockerfile ]; then
pass "Dockerfile exists"
else
fail "No Dockerfile found"
fi
# Health check in docker-compose
if grep -q "healthcheck" docker-compose.yml 2>/dev/null; then
pass "Docker Compose has health check"
else
warn "No health check in docker-compose.yml"
fi
# ── 11. Documentation ─────────────────────────────────────────────────────
echo ""
echo "== 11. Documentation =="
# CHANGELOG exists and has content
if [ -f CHANGELOG.md ] && [ -s CHANGELOG.md ]; then
pass "CHANGELOG.md exists and is not empty"
else
fail "CHANGELOG.md missing or empty"
fi
# README exists
if [ -f README.md ]; then
pass "README.md exists"
else
fail "README.md missing"
fi
# Check for TODO/FIXME without issue reference
TODOS=$(grep -rn "TODO\|FIXME" document-parser/ frontend/src/ --include="*.py" --include="*.ts" --include="*.vue" ! -path "*/.venv/*" ! -path "*/node_modules/*" 2>/dev/null | grep -v "#[0-9]" | head -5)
if [ -n "$TODOS" ]; then
warn "TODO/FIXME without issue reference:"
echo "$TODOS"
else
pass "No orphaned TODO/FIXME"
fi
# ── 12. Performance ───────────────────────────────────────────────────────
echo ""
echo "== 12. Performance =="
# Check for synchronous file I/O in async context
if grep -rn "open(" document-parser/api/ document-parser/services/ --include="*.py" 2>/dev/null | grep -v "aiofiles\|async\|#"; then
warn "Synchronous file I/O in async code (review above)"
else
pass "No synchronous file I/O in async endpoints"
fi
# Check for N+1 patterns (loop with DB call)
if grep -rn "for.*in.*:" document-parser/services/ --include="*.py" -A5 2>/dev/null | grep "await.*repo\|await.*db"; then
warn "Possible N+1 query pattern (review above)"
else
pass "No obvious N+1 patterns"
fi
# ── Summary ────────────────────────────────────────────────────────────────
echo ""
echo "============================================"
echo -e " ${GREEN}PASS${NC}: $PASS"
echo -e " ${YELLOW}WARN${NC}: $WARN"
echo -e " ${RED}FAIL${NC}: $FAIL"
echo "============================================"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi