Initial commit
This commit is contained in:
387
commands/architect/.scripts/analyze-dependencies.sh
Executable file
387
commands/architect/.scripts/analyze-dependencies.sh
Executable file
@@ -0,0 +1,387 @@
|
||||
#!/bin/bash
|
||||
# Purpose: Analyze project dependencies for security, versioning, and usage
|
||||
# Version: 1.0.0
|
||||
# Usage: ./analyze-dependencies.sh [path]
|
||||
# Returns: JSON formatted dependency analysis
|
||||
# Exit codes: 0=success, 1=error, 2=invalid input
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_DIR="${1:-.}"
|
||||
readonly OUTPUT_FORMAT="${2:-json}"
|
||||
|
||||
# Color codes for output
|
||||
readonly RED='\033[0;31m'
|
||||
readonly YELLOW='\033[1;33m'
|
||||
readonly GREEN='\033[0;32m'
|
||||
readonly NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $*" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $*" >&2
|
||||
}
|
||||
|
||||
# Validate input
|
||||
validate_input() {
|
||||
if [[ ! -d "$PROJECT_DIR" ]]; then
|
||||
log_error "Directory not found: $PROJECT_DIR"
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect package manager and dependency files
|
||||
detect_package_manager() {
|
||||
local pkg_manager=""
|
||||
local dep_file=""
|
||||
|
||||
if [[ -f "$PROJECT_DIR/package.json" ]]; then
|
||||
pkg_manager="npm"
|
||||
dep_file="package.json"
|
||||
elif [[ -f "$PROJECT_DIR/requirements.txt" ]]; then
|
||||
pkg_manager="pip"
|
||||
dep_file="requirements.txt"
|
||||
elif [[ -f "$PROJECT_DIR/Pipfile" ]]; then
|
||||
pkg_manager="pipenv"
|
||||
dep_file="Pipfile"
|
||||
elif [[ -f "$PROJECT_DIR/pyproject.toml" ]]; then
|
||||
pkg_manager="poetry"
|
||||
dep_file="pyproject.toml"
|
||||
elif [[ -f "$PROJECT_DIR/Gemfile" ]]; then
|
||||
pkg_manager="bundler"
|
||||
dep_file="Gemfile"
|
||||
elif [[ -f "$PROJECT_DIR/go.mod" ]]; then
|
||||
pkg_manager="go"
|
||||
dep_file="go.mod"
|
||||
elif [[ -f "$PROJECT_DIR/Cargo.toml" ]]; then
|
||||
pkg_manager="cargo"
|
||||
dep_file="Cargo.toml"
|
||||
elif [[ -f "$PROJECT_DIR/composer.json" ]]; then
|
||||
pkg_manager="composer"
|
||||
dep_file="composer.json"
|
||||
else
|
||||
log_warn "No recognized dependency file found"
|
||||
pkg_manager="unknown"
|
||||
dep_file="none"
|
||||
fi
|
||||
|
||||
echo "$pkg_manager|$dep_file"
|
||||
}
|
||||
|
||||
# Count dependencies
|
||||
count_dependencies() {
|
||||
local pkg_manager="$1"
|
||||
local dep_file="$2"
|
||||
local direct_count=0
|
||||
local dev_count=0
|
||||
|
||||
case "$pkg_manager" in
|
||||
npm)
|
||||
if command -v jq &> /dev/null; then
|
||||
direct_count=$(jq -r '.dependencies // {} | length' "$PROJECT_DIR/$dep_file" 2>/dev/null || echo 0)
|
||||
dev_count=$(jq -r '.devDependencies // {} | length' "$PROJECT_DIR/$dep_file" 2>/dev/null || echo 0)
|
||||
else
|
||||
direct_count=$(grep -c '"' "$PROJECT_DIR/$dep_file" 2>/dev/null || echo 0)
|
||||
fi
|
||||
;;
|
||||
pip)
|
||||
direct_count=$(grep -v '^#' "$PROJECT_DIR/$dep_file" 2>/dev/null | grep -c . || echo 0)
|
||||
;;
|
||||
go)
|
||||
direct_count=$(grep -c 'require' "$PROJECT_DIR/$dep_file" 2>/dev/null || echo 0)
|
||||
;;
|
||||
*)
|
||||
direct_count=0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$direct_count|$dev_count"
|
||||
}
|
||||
|
||||
# Check for outdated dependencies (simplified - would need package manager specific commands)
|
||||
check_outdated() {
|
||||
local pkg_manager="$1"
|
||||
local outdated_count=0
|
||||
|
||||
# This is a simplified check - in practice would run actual package manager commands
|
||||
case "$pkg_manager" in
|
||||
npm)
|
||||
if command -v npm &> /dev/null && [[ -f "$PROJECT_DIR/package-lock.json" ]]; then
|
||||
log_info "Checking for outdated npm packages..."
|
||||
# Would run: npm outdated --json in production
|
||||
outdated_count=0 # Placeholder
|
||||
fi
|
||||
;;
|
||||
pip)
|
||||
if command -v pip &> /dev/null; then
|
||||
log_info "Checking for outdated pip packages..."
|
||||
# Would run: pip list --outdated in production
|
||||
outdated_count=0 # Placeholder
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$outdated_count"
|
||||
}
|
||||
|
||||
# Check for security vulnerabilities (simplified)
|
||||
check_vulnerabilities() {
|
||||
local pkg_manager="$1"
|
||||
local vuln_count=0
|
||||
local critical=0
|
||||
local high=0
|
||||
local medium=0
|
||||
local low=0
|
||||
|
||||
# This would integrate with actual security scanners
|
||||
case "$pkg_manager" in
|
||||
npm)
|
||||
if command -v npm &> /dev/null && [[ -f "$PROJECT_DIR/package-lock.json" ]]; then
|
||||
log_info "Checking for npm security vulnerabilities..."
|
||||
# Would run: npm audit --json in production
|
||||
vuln_count=0 # Placeholder
|
||||
fi
|
||||
;;
|
||||
pip)
|
||||
if command -v safety &> /dev/null; then
|
||||
log_info "Checking for Python security vulnerabilities..."
|
||||
# Would run: safety check in production
|
||||
vuln_count=0 # Placeholder
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$critical|$high|$medium|$low"
|
||||
}
|
||||
|
||||
# Analyze dependency tree depth (simplified)
|
||||
analyze_tree_depth() {
|
||||
local pkg_manager="$1"
|
||||
local max_depth=0
|
||||
|
||||
case "$pkg_manager" in
|
||||
npm)
|
||||
if [[ -f "$PROJECT_DIR/package-lock.json" ]]; then
|
||||
# Simplified depth calculation
|
||||
max_depth=3 # Placeholder - would calculate from lockfile
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
max_depth=0
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$max_depth"
|
||||
}
|
||||
|
||||
# Find unused dependencies (simplified)
|
||||
find_unused() {
|
||||
local pkg_manager="$1"
|
||||
local unused_count=0
|
||||
|
||||
# This would require code analysis to see what's actually imported/required
|
||||
case "$pkg_manager" in
|
||||
npm)
|
||||
log_info "Analyzing for unused npm packages..."
|
||||
# Would use tools like depcheck in production
|
||||
unused_count=0 # Placeholder
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$unused_count"
|
||||
}
|
||||
|
||||
# Check for duplicate dependencies
|
||||
check_duplicates() {
|
||||
local pkg_manager="$1"
|
||||
local duplicate_count=0
|
||||
|
||||
case "$pkg_manager" in
|
||||
npm)
|
||||
if [[ -f "$PROJECT_DIR/package-lock.json" ]]; then
|
||||
log_info "Checking for duplicate packages..."
|
||||
# Would analyze lockfile for version conflicts
|
||||
duplicate_count=0 # Placeholder
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$duplicate_count"
|
||||
}
|
||||
|
||||
# Generate dependency analysis report
|
||||
generate_report() {
|
||||
local pkg_manager="$1"
|
||||
local dep_file="$2"
|
||||
local dep_counts="$3"
|
||||
local outdated="$4"
|
||||
local vulnerabilities="$5"
|
||||
local tree_depth="$6"
|
||||
local unused="$7"
|
||||
local duplicates="$8"
|
||||
|
||||
IFS='|' read -r direct_deps dev_deps <<< "$dep_counts"
|
||||
IFS='|' read -r crit_vulns high_vulns med_vulns low_vulns <<< "$vulnerabilities"
|
||||
|
||||
local total_deps=$((direct_deps + dev_deps))
|
||||
local total_vulns=$((crit_vulns + high_vulns + med_vulns + low_vulns))
|
||||
|
||||
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"package_manager": "$pkg_manager",
|
||||
"dependency_file": "$dep_file",
|
||||
"analysis_date": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
|
||||
"dependencies": {
|
||||
"total": $total_deps,
|
||||
"direct": $direct_deps,
|
||||
"development": $dev_deps,
|
||||
"outdated": $outdated,
|
||||
"unused": $unused,
|
||||
"duplicates": $duplicates
|
||||
},
|
||||
"vulnerabilities": {
|
||||
"total": $total_vulns,
|
||||
"critical": $crit_vulns,
|
||||
"high": $high_vulns,
|
||||
"medium": $med_vulns,
|
||||
"low": $low_vulns
|
||||
},
|
||||
"tree_depth": $tree_depth,
|
||||
"health_score": $(calculate_health_score "$total_vulns" "$outdated" "$unused" "$duplicates"),
|
||||
"recommendations": $(generate_recommendations "$total_vulns" "$outdated" "$unused" "$duplicates")
|
||||
}
|
||||
EOF
|
||||
else
|
||||
cat <<EOF
|
||||
|
||||
==============================================
|
||||
Dependency Analysis Report
|
||||
==============================================
|
||||
|
||||
Package Manager: $pkg_manager
|
||||
Dependency File: $dep_file
|
||||
Analysis Date: $(date)
|
||||
|
||||
Dependencies:
|
||||
Total Dependencies: $total_deps
|
||||
Direct Dependencies: $direct_deps
|
||||
Development Dependencies: $dev_deps
|
||||
Outdated: $outdated
|
||||
Unused: $unused
|
||||
Duplicates: $duplicates
|
||||
|
||||
Security Vulnerabilities:
|
||||
Total: $total_vulns
|
||||
Critical: $crit_vulns
|
||||
High: $high_vulns
|
||||
Medium: $med_vulns
|
||||
Low: $low_vulns
|
||||
|
||||
Dependency Tree:
|
||||
Maximum Depth: $tree_depth
|
||||
|
||||
Health Score: $(calculate_health_score "$total_vulns" "$outdated" "$unused" "$duplicates")/10
|
||||
|
||||
==============================================
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
# Calculate health score (0-10)
|
||||
calculate_health_score() {
|
||||
local vulns="$1"
|
||||
local outdated="$2"
|
||||
local unused="$3"
|
||||
local duplicates="$4"
|
||||
|
||||
local score=10
|
||||
|
||||
# Deduct points for issues
|
||||
score=$((score - vulns)) # -1 per vulnerability
|
||||
score=$((score - outdated / 5)) # -1 per 5 outdated packages
|
||||
score=$((score - unused / 3)) # -1 per 3 unused packages
|
||||
score=$((score - duplicates / 2)) # -1 per 2 duplicates
|
||||
|
||||
# Ensure score is between 0 and 10
|
||||
if (( score < 0 )); then
|
||||
score=0
|
||||
fi
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
# Generate recommendations
|
||||
generate_recommendations() {
|
||||
local vulns="$1"
|
||||
local outdated="$2"
|
||||
local unused="$3"
|
||||
local duplicates="$4"
|
||||
|
||||
local recommendations="["
|
||||
|
||||
if (( vulns > 0 )); then
|
||||
recommendations+='{"priority":"critical","action":"Update packages with security vulnerabilities immediately"},'
|
||||
fi
|
||||
|
||||
if (( outdated > 10 )); then
|
||||
recommendations+='{"priority":"high","action":"Review and update outdated dependencies"},'
|
||||
fi
|
||||
|
||||
if (( unused > 5 )); then
|
||||
recommendations+='{"priority":"medium","action":"Remove unused dependencies to reduce bundle size"},'
|
||||
fi
|
||||
|
||||
if (( duplicates > 0 )); then
|
||||
recommendations+='{"priority":"medium","action":"Resolve duplicate dependencies with version conflicts"},'
|
||||
fi
|
||||
|
||||
# Remove trailing comma if exists
|
||||
recommendations="${recommendations%,}"
|
||||
recommendations+="]"
|
||||
|
||||
echo "$recommendations"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log_info "Starting dependency analysis..."
|
||||
|
||||
validate_input
|
||||
|
||||
# Detect package manager
|
||||
IFS='|' read -r pkg_manager dep_file <<< "$(detect_package_manager)"
|
||||
|
||||
if [[ "$pkg_manager" == "unknown" ]]; then
|
||||
log_error "Could not detect package manager"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Detected package manager: $pkg_manager"
|
||||
|
||||
# Gather metrics
|
||||
dep_counts=$(count_dependencies "$pkg_manager" "$dep_file")
|
||||
outdated=$(check_outdated "$pkg_manager")
|
||||
vulnerabilities=$(check_vulnerabilities "$pkg_manager")
|
||||
tree_depth=$(analyze_tree_depth "$pkg_manager")
|
||||
unused=$(find_unused "$pkg_manager")
|
||||
duplicates=$(check_duplicates "$pkg_manager")
|
||||
|
||||
# Generate report
|
||||
generate_report "$pkg_manager" "$dep_file" "$dep_counts" "$outdated" "$vulnerabilities" "$tree_depth" "$unused" "$duplicates"
|
||||
|
||||
log_info "Analysis complete"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user