piflash / pre-commit.sh
S-Dreamer's picture
Update pre-commit.sh
e3581c4 verified
#!/usr/bin/env bash
# PiFlash Pre-commit Hook
# Validates staged changes before commit
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_status() {
echo -e "${BLUE}[PiFlash]${NC} $1"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}⚠️${NC} $1"
}
print_error() {
echo -e "${RED}${NC} $1"
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
print_error "Required command not found: $1"
exit 1
fi
}
get_file_size() {
local file="$1"
stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null
}
cleanup_trailing_whitespace() {
local file="$1"
python3 - "$file" <<'PY'
from pathlib import Path
import sys
path = Path(sys.argv[1])
data = path.read_text(encoding="utf-8", errors="ignore").splitlines()
path.write_text("\n".join(line.rstrip() for line in data) + ("\n" if data else ""), encoding="utf-8")
PY
}
validate_css_braces() {
local file="$1"
node - "$file" <<'NODE'
const fs = require('fs');
const path = process.argv[2];
const css = fs.readFileSync(path, 'utf8');
const openBraces = (css.match(/\{/g) || []).length;
const closeBraces = (css.match(/\}/g) || []).length;
if (openBraces !== closeBraces) {
console.error(`Mismatched braces in ${path}`);
process.exit(1);
}
NODE
}
validate_json() {
local file="$1"
node - "$file" <<'NODE'
const fs = require('fs');
const path = process.argv[2];
JSON.parse(fs.readFileSync(path, 'utf8'));
NODE
}
scan_sensitive_content() {
local file="$1"
local content
content="$(git show ":$file" 2>/dev/null || true)"
if [[ -z "$content" ]]; then
return 0
fi
local patterns=(
"password[[:space:]]*[:=][[:space:]]*['\"][^'\"]+['\"]"
"api[_-]?key[[:space:]]*[:=][[:space:]]*['\"][^'\"]+['\"]"
"secret[[:space:]]*[:=][[:space:]]*['\"][^'\"]+['\"]"
"token[[:space:]]*[:=][[:space:]]*['\"][^'\"]+['\"]"
"private[_-]?key"
"BEGIN[[:space:]]+PRIVATE[[:space:]]+KEY"
"ghp_[A-Za-z0-9_]+"
"sk-[A-Za-z0-9]+"
)
local pattern
for pattern in "${patterns[@]}"; do
if grep -iE "$pattern" <<<"$content" >/dev/null 2>&1; then
print_error "Potential sensitive data found in staged content: $file"
print_error "Matched pattern: $pattern"
exit 1
fi
done
}
print_status "Running pre-commit checks for PiFlash..."
if ! git rev-parse --git-dir >/dev/null 2>&1; then
print_error "Not in a git repository"
exit 1
fi
require_cmd git
require_cmd grep
require_cmd file
STAGED_FILES=()
while IFS= read -r -d '' file; do
STAGED_FILES+=("$file")
done < <(git diff --cached --name-only --diff-filter=ACM -z)
if [[ ${#STAGED_FILES[@]} -eq 0 ]]; then
print_warning "No staged files found"
exit 0
fi
print_status "Staged files: ${#STAGED_FILES[@]}"
HTML_FILES=()
CSS_FILES=()
JS_FILES=()
JSON_FILES=()
for file in "${STAGED_FILES[@]}"; do
case "$file" in
*.html|*.htm) HTML_FILES+=("$file") ;;
*.css|*.scss|*.sass) CSS_FILES+=("$file") ;;
*.js|*.jsx|*.ts|*.tsx|*.mjs|*.cjs) JS_FILES+=("$file") ;;
*.json) JSON_FILES+=("$file") ;;
esac
done
print_status "Validating HTML files..."
if [[ ${#HTML_FILES[@]} -eq 0 ]]; then
print_status "No HTML files to validate"
else
for file in "${HTML_FILES[@]}"; do
[[ -f "$file" ]] || continue
if ! grep -q "<!DOCTYPE html>" "$file"; then
print_error "Missing DOCTYPE in $file"
exit 1
fi
if ! grep -q "<html" "$file" || ! grep -q "</html>" "$file"; then
print_error "Invalid HTML structure in $file"
exit 1
fi
print_success "HTML validation passed for $file"
done
fi
print_status "Validating CSS files..."
if [[ ${#CSS_FILES[@]} -eq 0 ]]; then
print_status "No CSS files to validate"
else
require_cmd node
for file in "${CSS_FILES[@]}"; do
[[ -f "$file" ]] || continue
if ! validate_css_braces "$file" >/dev/null 2>&1; then
print_error "CSS syntax error in $file"
exit 1
fi
print_success "CSS validation passed for $file"
done
fi
print_status "Validating JavaScript/TypeScript files..."
if [[ ${#JS_FILES[@]} -eq 0 ]]; then
print_status "No JavaScript files to validate"
else
require_cmd node
for file in "${JS_FILES[@]}"; do
[[ -f "$file" ]] || continue
if [[ "$file" =~ \.(js|mjs|cjs)$ ]]; then
if ! node -c "$file" >/dev/null 2>&1; then
print_error "JavaScript syntax error in $file"
exit 1
fi
else
print_warning "Skipping syntax parse for $file (Node cannot directly parse TypeScript)"
fi
if grep -n "console\.log" "$file" >/dev/null 2>&1; then
print_warning "console.log found in $file"
fi
if grep -niE "TODO|FIXME|HACK" "$file" >/dev/null 2>&1; then
print_warning "TODO/FIXME/HACK comments found in $file"
fi
print_success "Script validation passed for $file"
done
fi
print_status "Validating JSON files..."
if [[ ${#JSON_FILES[@]} -eq 0 ]]; then
print_status "No JSON files to validate"
else
require_cmd node
for file in "${JSON_FILES[@]}"; do
[[ -f "$file" ]] || continue
if ! validate_json "$file" >/dev/null 2>&1; then
print_error "Invalid JSON in $file"
exit 1
fi
print_success "JSON validation passed for $file"
done
fi
print_status "Checking file sizes..."
MAX_SIZE=1048576
for file in "${STAGED_FILES[@]}"; do
[[ -f "$file" ]] || continue
size="$(get_file_size "$file" 2>/dev/null || echo 0)"
if [[ "$size" -gt "$MAX_SIZE" ]]; then
print_error "File too large: $file ($(($size / 1024))KB > 1024KB)"
exit 1
fi
done
print_success "File size check passed"
print_status "Checking line endings..."
for file in "${STAGED_FILES[@]}"; do
[[ -f "$file" ]] || continue
if file "$file" | grep -q "CRLF"; then
print_warning "CRLF line endings found in $file"
fi
done
print_success "Line ending check completed"
print_status "Checking for trailing whitespace..."
WHITESPACE_FILES=()
for file in "${STAGED_FILES[@]}"; do
[[ -f "$file" ]] || continue
if grep -n '[[:blank:]]$' "$file" >/dev/null 2>&1; then
WHITESPACE_FILES+=("$file")
fi
done
if [[ ${#WHITESPACE_FILES[@]} -gt 0 ]]; then
print_warning "Trailing whitespace found in ${#WHITESPACE_FILES[@]} file(s)"
print_status "Fixing trailing whitespace and re-staging..."
require_cmd python3
for file in "${WHITESPACE_FILES[@]}"; do
cleanup_trailing_whitespace "$file"
git add "$file"
done
print_success "Trailing whitespace fixed and re-staged"
else
print_success "No trailing whitespace found"
fi
if [[ -n "${1:-}" && -f "${1:-}" ]]; then
print_status "Checking commit message..."
commit_msg="$(head -n1 "$1" | tr -d '\r')"
if [[ ${#commit_msg} -gt 72 ]]; then
print_warning "Commit message is longer than 72 characters"
fi
if grep -qE '^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .+' <<<"$commit_msg"; then
print_success "Conventional commit format detected"
else
print_warning "Consider using conventional commit format"
fi
fi
print_status "Scanning staged content for sensitive data..."
for file in "${STAGED_FILES[@]}"; do
scan_sensitive_content "$file"
done
print_success "Security scan completed"
print_success "All pre-commit checks passed"
print_status "Ready to commit ${#STAGED_FILES[@]} file(s)"
exit 0