2004 lines
54 KiB
Bash
2004 lines
54 KiB
Bash
|
|
#!/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
|
|||
|
|
# ============================================================================
|
|||
|
|
|
|||
|
|
# 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
|
|||
|
|
if yq eval '.envs.default' "$config_file" 2>/dev/null | grep -q -v '^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 < <(yq eval '.envs.default | to_entries | .[] | .key + "=" + .value' "$config_file" 2>/dev/null)
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Then load environment-specific variables (which can override defaults)
|
|||
|
|
if [[ "$current_env" != "default" ]]; then
|
|||
|
|
if yq eval ".envs.$current_env" "$config_file" 2>/dev/null | grep -q -v '^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 < <(yq eval ".envs.$current_env | to_entries | .[] | .key + \"=\" + .value" "$config_file" 2>/dev/null)
|
|||
|
|
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
|
|||
|
|
# Custom directory relative to config file directory
|
|||
|
|
local config_dir=$(dirname "$(realpath "${config_file}")")
|
|||
|
|
lib_dir="$config_dir/$custom_into"
|
|||
|
|
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
|
|||
|
|
local config_dir=$(dirname "$(realpath "${effective_config}")")
|
|||
|
|
lib_dir="$config_dir/$into"
|
|||
|
|
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=" |