secubox-openwrt/secubox-tools/pre-deploy-lint.sh
CyberMind-FR 00d92037b9 feat(tools): Add pre-deploy-lint.sh for syntax validation
- JavaScript validation via Node.js --check (with pattern fallback)
- JSON validation for menu.d and acl.d files
- Shell script validation with shellcheck integration
- CSS validation for unclosed braces and typos
- LuCI-specific checks: require format, console.log, debugger
- Integrated into quick-deploy.sh as default for LuCI apps
- --lint/--no-lint flags for deployment control
- Documentation added to secubox-tools/README.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-28 18:29:51 +01:00

393 lines
11 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
#
# SecuBox Pre-Deploy Lint Script
# Validates JavaScript, JSON, and shell syntax before deployment
#
# Usage:
# ./secubox-tools/pre-deploy-lint.sh [package-dir|--all]
# ./secubox-tools/pre-deploy-lint.sh luci-app-system-hub
# ./secubox-tools/pre-deploy-lint.sh package/secubox/luci-app-cdn-cache
# ./secubox-tools/pre-deploy-lint.sh --all
#
# Returns 0 on success, 1 on validation failure
#
set -eo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
ERRORS=0
WARNINGS=0
CHECKED_FILES=0
log() { echo -e "$*"; }
error() { echo -e "${RED}$*${NC}"; ((ERRORS++)); }
warn() { echo -e "${YELLOW}⚠️ $*${NC}"; ((WARNINGS++)); }
success() { echo -e "${GREEN}$*${NC}"; }
info() { echo -e "${BLUE} $*${NC}"; }
# Check if we have Node.js for JavaScript validation
HAS_NODE=0
if command -v node &>/dev/null; then
HAS_NODE=1
fi
# Check if we have shellcheck
HAS_SHELLCHECK=0
if command -v shellcheck &>/dev/null; then
HAS_SHELLCHECK=1
fi
# Find all luci-app directories
get_luci_apps() {
find . -maxdepth 1 -type d -name 'luci-app-*' 2>/dev/null
find package/secubox -maxdepth 1 -type d -name 'luci-app-*' 2>/dev/null
}
# Validate JavaScript syntax
validate_js_file() {
local file="$1"
local result=0
((CHECKED_FILES++))
if [[ $HAS_NODE -eq 1 ]]; then
# Use Node.js --check to validate syntax
if ! node --check "$file" 2>/dev/null; then
error "JS syntax error: $file"
# Get detailed error
node --check "$file" 2>&1 | head -5 | while read -r line; do
echo " $line"
done
result=1
fi
else
# Fallback: Basic pattern checks for common JS errors
# Check for unclosed strings (simple heuristic)
if grep -P "^[^/]*['\"][^'\"]*$" "$file" | grep -v '//' | grep -v '/*' | head -1 | grep -q .; then
warn "Possible unclosed string in: $file (manual review needed)"
fi
# Check for missing semicolons after common patterns (LuCI style uses them)
# This is just a warning as LuCI code often omits them
# Check for common typos
if grep -qE '\bretrun\b|\bfuntion\b|\bvat\b|\bleng th\b' "$file"; then
warn "Possible typo detected in: $file"
fi
# Check for duplicate function declarations
local dups
dups=$(grep -oE "^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:\s*function" "$file" 2>/dev/null | \
sed 's/:.*//' | sort | uniq -d)
if [[ -n "$dups" ]]; then
warn "Possible duplicate function: $dups in $file"
fi
fi
# Additional checks regardless of Node availability
# Check for console.log (should be removed in production)
if grep -qE '\bconsole\.(log|debug|info)\b' "$file"; then
warn "console.log found in: $file (consider removing for production)"
fi
# Check for debugger statements
if grep -qE '^\s*debugger\s*;?\s*$' "$file"; then
error "debugger statement found in: $file"
result=1
fi
# Check LuCI-specific patterns
# Check for missing 'use strict'
if ! head -5 "$file" | grep -qE "'use strict'|\"use strict\""; then
warn "Missing 'use strict' directive: $file"
fi
# Check for proper require statements (LuCI pattern)
if grep -qE "^'require [^']+';$" "$file"; then
# Valid LuCI require format
:
elif grep -qE "require\s*\(" "$file" && ! grep -qE "^'require " "$file"; then
# Uses require() instead of LuCI string format
warn "Non-standard require format in: $file (LuCI uses 'require module';)"
fi
return $result
}
# Validate JSON syntax
validate_json_file() {
local file="$1"
((CHECKED_FILES++))
if python3 -m json.tool "$file" >/dev/null 2>&1; then
return 0
else
error "JSON syntax error: $file"
# Show the error
python3 -m json.tool "$file" 2>&1 | head -3 | while read -r line; do
echo " $line"
done
return 1
fi
}
# Validate shell script syntax
validate_shell_file() {
local file="$1"
local result=0
((CHECKED_FILES++))
# Basic syntax check using bash/sh
if head -1 "$file" | grep -qE '^#!/bin/(ba)?sh'; then
local shell_type="sh"
head -1 "$file" | grep -q bash && shell_type="bash"
if ! $shell_type -n "$file" 2>/dev/null; then
error "Shell syntax error: $file"
$shell_type -n "$file" 2>&1 | head -5 | while read -r line; do
echo " $line"
done
result=1
fi
fi
# Use shellcheck if available
if [[ $HAS_SHELLCHECK -eq 1 && $result -eq 0 ]]; then
local sc_errors
sc_errors=$(shellcheck -f gcc "$file" 2>/dev/null | grep -c ':error:' || true)
if [[ "$sc_errors" -gt 0 ]]; then
error "shellcheck errors ($sc_errors) in: $file"
shellcheck -f gcc "$file" 2>/dev/null | grep ':error:' | head -5 | while read -r line; do
echo " $line"
done
result=1
fi
local sc_warnings
sc_warnings=$(shellcheck -f gcc "$file" 2>/dev/null | grep -c ':warning:' || true)
if [[ "$sc_warnings" -gt 0 ]]; then
warn "shellcheck warnings ($sc_warnings) in: $file"
fi
fi
# Check for common RPCD patterns
if [[ "$file" == */usr/libexec/rpcd/* ]]; then
# Check for proper JSON output
if ! grep -qE 'printf.*{.*}|echo.*{.*}' "$file"; then
warn "RPCD script may not output JSON: $file"
fi
# Check for case statement (method dispatcher)
if ! grep -qE 'case\s+"\$[12]"\s+in' "$file"; then
warn "RPCD script missing method dispatcher: $file"
fi
fi
return $result
}
# Validate CSS file
validate_css_file() {
local file="$1"
((CHECKED_FILES++))
# Basic CSS validation - check for unclosed braces
local open_braces close_braces
open_braces=$(grep -o '{' "$file" 2>/dev/null | wc -l)
close_braces=$(grep -o '}' "$file" 2>/dev/null | wc -l)
if [[ "$open_braces" -ne "$close_braces" ]]; then
error "CSS unclosed braces in: $file (open: $open_braces, close: $close_braces)"
return 1
fi
# Check for common typos
if grep -qE 'backgrond|colro|maring|paddig|widht|heigth' "$file"; then
warn "Possible CSS typo in: $file"
fi
return 0
}
# Lint a single package directory
lint_package() {
local pkg_dir="$1"
local pkg_name
pkg_name=$(basename "$pkg_dir")
log ""
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
log "${BLUE}Linting: $pkg_name${NC}"
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local pkg_errors=0
# Validate JavaScript files
while IFS= read -r js_file; do
[[ -z "$js_file" ]] && continue
if ! validate_js_file "$js_file"; then
((pkg_errors++))
fi
done < <(find "$pkg_dir" -name "*.js" -type f 2>/dev/null)
# Validate JSON files
while IFS= read -r json_file; do
[[ -z "$json_file" ]] && continue
if ! validate_json_file "$json_file"; then
((pkg_errors++))
fi
done < <(find "$pkg_dir" -name "*.json" -type f 2>/dev/null)
# Validate shell scripts (RPCD handlers)
local rpcd_dir="$pkg_dir/root/usr/libexec/rpcd"
if [[ -d "$rpcd_dir" ]]; then
while IFS= read -r script; do
[[ -z "$script" ]] && continue
if ! validate_shell_file "$script"; then
((pkg_errors++))
fi
done < <(find "$rpcd_dir" -type f ! -name "*.md" 2>/dev/null)
fi
# Validate other shell scripts
while IFS= read -r script; do
[[ -z "$script" ]] && continue
[[ "$script" == */rpcd/* ]] && continue # Already checked
if head -1 "$script" 2>/dev/null | grep -qE '^#!/bin/(ba)?sh'; then
if ! validate_shell_file "$script"; then
((pkg_errors++))
fi
fi
done < <(find "$pkg_dir/root" -type f 2>/dev/null)
# Validate CSS files
while IFS= read -r css_file; do
[[ -z "$css_file" ]] && continue
if ! validate_css_file "$css_file"; then
((pkg_errors++))
fi
done < <(find "$pkg_dir" -name "*.css" -type f 2>/dev/null)
if [[ $pkg_errors -eq 0 ]]; then
success "$pkg_name: All files validated"
fi
return $pkg_errors
}
# Resolve package path from name
resolve_package() {
local input="$1"
# Direct path
if [[ -d "$input" ]]; then
echo "$input"
return 0
fi
# Try adding luci-app- prefix
if [[ -d "luci-app-$input" ]]; then
echo "luci-app-$input"
return 0
fi
# Try in package/secubox
if [[ -d "package/secubox/luci-app-$input" ]]; then
echo "package/secubox/luci-app-$input"
return 0
fi
if [[ -d "package/secubox/$input" ]]; then
echo "package/secubox/$input"
return 0
fi
return 1
}
# Main
main() {
log "========================================"
log "SecuBox Pre-Deploy Lint"
log "========================================"
# Check tools
if [[ $HAS_NODE -eq 1 ]]; then
info "Node.js available: $(node --version) - full JS syntax checking enabled"
else
warn "Node.js not found - using basic JS pattern checks"
info "Install Node.js for better JavaScript validation"
fi
if [[ $HAS_SHELLCHECK -eq 1 ]]; then
info "shellcheck available - enhanced shell script checking enabled"
else
warn "shellcheck not found - using basic shell syntax checks"
fi
local target="${1:-}"
if [[ -z "$target" ]]; then
log ""
log "Usage: $0 <package-dir|package-name|--all>"
log ""
log "Examples:"
log " $0 luci-app-system-hub"
log " $0 package/secubox/luci-app-cdn-cache"
log " $0 --all"
exit 1
fi
if [[ "$target" == "--all" ]]; then
while IFS= read -r pkg_dir; do
[[ ! -d "$pkg_dir" ]] && continue
lint_package "$pkg_dir"
done < <(get_luci_apps)
else
local pkg_path
if ! pkg_path=$(resolve_package "$target"); then
error "Package not found: $target"
log ""
log "Available packages:"
get_luci_apps | while read -r d; do
log " - $(basename "$d")"
done
exit 1
fi
lint_package "$pkg_path"
fi
# Summary
log ""
log "========================================"
log "Lint Summary"
log "========================================"
log "Files checked: $CHECKED_FILES"
if [[ $ERRORS -eq 0 && $WARNINGS -eq 0 ]]; then
success "All checks passed!"
exit 0
elif [[ $ERRORS -eq 0 ]]; then
warn "Passed with $WARNINGS warning(s)"
exit 0
else
error "Failed with $ERRORS error(s) and $WARNINGS warning(s)"
log ""
log "Fix errors before deploying!"
exit 1
fi
}
main "$@"