#!/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 < 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 "$@"