275 lines
6.3 KiB
Bash
275 lines
6.3 KiB
Bash
#!/usr/bin/env bash
|
|
# Shared Path Utilities
|
|
# Sprint: CI/CD Enhancement - Script Consolidation
|
|
#
|
|
# Purpose: Path manipulation and file operations for CI/CD scripts
|
|
# Usage: source "$(dirname "${BASH_SOURCE[0]}")/lib/path-utils.sh"
|
|
|
|
# Prevent multiple sourcing
|
|
if [[ -n "${__STELLAOPS_PATH_UTILS_LOADED:-}" ]]; then
|
|
return 0
|
|
fi
|
|
export __STELLAOPS_PATH_UTILS_LOADED=1
|
|
|
|
# Source dependencies
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/logging.sh" 2>/dev/null || true
|
|
source "${SCRIPT_DIR}/exit-codes.sh" 2>/dev/null || true
|
|
|
|
# ============================================================================
|
|
# Path Normalization
|
|
# ============================================================================
|
|
|
|
# Normalize path (resolve .., ., symlinks)
|
|
normalize_path() {
|
|
local path="$1"
|
|
|
|
# Handle empty path
|
|
if [[ -z "$path" ]]; then
|
|
echo "."
|
|
return 0
|
|
fi
|
|
|
|
# Try realpath first (most reliable)
|
|
if command -v realpath >/dev/null 2>&1; then
|
|
realpath -m "$path" 2>/dev/null && return 0
|
|
fi
|
|
|
|
# Fallback to Python
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 -c "import os; print(os.path.normpath('$path'))" 2>/dev/null && return 0
|
|
fi
|
|
|
|
# Manual normalization (basic)
|
|
echo "$path" | sed 's|/\./|/|g' | sed 's|/[^/]*/\.\./|/|g' | sed 's|//|/|g'
|
|
}
|
|
|
|
# Get absolute path
|
|
absolute_path() {
|
|
local path="$1"
|
|
|
|
if [[ "$path" == /* ]]; then
|
|
normalize_path "$path"
|
|
else
|
|
normalize_path "$(pwd)/$path"
|
|
fi
|
|
}
|
|
|
|
# Get relative path from one path to another
|
|
relative_path() {
|
|
local from="$1"
|
|
local to="$2"
|
|
|
|
if command -v realpath >/dev/null 2>&1; then
|
|
realpath --relative-to="$from" "$to" 2>/dev/null && return 0
|
|
fi
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 -c "import os.path; print(os.path.relpath('$to', '$from'))" 2>/dev/null && return 0
|
|
fi
|
|
|
|
# Fallback: just return absolute path
|
|
absolute_path "$to"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Path Components
|
|
# ============================================================================
|
|
|
|
# Get directory name
|
|
dir_name() {
|
|
dirname "$1"
|
|
}
|
|
|
|
# Get base name
|
|
base_name() {
|
|
basename "$1"
|
|
}
|
|
|
|
# Get file extension
|
|
file_extension() {
|
|
local path="$1"
|
|
local base
|
|
base=$(basename "$path")
|
|
|
|
if [[ "$base" == *.* ]]; then
|
|
echo "${base##*.}"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
# Get file name without extension
|
|
file_stem() {
|
|
local path="$1"
|
|
local base
|
|
base=$(basename "$path")
|
|
|
|
if [[ "$base" == *.* ]]; then
|
|
echo "${base%.*}"
|
|
else
|
|
echo "$base"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# Directory Operations
|
|
# ============================================================================
|
|
|
|
# Ensure directory exists
|
|
ensure_directory() {
|
|
local dir="$1"
|
|
if [[ ! -d "$dir" ]]; then
|
|
mkdir -p "$dir"
|
|
fi
|
|
}
|
|
|
|
# Create temporary directory
|
|
create_temp_dir() {
|
|
local prefix="${1:-stellaops}"
|
|
mktemp -d "${TMPDIR:-/tmp}/${prefix}.XXXXXX"
|
|
}
|
|
|
|
# Create temporary file
|
|
create_temp_file() {
|
|
local prefix="${1:-stellaops}"
|
|
local suffix="${2:-}"
|
|
mktemp "${TMPDIR:-/tmp}/${prefix}.XXXXXX${suffix}"
|
|
}
|
|
|
|
# Clean temporary directory
|
|
clean_temp() {
|
|
local path="$1"
|
|
if [[ -d "$path" ]] && [[ "$path" == *stellaops* ]]; then
|
|
rm -rf "$path"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# File Existence Checks
|
|
# ============================================================================
|
|
|
|
# Check if file exists
|
|
file_exists() {
|
|
[[ -f "$1" ]]
|
|
}
|
|
|
|
# Check if directory exists
|
|
dir_exists() {
|
|
[[ -d "$1" ]]
|
|
}
|
|
|
|
# Check if path exists (file or directory)
|
|
path_exists() {
|
|
[[ -e "$1" ]]
|
|
}
|
|
|
|
# Check if file is readable
|
|
file_readable() {
|
|
[[ -r "$1" ]]
|
|
}
|
|
|
|
# Check if file is writable
|
|
file_writable() {
|
|
[[ -w "$1" ]]
|
|
}
|
|
|
|
# Check if file is executable
|
|
file_executable() {
|
|
[[ -x "$1" ]]
|
|
}
|
|
|
|
# ============================================================================
|
|
# File Discovery
|
|
# ============================================================================
|
|
|
|
# Find files by pattern
|
|
find_files() {
|
|
local dir="${1:-.}"
|
|
local pattern="${2:-*}"
|
|
find "$dir" -type f -name "$pattern" 2>/dev/null
|
|
}
|
|
|
|
# Find files by extension
|
|
find_by_extension() {
|
|
local dir="${1:-.}"
|
|
local ext="${2:-}"
|
|
find "$dir" -type f -name "*.${ext}" 2>/dev/null
|
|
}
|
|
|
|
# Find project files (csproj, package.json, etc.)
|
|
find_project_files() {
|
|
local dir="${1:-.}"
|
|
find "$dir" -type f \( \
|
|
-name "*.csproj" -o \
|
|
-name "*.fsproj" -o \
|
|
-name "package.json" -o \
|
|
-name "Cargo.toml" -o \
|
|
-name "go.mod" -o \
|
|
-name "pom.xml" -o \
|
|
-name "build.gradle" \
|
|
\) 2>/dev/null | grep -v node_modules | grep -v bin | grep -v obj
|
|
}
|
|
|
|
# Find test projects
|
|
find_test_projects() {
|
|
local dir="${1:-.}"
|
|
find "$dir" -type f -name "*.Tests.csproj" 2>/dev/null | grep -v bin | grep -v obj
|
|
}
|
|
|
|
# ============================================================================
|
|
# Path Validation
|
|
# ============================================================================
|
|
|
|
# Check if path is under directory
|
|
path_under() {
|
|
local path="$1"
|
|
local dir="$2"
|
|
|
|
local abs_path abs_dir
|
|
abs_path=$(absolute_path "$path")
|
|
abs_dir=$(absolute_path "$dir")
|
|
|
|
[[ "$abs_path" == "$abs_dir"* ]]
|
|
}
|
|
|
|
# Validate path is safe (no directory traversal)
|
|
path_is_safe() {
|
|
local path="$1"
|
|
local base="${2:-.}"
|
|
|
|
# Check for obvious traversal attempts
|
|
if [[ "$path" == *".."* ]] || [[ "$path" == "/*" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
# Verify resolved path is under base
|
|
path_under "$path" "$base"
|
|
}
|
|
|
|
# ============================================================================
|
|
# CI/CD Helpers
|
|
# ============================================================================
|
|
|
|
# Get artifact output directory
|
|
get_artifact_dir() {
|
|
local name="${1:-artifacts}"
|
|
local base="${GITHUB_WORKSPACE:-$(pwd)}"
|
|
echo "${base}/out/${name}"
|
|
}
|
|
|
|
# Get test results directory
|
|
get_test_results_dir() {
|
|
local base="${GITHUB_WORKSPACE:-$(pwd)}"
|
|
echo "${base}/TestResults"
|
|
}
|
|
|
|
# Ensure artifact directory exists and return path
|
|
ensure_artifact_dir() {
|
|
local name="${1:-artifacts}"
|
|
local dir
|
|
dir=$(get_artifact_dir "$name")
|
|
ensure_directory "$dir"
|
|
echo "$dir"
|
|
}
|