Files
gh-djankies-claude-configs-…/hooks/scripts/check-prisma-client.sh
2025-11-29 18:22:25 +08:00

109 lines
4.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLAUDE_MARKETPLACE_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
source "${CLAUDE_MARKETPLACE_ROOT}/marketplace-utils/hook-lifecycle.sh"
init_hook "prisma-6" "PreToolUse"
INPUT=$(read_hook_input)
if ! command -v grep &> /dev/null; then
log_error "grep command not found"
pretooluse_respond "allow"
finish_hook 0
fi
TS_FILES=$(find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \) \
! -path "*/node_modules/*" \
! -path "*/dist/*" \
! -path "*/build/*" \
! -path "*/.next/*" 2>/dev/null || true)
if [ -z "$TS_FILES" ]; then
pretooluse_respond "allow"
finish_hook 0
fi
INSTANCES=$(echo "$TS_FILES" | xargs grep -n --exclude="*.test.ts" --exclude="*.spec.ts" --exclude-dir="__tests__" --exclude-dir="test" "new PrismaClient()" 2>/dev/null || true)
if [ -n "$INSTANCES" ]; then
INSTANCE_COUNT=$(echo "$INSTANCES" | wc -l | tr -d ' ')
if [ "$INSTANCE_COUNT" -gt 1 ]; then
log_warn "Multiple PrismaClient instances detected: $INSTANCE_COUNT"
pretooluse_respond "allow" "Warning: Multiple PrismaClient instances detected ($INSTANCE_COUNT)
Use global singleton pattern to prevent connection pool exhaustion:
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma"
finish_hook 0
fi
fi
FUNCTION_SCOPED=$(echo "$TS_FILES" | xargs grep -B5 --exclude="*.test.ts" --exclude="*.spec.ts" --exclude-dir="__tests__" --exclude-dir="test" "new PrismaClient()" 2>/dev/null | \
grep -E "(function|const.*=.*\(|async.*\()" || true)
if [ -n "$FUNCTION_SCOPED" ]; then
IS_SINGLETON_WRAPPER=false
for file in $TS_FILES; do
if grep -q "new PrismaClient()" "$file" 2>/dev/null; then
HAS_SINGLETON_FUNC=$(grep -E "const.*singleton.*=.*\(\).*=>.*new PrismaClient\(\)" "$file" || true)
HAS_GLOBAL_CACHE=$(grep -E "globalThis|globalForPrisma" "$file" || true)
SINGLETON_CALLS=$(grep -o "singleton()" "$file" 2>/dev/null | wc -l | tr -d ' ')
if [ -n "$HAS_SINGLETON_FUNC" ] && [ -n "$HAS_GLOBAL_CACHE" ] && [ "$SINGLETON_CALLS" -le 1 ]; then
IS_SINGLETON_WRAPPER=true
break
fi
fi
done
if [ "$IS_SINGLETON_WRAPPER" = false ]; then
log_warn "PrismaClient instantiated inside function scope"
pretooluse_respond "allow" "Warning: PrismaClient instantiated inside function scope
This creates new instances on each function call, exhausting connections.
Move PrismaClient to module scope with singleton pattern."
finish_hook 0
fi
fi
RAW_INTERPOLATION=$(echo "$TS_FILES" | xargs grep -n "prisma\.\(raw\|queryRaw\|executeRaw\)" 2>/dev/null | \
grep -E '\$\{.*\}|"\s*\+\s*[^+]|\`\$\{' || true)
if [ -n "$RAW_INTERPOLATION" ]; then
log_warn "Potential SQL interpolation in Prisma.raw() call"
pretooluse_respond "allow" "Warning: Potential SQL interpolation detected in Prisma.raw() call
Use parameterized queries to prevent SQL injection:
prisma.\$queryRaw\`SELECT * FROM users WHERE id = \${id}\`
Avoid string concatenation:
❌ prisma.\$queryRaw('SELECT * FROM users WHERE id = ' + id)
❌ prisma.\$queryRaw(\`SELECT * FROM users WHERE id = \${unsafeVar}\`)"
finish_hook 0
fi
MISSING_GLOBAL=$(echo "$TS_FILES" | xargs grep -L "globalForPrisma\|globalThis.*prisma" 2>/dev/null | \
xargs grep -l "new PrismaClient()" 2>/dev/null || true)
if [ -n "$MISSING_GLOBAL" ]; then
log_warn "PrismaClient instantiation without global singleton pattern"
pretooluse_respond "allow" "Warning: PrismaClient instantiation without global singleton pattern
Recommended pattern for Next.js and serverless environments:
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined }
export const prisma = globalForPrisma.prisma ?? new PrismaClient()"
finish_hook 0
fi
pretooluse_respond "allow"
finish_hook 0