The build_reference_tree() function was prepending config_dir to all 'into' paths without expanding environment variables or checking if the result was absolute. This caused paths like /workspace/ai//workspace/ComfyUI instead of /workspace/ComfyUI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2175 lines
60 KiB
Bash
Executable File
2175 lines
60 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# arty.sh - A bash library repository and release management system
|
||
# Combined functionality from arty.sh (repository management) and whip.sh (release cycle)
|
||
# Version: 1.0.0
|
||
|
||
set -euo pipefail
|
||
|
||
ARTY_CONFIG_FILE="${ARTY_CONFIG_FILE:-arty.yml}"
|
||
ARTY_ENV="${ARTY_ENV:-default}"
|
||
ARTY_DRY_RUN="${ARTY_DRY_RUN:-0}"
|
||
ARTY_VERBOSE="${ARTY_VERBOSE:-0}"
|
||
|
||
# Whip configuration
|
||
WHIP_CONFIG="${WHIP_CONFIG:-arty.yml}"
|
||
WHIP_HOOKS_DIR=".whip/hooks"
|
||
WHIP_CHANGELOG="${WHIP_CHANGELOG:-CHANGELOG.md}"
|
||
|
||
# Colors for output - only use colors if output is to a terminal or if FORCE_COLOR is set
|
||
export FORCE_COLOR=${FORCE_COLOR:-"1"}
|
||
if [[ "$FORCE_COLOR" = "0" ]]; then
|
||
export RED=''
|
||
export GREEN=''
|
||
export YELLOW=''
|
||
export BLUE=''
|
||
export CYAN=''
|
||
export MAGENTA=''
|
||
export BOLD=''
|
||
export NC=''
|
||
else
|
||
export RED='\033[0;31m'
|
||
export GREEN='\033[0;32m'
|
||
export YELLOW='\033[1;33m'
|
||
export BLUE='\033[0;34m'
|
||
export CYAN='\033[0;36m'
|
||
export MAGENTA='\033[0;35m'
|
||
export BOLD='\033[1m'
|
||
export NC='\033[0m'
|
||
fi
|
||
|
||
# Global array to track installation stack (prevent circular dependencies)
|
||
declare -g -A ARTY_INSTALL_STACK
|
||
|
||
# Logging functions
|
||
log_info() {
|
||
echo -e "${BLUE}[INFO]${NC} $1" >&2
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}[]${NC} $1" >&2
|
||
}
|
||
|
||
log_warn() {
|
||
echo -e "${YELLOW}[<5B>]${NC} $1" >&2
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}[]${NC} $1" >&2
|
||
}
|
||
|
||
log_debug() {
|
||
if [[ "$ARTY_VERBOSE" == "1" ]]; then
|
||
echo -e "${CYAN}[DEBUG]${NC} $1" >&2
|
||
fi
|
||
}
|
||
|
||
log_step() {
|
||
echo -e "${CYAN}[<5B>]${NC} $1" >&2
|
||
}
|
||
|
||
# ============================================================================
|
||
# ARTY.SH CORE FUNCTIONS - Repository Management
|
||
# ============================================================================
|
||
|
||
# Expand environment variables in a string with validation
|
||
# Supports nested expansion (e.g., $AI_ROOT/subdir where AI_ROOT contains another var)
|
||
# Fails if any undefined variables remain after expansion
|
||
expand_env_vars() {
|
||
local input="$1"
|
||
local max_iterations=10
|
||
local iteration=0
|
||
local expanded="$input"
|
||
|
||
# Keep expanding until no more variables or max iterations reached
|
||
while [[ "$expanded" =~ \$ ]] && [[ $iteration -lt $max_iterations ]]; do
|
||
# Use eval to expand variables
|
||
expanded=$(eval echo "$expanded" 2>/dev/null || echo "$expanded")
|
||
iteration=$((iteration+1))
|
||
done
|
||
|
||
# Check if any unexpanded variables remain
|
||
if [[ "$expanded" =~ \$[A-Za-z_][A-Za-z0-9_]* ]]; then
|
||
# Extract the undefined variable name for better error message
|
||
local undefined_var=$(echo "$expanded" | grep -o '\$[A-Za-z_][A-Za-z0-9_]*' | head -1)
|
||
log_error "Undefined environment variable in path: ${undefined_var}"
|
||
log_error " Original: $input"
|
||
log_error " Expanded: $expanded"
|
||
log_error ""
|
||
log_error "Available environment variables from arty.yml:"
|
||
if [[ -f "$ARTY_CONFIG_FILE" ]]; then
|
||
yq eval '.envs.default | keys | .[]' "$ARTY_CONFIG_FILE" 2>/dev/null | sed 's/^/ - /' || true
|
||
fi
|
||
return 1
|
||
fi
|
||
|
||
echo "$expanded"
|
||
}
|
||
|
||
# Load environment variables from arty.yml
|
||
load_env_vars() {
|
||
local config_file="${1:-$ARTY_CONFIG_FILE}"
|
||
|
||
if [[ ! -f "$config_file" ]]; then
|
||
return 0 # No config file, nothing to load
|
||
fi
|
||
|
||
# Check if YAML is valid
|
||
if ! yq eval '.' "$config_file" >/dev/null 2>&1; then
|
||
log_warn "Invalid YAML in config file, skipping env vars"
|
||
return 0
|
||
fi
|
||
|
||
# Check if envs section exists
|
||
local has_envs=$(yq eval '.envs' "$config_file" 2>/dev/null)
|
||
if [[ "$has_envs" == "null" ]] || [[ -z "$has_envs" ]]; then
|
||
return 0 # No envs section
|
||
fi
|
||
|
||
local current_env="$ARTY_ENV"
|
||
log_debug "Loading environment variables from '$current_env' environment"
|
||
|
||
# First load default variables if they exist
|
||
local default_envs=$(yq eval '.envs.default | to_entries | .[] | .key + "=" + .value' "$config_file" 2>/dev/null)
|
||
if [[ -n "$default_envs" ]] && [[ "$default_envs" != "null" ]]; then
|
||
while IFS='=' read -r key value; do
|
||
if [[ -n "$key" ]] && [[ "$key" != "null" ]] && [[ -n "$value" ]]; then
|
||
# Only export if not already set (for default env only)
|
||
if [[ "$current_env" == "default" ]] && [[ -n "${!key:-}" ]]; then
|
||
log_debug " Skipping $key (already set)"
|
||
continue
|
||
fi
|
||
export "$key=$value"
|
||
log_debug " Set $key (from default)"
|
||
fi
|
||
done <<< "$default_envs"
|
||
fi
|
||
|
||
# Then load environment-specific variables (which can override defaults)
|
||
if [[ "$current_env" != "default" ]]; then
|
||
local env_envs=$(yq eval ".envs.$current_env | to_entries | .[] | .key + \"=\" + .value" "$config_file" 2>/dev/null)
|
||
if [[ -n "$env_envs" ]] && [[ "$env_envs" != "null" ]]; then
|
||
while IFS='=' read -r key value; do
|
||
if [[ -n "$key" ]] && [[ "$key" != "null" ]] && [[ -n "$value" ]]; then
|
||
export "$key=$value"
|
||
log_debug " Set $key (from $current_env)"
|
||
fi
|
||
done <<< "$env_envs"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Check if yq is installed
|
||
check_yq() {
|
||
if ! command -v yq &>/dev/null; then
|
||
log_error "yq is not installed. Please install yq to use arty."
|
||
log_info "Visit https://github.com/mikefarah/yq for installation instructions"
|
||
log_info "Quick install: brew install yq (macOS) or see README.md"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Check if git is installed
|
||
check_dependencies() {
|
||
local missing=0
|
||
|
||
if ! command -v yq &>/dev/null; then
|
||
log_error "yq is not installed"
|
||
missing=1
|
||
fi
|
||
|
||
if ! command -v git &>/dev/null; then
|
||
log_error "git is not installed"
|
||
missing=1
|
||
fi
|
||
|
||
if [[ $missing -eq 1 ]]; then
|
||
log_error "Missing required dependencies"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# Initialize arty environment
|
||
init_arty() {
|
||
local arty_home="${ARTY_HOME:-$PWD/.arty}"
|
||
local libs_dir="${ARTY_LIBS_DIR:-$arty_home/libs}"
|
||
local bin_dir="${ARTY_BIN_DIR:-$arty_home/bin}"
|
||
|
||
if [[ ! -d "$arty_home" ]]; then
|
||
mkdir -p "$libs_dir"
|
||
mkdir -p "$bin_dir"
|
||
log_success "Initialized arty at $arty_home"
|
||
fi
|
||
}
|
||
|
||
# Get a field from YAML using yq
|
||
get_yaml_field() {
|
||
local file="$1"
|
||
local field="$2"
|
||
|
||
if [[ ! -f "$file" ]]; then
|
||
return 1
|
||
fi
|
||
|
||
# Check if file has valid YAML - yq will exit with error on invalid YAML
|
||
if ! yq eval '.' "$file" >/dev/null 2>&1; then
|
||
return 1
|
||
fi
|
||
|
||
yq eval ".$field" "$file" 2>/dev/null || echo ""
|
||
}
|
||
|
||
# Get array items from YAML using yq
|
||
get_yaml_array() {
|
||
local file="$1"
|
||
local field="$2"
|
||
|
||
if [[ ! -f "$file" ]]; then
|
||
return 1
|
||
fi
|
||
|
||
# Check if file has valid YAML - yq will exit with error on invalid YAML
|
||
if ! yq eval '.' "$file" >/dev/null 2>&1; then
|
||
return 1
|
||
fi
|
||
|
||
yq eval ".${field}[]" "$file" 2>/dev/null
|
||
}
|
||
|
||
# Get script command from YAML
|
||
get_yaml_script() {
|
||
local file="$1"
|
||
local script_name="$2"
|
||
|
||
if [[ ! -f "$file" ]]; then
|
||
return 1
|
||
fi
|
||
|
||
yq eval ".scripts.${script_name}" "$file" 2>/dev/null || echo "null"
|
||
}
|
||
|
||
# List all script names from YAML
|
||
list_yaml_scripts() {
|
||
local file="$1"
|
||
|
||
if [[ ! -f "$file" ]]; then
|
||
return 1
|
||
fi
|
||
|
||
yq eval '.scripts | keys | .[]' "$file" 2>/dev/null
|
||
}
|
||
|
||
# Get library name from repository URL
|
||
get_lib_name() {
|
||
local repo_url="$1"
|
||
basename "$repo_url" .git
|
||
}
|
||
|
||
# Parse reference - can be a string URL or an object with url, into, ref, env
|
||
# Returns: url|into|ref|env (pipe-delimited)
|
||
# env can be a single value or comma-separated list
|
||
parse_reference() {
|
||
local config_file="$1"
|
||
local ref_index="$2"
|
||
|
||
# Check if reference is a string or object
|
||
local ref_type=$(yq eval ".references[$ref_index] | type" "$config_file" 2>/dev/null)
|
||
|
||
if [[ "$ref_type" == "!!str" ]]; then
|
||
# Simple string format: just the URL
|
||
local url=$(yq eval ".references[$ref_index]" "$config_file" 2>/dev/null)
|
||
echo "$url||||"
|
||
else
|
||
# Object format with url, into, ref, env fields
|
||
local url=$(yq eval ".references[$ref_index].url" "$config_file" 2>/dev/null)
|
||
local into=$(yq eval ".references[$ref_index].into" "$config_file" 2>/dev/null)
|
||
local ref=$(yq eval ".references[$ref_index].ref" "$config_file" 2>/dev/null)
|
||
|
||
# Check if env is an array or string
|
||
local env_type=$(yq eval ".references[$ref_index].env | type" "$config_file" 2>/dev/null)
|
||
local env=""
|
||
|
||
if [[ "$env_type" == "!!seq" ]]; then
|
||
# Array format - convert to comma-separated string
|
||
env=$(yq eval ".references[$ref_index].env | join(\",\")" "$config_file" 2>/dev/null)
|
||
else
|
||
# Single value or null
|
||
env=$(yq eval ".references[$ref_index].env" "$config_file" 2>/dev/null)
|
||
fi
|
||
|
||
# Replace "null" with empty string
|
||
[[ "$url" == "null" ]] && url=""
|
||
[[ "$into" == "null" ]] && into=""
|
||
[[ "$ref" == "null" ]] && ref=""
|
||
[[ "$env" == "null" ]] && env=""
|
||
|
||
echo "$url|$into|$ref|$env"
|
||
fi
|
||
}
|
||
|
||
# Check if current environment matches the filter
|
||
# env_filter can be a single env or comma-separated list
|
||
check_env_match() {
|
||
local current_env="$1"
|
||
local env_filter="$2"
|
||
|
||
# No filter means match all
|
||
[[ -z "$env_filter" ]] && return 0
|
||
|
||
# Convert comma-separated list to array
|
||
IFS=',' read -ra env_list <<< "$env_filter"
|
||
|
||
# Check if current env is in the list
|
||
for env in "${env_list[@]}"; do
|
||
# Trim whitespace
|
||
env=$(echo "$env" | xargs)
|
||
if [[ "$env" == "$current_env" ]]; then
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
return 1
|
||
}
|
||
|
||
# Get git information for a repository
|
||
get_git_info() {
|
||
local repo_dir="$1"
|
||
|
||
if [[ ! -d "$repo_dir/.git" ]]; then
|
||
echo "|||0"
|
||
return
|
||
fi
|
||
|
||
# Get short commit hash
|
||
local commit_hash=$(cd "$repo_dir" && git rev-parse --short HEAD 2>/dev/null || echo "")
|
||
|
||
# Get all refs pointing to current commit (tags, branches)
|
||
local refs=$(cd "$repo_dir" && git describe --all --exact-match 2>/dev/null || git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
||
|
||
# Clean up refs (remove heads/ and tags/ prefixes)
|
||
refs=$(echo "$refs" | sed 's#^heads/##' | sed 's#^tags/##')
|
||
|
||
# Check if dirty (has uncommitted changes)
|
||
local is_dirty=0
|
||
if [[ -n "$(cd "$repo_dir" && git status --porcelain 2>/dev/null)" ]]; then
|
||
is_dirty=1
|
||
fi
|
||
|
||
echo "$commit_hash|$refs|$is_dirty"
|
||
}
|
||
|
||
# Normalize library identifier for tracking
|
||
normalize_lib_id() {
|
||
local repo_url="$1"
|
||
# Convert to lowercase and remove .git suffix for consistent tracking
|
||
local normalized="${repo_url,,}"
|
||
normalized=$(echo "$normalized" | sed 's/\.git$//')
|
||
|
||
# Normalize different Git URL formats to the same path
|
||
# https://github.com/user/repo -> github.com/user/repo
|
||
# git@github.com:user/repo -> github.com/user/repo
|
||
# ssh://git@github.com/user/repo -> github.com/user/repo
|
||
normalized=$(echo "$normalized" | sed -E 's#^https?://##' | sed -E 's#^git@([^:]+):#\1/#' | sed -E 's#^ssh://git@##')
|
||
|
||
echo "$normalized"
|
||
}
|
||
|
||
# Check if library is in installation stack
|
||
is_installing() {
|
||
local lib_id="$1"
|
||
[[ -n "${ARTY_INSTALL_STACK[$lib_id]:-}" ]]
|
||
}
|
||
|
||
# Add library to installation stack
|
||
mark_installing() {
|
||
local lib_id="$1"
|
||
ARTY_INSTALL_STACK[$lib_id]=1
|
||
}
|
||
|
||
# Remove library from installation stack
|
||
unmark_installing() {
|
||
local lib_id="$1"
|
||
unset ARTY_INSTALL_STACK[$lib_id]
|
||
}
|
||
|
||
# Check if library is already installed
|
||
is_installed() {
|
||
local lib_name="$1"
|
||
local libs_dir="${ARTY_LIBS_DIR:-${ARTY_HOME:-$PWD/.arty}/libs}"
|
||
[[ -d "$libs_dir/$lib_name" ]]
|
||
}
|
||
|
||
# Install a library from git repository
|
||
install_lib() {
|
||
local repo_url="$1"
|
||
local lib_name="${2:-$(get_lib_name "$repo_url")}"
|
||
local git_ref="${3:-main}"
|
||
local custom_into="${4:-}"
|
||
local config_file="${5:-$ARTY_CONFIG_FILE}"
|
||
|
||
# Determine installation directory
|
||
local lib_dir
|
||
if [[ -n "$custom_into" ]]; then
|
||
# Expand environment variables in custom_into path
|
||
local expanded_into
|
||
if ! expanded_into=$(expand_env_vars "$custom_into"); then
|
||
log_error "Failed to expand environment variables in 'into' path for ${repo_url}"
|
||
return 1
|
||
fi
|
||
|
||
# Check if expanded path is absolute or relative
|
||
if [[ "$expanded_into" == /* ]]; then
|
||
# Absolute path - use as-is
|
||
lib_dir="$expanded_into"
|
||
else
|
||
# Relative path - make relative to config file directory
|
||
local config_dir=$(dirname "$(realpath "${config_file}")")
|
||
lib_dir="$config_dir/$expanded_into"
|
||
fi
|
||
else
|
||
# Use global .arty/libs for libraries without custom 'into'
|
||
lib_dir="$ARTY_LIBS_DIR/$lib_name"
|
||
fi
|
||
|
||
# Normalize the library identifier for circular dependency detection
|
||
# Include installation path to allow same library at different locations
|
||
local lib_id=$(normalize_lib_id "$repo_url")
|
||
local lib_id_with_path="${lib_id}@${lib_dir}"
|
||
|
||
# Check for circular dependency
|
||
if is_installing "$lib_id_with_path"; then
|
||
log_warn "Circular dependency detected: $lib_name (already being installed at $lib_dir)"
|
||
log_info "Skipping to prevent infinite loop"
|
||
return 0
|
||
fi
|
||
|
||
# Check if already installed (optimization)
|
||
if [[ -d "$lib_dir" ]]; then
|
||
log_info "Library '$lib_name' already installed at $lib_dir"
|
||
|
||
if [[ "$ARTY_DRY_RUN" == "1" ]]; then
|
||
log_info "[DRY RUN] Would check for updates..."
|
||
return 0
|
||
fi
|
||
|
||
# Try to update
|
||
(cd "$lib_dir" && git fetch -q && git checkout -q "$git_ref" && git pull -q) || {
|
||
log_warn "Failed to update library (continuing with existing version)"
|
||
}
|
||
|
||
return 0
|
||
fi
|
||
|
||
# Mark as currently installing
|
||
mark_installing "$lib_id_with_path"
|
||
|
||
if [[ "$ARTY_DRY_RUN" != "1" ]]; then
|
||
init_arty
|
||
fi
|
||
|
||
log_info "Installing library: $lib_name"
|
||
log_info "Repository: $repo_url"
|
||
log_info "Git ref: $git_ref"
|
||
log_info "Location: $lib_dir"
|
||
|
||
if [[ "$ARTY_DRY_RUN" == "1" ]]; then
|
||
log_info "[DRY RUN] Would clone repository and checkout $git_ref"
|
||
unmark_installing "$lib_id_with_path"
|
||
return 0
|
||
fi
|
||
|
||
# Clone the repository
|
||
git clone "$repo_url" "$lib_dir" || {
|
||
log_error "Failed to clone repository"
|
||
unmark_installing "$lib_id_with_path"
|
||
return 1
|
||
}
|
||
|
||
# Checkout the specified ref
|
||
(cd "$lib_dir" && git checkout -q "$git_ref") || {
|
||
log_warn "Failed to checkout ref '$git_ref', using default branch"
|
||
}
|
||
|
||
# Run setup hook if exists
|
||
if [[ -f "$lib_dir/setup.sh" ]]; then
|
||
log_info "Running setup hook..."
|
||
(cd "$lib_dir" && bash setup.sh) || {
|
||
log_warn "Setup hook failed, continuing anyway..."
|
||
}
|
||
fi
|
||
|
||
# Process arty.yml if it exists
|
||
if [[ -f "$lib_dir/arty.yml" ]]; then
|
||
# Link main script to .arty/bin (only for standard installations, not custom 'into')
|
||
if [[ -z "$custom_into" ]]; then
|
||
local main_script=$(get_yaml_field "$lib_dir/arty.yml" "main")
|
||
if [[ -n "$main_script" ]] && [[ "$main_script" != "null" ]]; then
|
||
local main_file="$lib_dir/$main_script"
|
||
if [[ -f "$main_file" ]]; then
|
||
local local_bin_dir="$ARTY_BIN_DIR"
|
||
local lib_name_stripped="$(basename $main_file .sh)"
|
||
local bin_link="$local_bin_dir/$lib_name_stripped"
|
||
|
||
log_info "Linking main script: $main_script -> $bin_link"
|
||
ln -sf "$main_file" "$bin_link"
|
||
chmod +x "$main_file"
|
||
log_success "Main script linked to $bin_link"
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Install nested dependencies (always, regardless of 'into')
|
||
log_info "Found arty.yml, checking for references..."
|
||
install_references "$lib_dir/arty.yml"
|
||
fi
|
||
|
||
# Unmark as installing (we're done with this library)
|
||
unmark_installing "$lib_id_with_path"
|
||
|
||
log_success "Library '$lib_name' installed successfully"
|
||
log_info "Location: $lib_dir"
|
||
}
|
||
|
||
# Find if a library is defined in current or ancestor configs with 'into'
|
||
find_ancestor_into() {
|
||
local lib_url="$1"
|
||
local current_config="$2"
|
||
local lib_url_normalized=$(normalize_lib_id "$lib_url")
|
||
|
||
# Build list of ancestor configs to check
|
||
# Start with current config's directory and walk up to find parent arty.yml files
|
||
local config_to_check="$current_config"
|
||
local current_dir=$(dirname "$(realpath "$current_config")")
|
||
|
||
# Check current config and walk up the directory tree
|
||
while true; do
|
||
if [[ -f "$config_to_check" ]]; then
|
||
# Get count of references
|
||
local ref_count=$(yq eval '.references | length' "$config_to_check" 2>/dev/null)
|
||
if [[ "$ref_count" != "null" ]] && [[ "$ref_count" != "0" ]]; then
|
||
# Check each reference
|
||
for ((i = 0; i < ref_count; i++)); do
|
||
local ref_data=$(parse_reference "$config_to_check" "$i")
|
||
IFS='|' read -r url into git_ref env_filter <<<"$ref_data"
|
||
|
||
local url_normalized=$(normalize_lib_id "$url")
|
||
if [[ "$url_normalized" == "$lib_url_normalized" ]] && [[ -n "$into" ]]; then
|
||
# Found it with an 'into' directive
|
||
echo "$into|$config_to_check"
|
||
return
|
||
fi
|
||
done
|
||
fi
|
||
fi
|
||
|
||
# Check if we've reached the root ARTY_CONFIG_FILE (after checking it)
|
||
if [[ "$(realpath "$config_to_check" 2>/dev/null)" == "$(realpath "$ARTY_CONFIG_FILE" 2>/dev/null)" ]]; then
|
||
# We've checked the root config, stop here
|
||
break
|
||
fi
|
||
|
||
# Move up one directory
|
||
local parent_dir=$(dirname "$current_dir")
|
||
if [[ "$parent_dir" == "$current_dir" ]] || [[ "$parent_dir" == "/" ]]; then
|
||
# Reached filesystem root, stop
|
||
break
|
||
fi
|
||
current_dir="$parent_dir"
|
||
config_to_check="$current_dir/arty.yml"
|
||
done
|
||
|
||
echo ""
|
||
}
|
||
|
||
# Install all references from arty.yml
|
||
install_references() {
|
||
local config_file="${1:-$ARTY_CONFIG_FILE}"
|
||
|
||
if [[ ! -f "$config_file" ]]; then
|
||
log_error "Config file not found: $config_file"
|
||
return 1
|
||
fi
|
||
|
||
# Check if YAML is valid by trying to read it
|
||
if ! yq eval '.' "$config_file" >/dev/null 2>&1; then
|
||
log_error "Invalid YAML in config file: $config_file"
|
||
return 1
|
||
fi
|
||
|
||
# Initialize arty directory structure first (unless dry run)
|
||
if [[ "$ARTY_DRY_RUN" != "1" ]]; then
|
||
init_arty
|
||
fi
|
||
|
||
# Count references
|
||
local ref_count=$(yq eval '.references | length' "$config_file" 2>/dev/null)
|
||
if [[ "$ref_count" == "null" ]] || [[ "$ref_count" == "0" ]]; then
|
||
log_info "No references to install"
|
||
return 0
|
||
fi
|
||
|
||
# Process each reference by index
|
||
local i
|
||
for ((i = 0; i < ref_count; i++)); do
|
||
# Parse the reference
|
||
local ref_data=$(parse_reference "$config_file" "$i")
|
||
IFS='|' read -r url into git_ref env_filter <<<"$ref_data"
|
||
|
||
# Skip empty URLs
|
||
if [[ -z "$url" ]] || [[ "$url" == "null" ]]; then
|
||
continue
|
||
fi
|
||
|
||
# Check environment filter using the new check_env_match function
|
||
if [[ -n "$env_filter" ]] && ! check_env_match "$ARTY_ENV" "$env_filter"; then
|
||
log_info "Skipping reference (env filter: [$env_filter], current: $ARTY_ENV): $url"
|
||
continue
|
||
fi
|
||
|
||
# Use default ref if not specified
|
||
[[ -z "$git_ref" ]] && git_ref="main"
|
||
|
||
# Get library name
|
||
local lib_name=$(get_lib_name "$url")
|
||
|
||
# If no 'into' specified, check if an ancestor config defines it with 'into'
|
||
local effective_config="$config_file"
|
||
if [[ -z "$into" ]] && [[ "$config_file" != "$ARTY_CONFIG_FILE" ]]; then
|
||
local ancestor_result=$(find_ancestor_into "$url" "$config_file")
|
||
if [[ -n "$ancestor_result" ]]; then
|
||
IFS='|' read -r ancestor_into ancestor_config <<<"$ancestor_result"
|
||
into="$ancestor_into"
|
||
effective_config="$ancestor_config"
|
||
fi
|
||
fi
|
||
|
||
log_info "Installing reference: $url"
|
||
[[ -n "$into" ]] && log_info "Custom location: $into"
|
||
[[ -n "$env_filter" ]] && log_info "Environment filter: [$env_filter]"
|
||
|
||
install_lib "$url" "$lib_name" "$git_ref" "$into" "$effective_config" || log_warn "Failed to install reference: $url"
|
||
done
|
||
}
|
||
|
||
# Build a reference tree from arty.yml showing all dependencies
|
||
build_reference_tree() {
|
||
local config_file="${1:-$ARTY_CONFIG_FILE}"
|
||
local indent="${2:-}"
|
||
local is_last="${3:-1}"
|
||
local visited_file="${4:-/tmp/arty_visited_$$}"
|
||
local depth="${5:-0}"
|
||
|
||
if [[ ! -f "$config_file" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
# Track visited library directories to prevent infinite circular recursion
|
||
# Only track at depth > 0 to allow root-level refs to show even if already nested
|
||
local config_dir=$(dirname "$(realpath "$config_file" 2>/dev/null || echo "$config_file")")
|
||
if [[ "$depth" -gt 0 ]]; then
|
||
if grep -qx "$config_dir" "$visited_file" 2>/dev/null; then
|
||
return 0
|
||
fi
|
||
echo "$config_dir" >>"$visited_file"
|
||
fi
|
||
|
||
# Count references
|
||
local ref_count=$(yq eval '.references | length' "$config_file" 2>/dev/null)
|
||
if [[ "$ref_count" == "null" ]] || [[ "$ref_count" == "0" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
# Process each reference
|
||
local i
|
||
for ((i = 0; i < ref_count; i++)); do
|
||
local ref_data=$(parse_reference "$config_file" "$i")
|
||
IFS='|' read -r url into git_ref env_filter <<<"$ref_data"
|
||
|
||
# Skip if no URL or env filter doesn't match
|
||
if [[ -z "$url" ]] || [[ "$url" == "null" ]]; then
|
||
continue
|
||
fi
|
||
if [[ -n "$env_filter" ]] && ! check_env_match "$ARTY_ENV" "$env_filter"; then
|
||
continue
|
||
fi
|
||
|
||
local lib_name=$(get_lib_name "$url")
|
||
|
||
# Calculate is_last_ref by counting remaining valid refs
|
||
local is_last_ref=0
|
||
local remaining=0
|
||
for ((j = i + 1; j < ref_count; j++)); do
|
||
local check_ref=$(parse_reference "$config_file" "$j")
|
||
IFS='|' read -r check_url _ _ check_env <<<"$check_ref"
|
||
if [[ -n "$check_url" ]] && [[ "$check_url" != "null" ]]; then
|
||
if [[ -z "$check_env" ]] || check_env_match "$ARTY_ENV" "$check_env"; then
|
||
remaining=1
|
||
break
|
||
fi
|
||
fi
|
||
done
|
||
[[ "$remaining" == "0" ]] && is_last_ref=1
|
||
|
||
# If no 'into' specified, check if an ancestor config defines it with 'into'
|
||
local effective_config="$config_file"
|
||
if [[ -z "$into" ]] && [[ "$config_file" != "$ARTY_CONFIG_FILE" ]]; then
|
||
local ancestor_result=$(find_ancestor_into "$url" "$config_file")
|
||
if [[ -n "$ancestor_result" ]]; then
|
||
IFS='|' read -r ancestor_into ancestor_config <<<"$ancestor_result"
|
||
into="$ancestor_into"
|
||
effective_config="$ancestor_config"
|
||
fi
|
||
fi
|
||
|
||
# Determine installation directory
|
||
local lib_dir
|
||
if [[ -n "$into" ]]; then
|
||
# Expand environment variables
|
||
local expanded_into
|
||
expanded_into=$(expand_env_vars "$into" 2>/dev/null) || expanded_into="$into"
|
||
|
||
# Check if expanded path is absolute
|
||
if [[ "$expanded_into" == /* ]]; then
|
||
lib_dir="$expanded_into"
|
||
else
|
||
local config_dir=$(dirname "$(realpath "${effective_config}")")
|
||
lib_dir="$config_dir/$expanded_into"
|
||
fi
|
||
else
|
||
lib_dir="$ARTY_LIBS_DIR/$lib_name"
|
||
fi
|
||
|
||
# Get version and git info
|
||
local version=""
|
||
local location="$lib_dir"
|
||
|
||
if [[ -f "$lib_dir/arty.yml" ]]; then
|
||
version=$(get_yaml_field "$lib_dir/arty.yml" "version")
|
||
[[ "$version" == "null" ]] || [[ -z "$version" ]] && version=""
|
||
fi
|
||
|
||
# Get git information
|
||
local git_info=$(get_git_info "$lib_dir")
|
||
IFS='|' read -r commit_hash git_refs is_dirty <<<"$git_info"
|
||
|
||
# Determine display version
|
||
local display_version="${version:-$commit_hash}"
|
||
[[ -z "$display_version" ]] && display_version="unknown"
|
||
|
||
# Tree characters
|
||
local tree_char=" |