platform-tooling/scripts/lib/dependency-graph.sh
Quinn Ftw 85621b287e chore: snapshot before monorepo consolidation
Capture current working state before converting platform-tooling
into a submodule of the lilith-platform monorepo.
2026-01-29 07:04:39 -08:00

228 lines
7.1 KiB
Bash
Executable file

#!/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