Replace static JSON config with runtime dependency discovery by grepping package.json files. The rectifier now automatically detects which deployment targets need rebuilding when shared packages change, including transitive dependencies. Changes: - Add lib/dependency-graph.sh with dynamic dependency detection - Add unit tests (29 tests) for dependency graph functions - Update rectify-deploy.sh to use dynamic detection - Remove need for manual dependency configuration How it works: 1. Extract package name from changed file path 2. Grep package.json files to find dependents 3. Map dependents to deployment targets 4. Handle transitive deps (ui-utils -> ui-primitives -> targets) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
239 lines
7.5 KiB
Bash
Executable file
239 lines
7.5 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
|
|
infrastructure/service-registry/*)
|
|
echo "service-registry"
|
|
;;
|
|
features/status-dashboard/*)
|
|
echo "status-dashboard"
|
|
;;
|
|
*)
|
|
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=""
|
|
|
|
# service-registry dashboard
|
|
if grep -q "\"$pkg_name\"" "$PROJECT_ROOT/infrastructure/service-registry/apps/dashboard/package.json" 2>/dev/null; then
|
|
targets="$targets service-registry"
|
|
fi
|
|
|
|
# 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
|
|
|
|
# Handle infrastructure/service-registry packages
|
|
if [[ "$file_path" =~ ^infrastructure/service-registry/packages/@service-registry/([^/]+)/ ]]; then
|
|
echo "@service-registry/${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
|