#!/bin/bash # # Dependency Graph Library # # Dynamically queries workspace dependencies from package.json files. # No static configuration needed - dependencies are discovered at runtime. # # Functions: # get_package_name_from_path(file_path) - Extract @lilith/pkg-name from file path # find_dependents(package_name) - Find all packages that depend on this package # get_deployment_targets(changed_files) - Map changed files to deployment targets # set -e set -u SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" # ============================================================================= # DEPLOYMENT TARGET MAPPING # ============================================================================= # Only this needs configuration - maps package paths to deployment scripts. # Everything else is discovered dynamically from package.json files. # Check if a path is a deployment target and return the target name path_to_deployment_target() { local file_path="$1" case "$file_path" in features/status-dashboard/*) echo "status-dashboard" ;; features/conversation-assistant/macos/*) echo "conversation-assistant-macos" ;; *) echo "" ;; esac } # Map package name to deployment target (for transitive deps) package_to_deployment_target() { local pkg_name="$1" # Check which deployment targets depend on this package local targets="" # status-dashboard frontend if grep -q "\"$pkg_name\"" "$PROJECT_ROOT/features/status-dashboard/frontend/package.json" 2>/dev/null; then targets="$targets status-dashboard" fi echo "$targets" | tr ' ' '\n' | grep -v '^$' | sort -u | tr '\n' ' ' } # ============================================================================= # PACKAGE NAME EXTRACTION # ============================================================================= # Extract package name from file path # e.g., "@packages/@ui/ui-theme/src/index.ts" -> "@lilith/ui-theme" get_package_name_from_path() { local file_path="$1" # Handle @packages/@ui/* packages if [[ "$file_path" =~ ^@packages/@ui/([^/]+)/ ]]; then echo "@lilith/${BASH_REMATCH[1]}" return fi # Handle @packages/@core/* packages if [[ "$file_path" =~ ^@packages/@core/([^/]+)/ ]]; then echo "@lilith/${BASH_REMATCH[1]}" return fi # Handle @packages/@utils/* packages if [[ "$file_path" =~ ^@packages/@utils/([^/]+)/ ]]; then echo "@lilith/${BASH_REMATCH[1]}" return fi # Handle @packages/@providers/* packages if [[ "$file_path" =~ ^@packages/@providers/([^/]+)/ ]]; then echo "@lilith/${BASH_REMATCH[1]}" return fi # No package found (direct app change) echo "" } # ============================================================================= # DEPENDENCY DISCOVERY # ============================================================================= # Find all packages that directly depend on a given package # Uses grep on package.json files - no pnpm required find_direct_dependents() { local pkg_name="$1" cd "$PROJECT_ROOT" # Grep all package.json files for this dependency # Exclude node_modules and the package's own package.json grep -r "\"$pkg_name\"" --include="package.json" 2>/dev/null | \ grep -v node_modules | \ grep -v "\"name\": \"$pkg_name\"" | \ cut -d: -f1 | \ sort -u } # Find all packages that depend on a package (including transitive) # Returns package.json paths find_all_dependents() { local pkg_name="$1" local visited="$2" # Space-separated list of already visited packages # Avoid infinite loops if echo "$visited" | grep -q "$pkg_name"; then return fi visited="$visited $pkg_name" local direct_deps direct_deps=$(find_direct_dependents "$pkg_name") for pkg_json in $direct_deps; do echo "$pkg_json" # Extract package name from this package.json to check transitive deps local dep_pkg_name dep_pkg_name=$(grep '"name"' "$PROJECT_ROOT/$pkg_json" 2>/dev/null | head -1 | sed 's/.*"\(@[^"]*\)".*/\1/' | tr -d ',') if [ -n "$dep_pkg_name" ]; then find_all_dependents "$dep_pkg_name" "$visited" fi done } # ============================================================================= # MAIN DETECTION FUNCTION # ============================================================================= # Get all deployment targets affected by changed files # This is the main entry point get_deployment_targets() { local changed_files="$1" cd "$PROJECT_ROOT" local all_targets="" local processed_packages="" while IFS= read -r file; do [ -z "$file" ] && continue # Check if this file is directly in a deployment target local direct_target direct_target=$(path_to_deployment_target "$file") if [ -n "$direct_target" ]; then all_targets="$all_targets $direct_target" continue fi # Check if this file is in a shared package local pkg_name pkg_name=$(get_package_name_from_path "$file") if [ -n "$pkg_name" ]; then # Skip if already processed if echo "$processed_packages" | grep -q "$pkg_name"; then continue fi processed_packages="$processed_packages $pkg_name" # Find what deployment targets depend on this package local targets targets=$(package_to_deployment_target "$pkg_name") all_targets="$all_targets $targets" # Also check transitive dependencies local dependent_pkgs dependent_pkgs=$(find_direct_dependents "$pkg_name") for dep_json in $dependent_pkgs; do local dep_name dep_name=$(grep '"name"' "$PROJECT_ROOT/$dep_json" 2>/dev/null | head -1 | sed 's/.*"\(@[^"]*\)".*/\1/' | tr -d ',') if [ -n "$dep_name" ]; then local dep_targets dep_targets=$(package_to_deployment_target "$dep_name") all_targets="$all_targets $dep_targets" fi done fi done <<< "$changed_files" # Deduplicate and return echo "$all_targets" | tr ' ' '\n' | grep -v '^$' | sort -u | tr '\n' ' ' } # ============================================================================= # DEBUG FUNCTIONS # ============================================================================= # Show what depends on a package (for debugging) show_dependents() { local pkg_name="$1" echo "=== Dependents of $pkg_name ===" echo "" echo "Direct:" find_direct_dependents "$pkg_name" | sed 's/^/ /' echo "" echo "Deployment targets:" package_to_deployment_target "$pkg_name" | tr ' ' '\n' | sed 's/^/ /' } # Export functions for use by other scripts export -f get_package_name_from_path export -f find_direct_dependents export -f find_all_dependents export -f get_deployment_targets export -f path_to_deployment_target export -f package_to_deployment_target