#!/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 -uo 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