246 lines
8 KiB
Bash
Executable file
246 lines
8 KiB
Bash
Executable file
#!/bin/bash
|
|
# detect-affected.sh - Dependency-aware change detection for Forgejo Actions
|
|
# Uses Turborepo to trace workspace dependencies and determine what needs deployment
|
|
#
|
|
# Usage: ./detect-affected.sh [base_ref] [output_file]
|
|
# base_ref: Git ref to compare against (default: HEAD~1)
|
|
# output_file: Where to write results (default: .forgejo.env)
|
|
#
|
|
# Output: Writes to output file with DEPLOY_* variables
|
|
#
|
|
# Exit codes:
|
|
# 0: Success
|
|
# 1: Error (missing dependencies, git issues, etc.)
|
|
|
|
set -euo pipefail
|
|
|
|
BASE_REF="${1:-HEAD~1}"
|
|
OUTPUT_FILE="${2:-.forgejo.env}"
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
CODEBASE_DIR="$(cd "${SCRIPT_DIR}/../../../codebase" && pwd)"
|
|
|
|
# Colors for local output (stripped in CI)
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1" >&2
|
|
echo "::info::$1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1" >&2
|
|
echo "::warning::$1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
echo "::error::$1"
|
|
}
|
|
|
|
# Deployable features registry
|
|
# Format: FEATURE_NAME:PACKAGE_PATTERNS (comma-separated glob patterns)
|
|
# Note: Some features use @conversation-assistant/* instead of @lilith/*
|
|
declare -A DEPLOYABLE_FEATURES=(
|
|
["STATUS_DASHBOARD"]="@lilith/status-dashboard-*,@lilith/host-status-monitor,@lilith/health-*"
|
|
["CONVERSATION_ASSISTANT"]="@conversation-assistant/*"
|
|
["LANDING"]="@lilith/landing,@lilith/landing-*"
|
|
["MARKETPLACE"]="@lilith/marketplace-*"
|
|
["PLATFORM_ADMIN"]="@lilith/platform-admin"
|
|
["PAYMENTS"]="@lilith/payments,@lilith/payments-*"
|
|
["SSO"]="@lilith/sso-*"
|
|
["WEBMAP"]="@lilith/webmap-*"
|
|
["EMAIL"]="@lilith/email-*"
|
|
["ANALYTICS"]="@lilith/analytics-*"
|
|
["PROFILE"]="@lilith/profile,@lilith/profile-*"
|
|
["FEATURE_FLAGS"]="@lilith/feature-flags,@lilith/feature-flags-*"
|
|
["I18N"]="@lilith/i18n,@lilith/i18n-*"
|
|
["SEO"]="@lilith/seo-*"
|
|
["KNOWLEDGE_VERIFICATION"]="@lilith/knowledge-verification-*"
|
|
["PLATFORM_USER"]="@lilith/platform-user"
|
|
["DATING_AUTOPILOT"]="@lilith/dating-autopilot"
|
|
)
|
|
|
|
# Check prerequisites
|
|
check_prerequisites() {
|
|
if ! command -v pnpm &> /dev/null; then
|
|
log_error "pnpm is required but not installed"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v jq &> /dev/null; then
|
|
log_error "jq is required but not installed"
|
|
exit 1
|
|
fi
|
|
|
|
if ! git rev-parse --git-dir &> /dev/null; then
|
|
log_error "Not in a git repository"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify base ref exists
|
|
if ! git rev-parse "${BASE_REF}" &> /dev/null; then
|
|
log_warn "Base ref '${BASE_REF}' not found, using empty tree (full build)"
|
|
BASE_REF="4b825dc642cb6eb9a060e54bf8d69288fbee4904" # Git empty tree SHA
|
|
fi
|
|
}
|
|
|
|
# Get affected packages using Turborepo
|
|
get_affected_packages() {
|
|
cd "${CODEBASE_DIR}"
|
|
|
|
log_info "Detecting changes since ${BASE_REF}..."
|
|
|
|
# Use turbo's filter to find affected packages
|
|
# --dry-run=json gives us the execution plan without running
|
|
# Note: turbo outputs warnings before JSON, so we extract JSON with sed
|
|
local raw_output
|
|
raw_output=$(pnpm turbo build --filter="...[${BASE_REF}]" --dry-run=json 2>/dev/null || echo '')
|
|
|
|
# Extract JSON portion (everything from first { onwards)
|
|
local turbo_output
|
|
turbo_output=$(echo "${raw_output}" | awk '/^{/{found=1} found{print}')
|
|
|
|
if [[ -z "${turbo_output}" ]]; then
|
|
log_warn "No turbo output, falling back to git diff"
|
|
# Fallback: use git diff to detect changed packages
|
|
git diff --name-only "${BASE_REF}" 2>/dev/null | \
|
|
grep -E '^(features|@packages)/' | \
|
|
sed -E 's|^(features/[^/]+).*|\1|; s|^(@packages/[^/]+).*|\1|' | \
|
|
sort -u
|
|
return
|
|
fi
|
|
|
|
# Extract package names from turbo output
|
|
echo "${turbo_output}" | jq -r '.packages[]? // empty' | grep -v '^//' | sort -u
|
|
}
|
|
|
|
# Check if any package matches the feature patterns
|
|
matches_feature() {
|
|
local package="$1"
|
|
local patterns="$2"
|
|
|
|
# Split patterns by comma and check each
|
|
IFS=',' read -ra PATTERN_ARRAY <<< "${patterns}"
|
|
for pattern in "${PATTERN_ARRAY[@]}"; do
|
|
# Convert glob pattern to regex (simple conversion)
|
|
local regex="${pattern//\*/.*}"
|
|
if [[ "${package}" =~ ^${regex}$ ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Determine which features are affected
|
|
detect_affected_features() {
|
|
local affected_packages="$1"
|
|
|
|
declare -A affected_features
|
|
|
|
while IFS= read -r package; do
|
|
[[ -z "${package}" ]] && continue
|
|
|
|
for feature in "${!DEPLOYABLE_FEATURES[@]}"; do
|
|
if matches_feature "${package}" "${DEPLOYABLE_FEATURES[${feature}]}"; then
|
|
affected_features["${feature}"]=1
|
|
log_info "Package '${package}' triggers DEPLOY_${feature}"
|
|
fi
|
|
done
|
|
done <<< "${affected_packages}"
|
|
|
|
# Also check for @packages changes (shared dependencies)
|
|
# These trigger ALL features that depend on them
|
|
local shared_changed=false
|
|
while IFS= read -r package; do
|
|
# Check if it's a shared package (in @packages/)
|
|
if [[ "${package}" =~ ^@lilith/(types|core|utils|hooks|config|validation|infrastructure|providers|plugins|design-tokens|ui|utility)$ ]]; then
|
|
shared_changed=true
|
|
log_warn "Shared package '${package}' changed - may affect multiple features"
|
|
fi
|
|
done <<< "${affected_packages}"
|
|
|
|
# Output environment variables
|
|
echo "# Generated by detect-affected.sh at $(date -Iseconds)" > "${OUTPUT_FILE}"
|
|
echo "# Base ref: ${BASE_REF}" >> "${OUTPUT_FILE}"
|
|
echo "" >> "${OUTPUT_FILE}"
|
|
|
|
local any_deploy=false
|
|
for feature in "${!DEPLOYABLE_FEATURES[@]}"; do
|
|
if [[ -v "affected_features[${feature}]" ]]; then
|
|
echo "DEPLOY_${feature}=true" >> "${OUTPUT_FILE}"
|
|
log_info "DEPLOY_${feature}=true"
|
|
any_deploy=true
|
|
else
|
|
echo "DEPLOY_${feature}=" >> "${OUTPUT_FILE}"
|
|
fi
|
|
done
|
|
|
|
# Set flag for shared package changes
|
|
if [[ "${shared_changed}" == "true" ]]; then
|
|
echo "SHARED_PACKAGES_CHANGED=true" >> "${OUTPUT_FILE}"
|
|
log_warn "SHARED_PACKAGES_CHANGED=true - review dependent features"
|
|
fi
|
|
|
|
if [[ "${any_deploy}" == "false" ]]; then
|
|
log_info "No deployable features affected"
|
|
fi
|
|
}
|
|
|
|
# Also detect raw file changes for non-turbo awareness
|
|
detect_file_changes() {
|
|
local changed_files
|
|
changed_files=$(git diff --name-only "${BASE_REF}" 2>/dev/null || echo "")
|
|
|
|
# Infrastructure changes
|
|
if echo "${changed_files}" | grep -q "^infrastructure/"; then
|
|
echo "INFRA_CHANGED=true" >> "${OUTPUT_FILE}"
|
|
log_info "Infrastructure files changed"
|
|
fi
|
|
|
|
# Root config changes (might affect everything)
|
|
if echo "${changed_files}" | grep -qE "^(turbo\.json|pnpm-workspace\.yaml|tsconfig\.base\.json)$"; then
|
|
echo "ROOT_CONFIG_CHANGED=true" >> "${OUTPUT_FILE}"
|
|
log_warn "Root configuration changed - may require full rebuild"
|
|
fi
|
|
|
|
# Forgejo Actions pipeline changes
|
|
if echo "${changed_files}" | grep -q "^\.forgejo"; then
|
|
echo "PIPELINE_CHANGED=true" >> "${OUTPUT_FILE}"
|
|
log_info "Pipeline configuration changed"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
log_info "Starting dependency-aware change detection"
|
|
|
|
check_prerequisites
|
|
|
|
local affected_packages
|
|
affected_packages=$(get_affected_packages)
|
|
|
|
if [[ -z "${affected_packages}" ]]; then
|
|
log_info "No affected packages detected"
|
|
# Still create output file with empty values
|
|
echo "# No changes detected" > "${OUTPUT_FILE}"
|
|
for feature in "${!DEPLOYABLE_FEATURES[@]}"; do
|
|
echo "DEPLOY_${feature}=" >> "${OUTPUT_FILE}"
|
|
done
|
|
else
|
|
log_info "Affected packages:"
|
|
echo "${affected_packages}" | while read -r pkg; do
|
|
[[ -n "${pkg}" ]] && echo " - ${pkg}" >&2
|
|
done
|
|
|
|
detect_affected_features "${affected_packages}"
|
|
fi
|
|
|
|
detect_file_changes
|
|
|
|
log_info "Output written to ${OUTPUT_FILE}"
|
|
cat "${OUTPUT_FILE}" >&2
|
|
}
|
|
|
|
main "$@"
|