Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:22:25 +08:00
commit c3294f28aa
60 changed files with 10297 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
#!/bin/bash
FILE_PATH="$1"
[[ ! -f "$FILE_PATH" ]] && exit 0
grep -E "from ['\"]@prisma/client['\"]|import.*@prisma/client|\\\$queryRaw|\\\$executeRaw" "$FILE_PATH" 2>/dev/null

View File

@@ -0,0 +1,107 @@
#!/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
PRISMA_FILES=$(echo "$TS_FILES" | xargs grep -l '@prisma/client' 2>/dev/null || true)
BUFFER_USAGE=""
if [ -n "$PRISMA_FILES" ]; then
BUFFER_USAGE=$(echo "$PRISMA_FILES" | xargs grep -nE 'Buffer\.from\(' 2>/dev/null || true)
fi
if [ -n "$BUFFER_USAGE" ]; then
log_warn "Buffer.from() usage detected in files importing @prisma/client"
pretooluse_respond "allow" "Warning: Buffer.from() usage detected in files importing @prisma/client
Prisma 6 Bytes fields use Uint8Array instead of Buffer:
✗ const bytes = Buffer.from(data)
✗ reportData: Buffer.from(jsonString)
✓ reportData: new TextEncoder().encode(jsonString)
Bytes fields are returned as Uint8Array, no conversion needed:
✗ Buffer.from(user.profilePicture)
✓ user.profilePicture (already Uint8Array)"
finish_hook 0
fi
TOSTRING_ON_BYTES=$(echo "$TS_FILES" | xargs grep -nE '\.(avatar|file|attachment|document|reportData|image|photo|content|data|binary)\.toString\(' 2>/dev/null || true)
if [ -n "$TOSTRING_ON_BYTES" ]; then
log_warn ".toString() on potential Bytes fields detected"
pretooluse_respond "allow" "Warning: .toString() on potential Bytes fields detected
Bytes fields are now Uint8Array, not Buffer:
✗ reportData.toString('utf-8')
✓ new TextDecoder().decode(reportData)
For base64 encoding:
✗ avatar.toString('base64')
✓ Buffer.from(avatar).toString('base64')"
finish_hook 0
fi
NOT_FOUND_ERROR=$(echo "$TS_FILES" | xargs grep -En --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 'Prisma.*NotFoundError|@prisma/client.*NotFoundError|PrismaClient.*NotFoundError|from.*["\047]@prisma/client["\047].*NotFoundError' 2>/dev/null || true)
if [ -n "$NOT_FOUND_ERROR" ]; then
log_warn "Deprecated NotFoundError handling detected"
pretooluse_respond "allow" "Warning: Deprecated NotFoundError handling detected
Use error code P2025 instead of NotFoundError:
✗ if (error instanceof NotFoundError)
✓ if (error.code === 'P2025')"
finish_hook 0
fi
REJECTONFOUND=$(echo "$TS_FILES" | xargs grep -n 'rejectOnNotFound' 2>/dev/null || true)
if [ -n "$REJECTONFOUND" ]; then
log_warn "Deprecated rejectOnNotFound option detected"
pretooluse_respond "allow" "Warning: Deprecated rejectOnNotFound option detected
Use findUniqueOrThrow() or findFirstOrThrow() instead:
✗ findUnique({ where: { id }, rejectOnNotFound: true })
✓ findUniqueOrThrow({ where: { id } })"
finish_hook 0
fi
EXPERIMENTAL_FEATURES=$(echo "$TS_FILES" | xargs grep -n 'experimentalFeatures.*extendedWhereUnique\|experimentalFeatures.*fullTextSearch' 2>/dev/null || true)
if [ -n "$EXPERIMENTAL_FEATURES" ]; then
log_warn "Deprecated experimental features detected"
pretooluse_respond "allow" "Warning: Deprecated experimental features detected
These features are now stable in Prisma 6:
- extendedWhereUnique (enabled by default)
- fullTextSearch (enabled by default)
Remove from schema.prisma generator block."
finish_hook 0
fi
pretooluse_respond "allow"
finish_hook 0

View File

@@ -0,0 +1,108 @@
#!/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

View File

@@ -0,0 +1,80 @@
#!/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
UNSAFE_QUERY_RAW=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" '\$queryRawUnsafe' 2>/dev/null || true)
if [ -n "$UNSAFE_QUERY_RAW" ]; then
log_error "Unsafe raw SQL query detected - SQL injection risk"
pretooluse_respond "block" "Warning: Unsafe raw SQL query detected - SQL injection risk
Use \$queryRaw with tagged template syntax instead:
✗ prisma.\$queryRawUnsafe(\`SELECT * FROM User WHERE id = \${id}\`)
✓ prisma.\$queryRaw\`SELECT * FROM User WHERE id = \${id}\`"
finish_hook 0
fi
RAW_WITH_INTERPOLATION=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" 'Prisma\.raw(' 2>/dev/null | \
grep -E '\$\{|\+.*["\`]' || true)
if [ -n "$RAW_WITH_INTERPOLATION" ]; then
log_error "Prisma.raw() with string interpolation - SQL injection risk"
pretooluse_respond "block" "Warning: Prisma.raw() with string interpolation - SQL injection risk
Use Prisma.sql with tagged template syntax:
✗ Prisma.raw(\`WHERE id = \${id}\`)
✓ Prisma.sql\`WHERE id = \${id}\`"
finish_hook 0
fi
MISSING_TAGGED_TEMPLATE=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" '\$queryRaw(' 2>/dev/null || true)
if [ -n "$MISSING_TAGGED_TEMPLATE" ]; then
log_warn "\$queryRaw with function call syntax instead of tagged template"
pretooluse_respond "allow" "Warning: \$queryRaw with function call syntax instead of tagged template
Use tagged template syntax for automatic parameterization:
✗ prisma.\$queryRaw(Prisma.sql\`...\`)
✓ prisma.\$queryRaw\`...\`"
finish_hook 0
fi
EXECUTE_RAW_UNSAFE=$(echo "$TS_FILES" | xargs grep -n --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx" '\$executeRawUnsafe' 2>/dev/null || true)
if [ -n "$EXECUTE_RAW_UNSAFE" ]; then
log_error "Unsafe raw SQL execution detected - SQL injection risk"
pretooluse_respond "block" "Warning: Unsafe raw SQL execution detected - SQL injection risk
Use \$executeRaw with tagged template syntax instead:
✗ prisma.\$executeRawUnsafe(\`UPDATE User SET name = '\${name}'\`)
✓ prisma.\$executeRaw\`UPDATE User SET name = \${name}\`"
finish_hook 0
fi
pretooluse_respond "allow"
finish_hook 0

14
hooks/scripts/init-session.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/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" "SessionStart"
log_info "Prisma 6 session initialized"
inject_context "Prisma 6 plugin session started"
finish_hook 0

View File

@@ -0,0 +1,73 @@
#!/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" "PostToolUse"
INPUT=$(read_hook_input)
FILE_PATH=$(get_input_field "tool_input.file_path")
if [[ -z "$FILE_PATH" ]]; then
FILE_PATH=$(get_input_field "tool_input.path")
fi
if [[ -z "$FILE_PATH" ]]; then
finish_hook 0
fi
FILE_NAME="${FILE_PATH##*/}"
FILE_DIR="${FILE_PATH%/*}"
RECOMMENDATION_TYPE=""
SKILLS=""
MESSAGE=""
if [[ "$FILE_NAME" == "schema.prisma" ]]; then
RECOMMENDATION_TYPE="schema_files"
SKILLS="MIGRATIONS-*, CLIENT-*, QUERIES-type-safety"
MESSAGE="Prisma Schema: $SKILLS"
elif [[ "$FILE_DIR" == *"migrations"* ]]; then
RECOMMENDATION_TYPE="migration_files"
SKILLS="MIGRATIONS-dev-workflow, MIGRATIONS-production, MIGRATIONS-v6-upgrade"
MESSAGE="Prisma Migrations: $SKILLS"
elif [[ "$FILE_PATH" =~ \.(ts|js|tsx|jsx)$ ]]; then
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$SCRIPT_DIR")")}"
IMPORTS=$(bash "$PLUGIN_ROOT/hooks/scripts/analyze-imports.sh" "$FILE_PATH" 2>/dev/null || true)
if [[ "$IMPORTS" == *"@prisma/client"* ]]; then
RECOMMENDATION_TYPE="prisma_files"
SKILLS="CLIENT-*, QUERIES-*, TRANSACTIONS-*, SECURITY-*"
MESSAGE="Prisma Client Usage: $SKILLS"
if [[ "$IMPORTS" == *"\$queryRaw"* ]]; then
RECOMMENDATION_TYPE="raw_sql_context"
SKILLS="SECURITY-sql-injection (CRITICAL)"
MESSAGE="Raw SQL Detected: $SKILLS"
fi
fi
if [[ "$FILE_PATH" == *"vercel"* || "$FILE_PATH" == *"lambda"* || "$FILE_PATH" == *"app/"* ]]; then
if ! has_shown_recommendation "prisma-6" "serverless_context"; then
log_info "Recommending skills: CLIENT-serverless-config, PERFORMANCE-connection-pooling for serverless context in $FILE_PATH"
mark_recommendation_shown "prisma-6" "serverless_context"
inject_context "Serverless Context: CLIENT-serverless-config, PERFORMANCE-connection-pooling"
fi
fi
fi
if [[ -z "$RECOMMENDATION_TYPE" ]]; then
finish_hook 0
fi
if ! has_shown_recommendation "prisma-6" "$RECOMMENDATION_TYPE"; then
log_info "Recommending skills: $SKILLS for $FILE_PATH"
mark_recommendation_shown "prisma-6" "$RECOMMENDATION_TYPE"
inject_context "$MESSAGE
Use Skill tool to activate specific skills when needed."
fi
finish_hook 0