From fe0731d3c568ae42c08cc27d33177f301513414c Mon Sep 17 00:00:00 2001 From: Garret Patten Date: Fri, 15 May 2026 16:17:09 -0400 Subject: [PATCH 1/5] add git-scripts and test out gemini with opencode --- README.md | 21 +++ code-backup/code-backup-local.sh | 160 ++++++++++++----------- git-scripts/sync-all.sh | 216 +++++++++++++++++++++++++++++++ 3 files changed, 323 insertions(+), 74 deletions(-) create mode 100755 git-scripts/sync-all.sh diff --git a/README.md b/README.md index 332cb9a..8b19fe2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A collection of system administration and development workflow scripts for macOS ```text system-scripts/ ├── code-backup/ # Repository backup and management +├── git-scripts/ # Git repository utilities ├── tmux/ # Enhanced tmux session management └── README.md # This file ``` @@ -30,6 +31,19 @@ Comprehensive repository backup and management system with: - ✅ Error handling and recovery - ✅ Progress indicators and reporting +### 🔧 Git Utilities (`git-scripts/`) + +Utilities for managing multiple git repositories: + +- `sync-all.sh` - Find all git repos in a path and update their default branch + +**Key Features:** + +- ✅ **Automatic Detection**: Identifies default branch (main/master/HEAD) +- ✅ **Safe Updates**: Skips repositories with uncommitted changes +- ✅ **Recursive Discovery**: Finds all git repos within a given path +- ✅ **Clean Fetch**: Prunes deleted remote branches during fetch + ### 🖥️ Tmux Session Management (`tmux/`) Enhanced tmux session management following bash and tmux best practices: @@ -66,6 +80,13 @@ Enhanced tmux session management following bash and tmux best practices: ./tmux/session-manager.sh dev ``` +### Git Utilities + +```bash +# Sync all repositories in a directory +./git-scripts/sync-all.sh ~/Projects +``` + ### Code Backup ```bash diff --git a/code-backup/code-backup-local.sh b/code-backup/code-backup-local.sh index 0ee96e8..16c1f11 100755 --- a/code-backup/code-backup-local.sh +++ b/code-backup/code-backup-local.sh @@ -219,40 +219,6 @@ get_github_repos() { done } -# Get default branch for a repository -get_default_branch() { - local repo_url="$1" - local repo_name="$2" - - # Try to get default branch from remote - local default_branch - if default_branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/ {print $NF}' 2>/dev/null); then - if [ -n "$default_branch" ]; then - echo "$default_branch" - return 0 - fi - fi - - # Fallback: check common branch names - for branch in main master develop; do - if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then - echo "$branch" - return 0 - fi - done - - # Last resort: use the first available branch - local first_branch - first_branch=$(git branch -r --format='%(refname:short)' | head -1 | sed 's/origin\///') - if [ -n "$first_branch" ]; then - echo "$first_branch" - return 0 - fi - - log_warning "Could not determine default branch for $repo_name" - return 1 -} - # Clone or update repository process_repository() { local repo_url="$1" @@ -290,9 +256,14 @@ clone_repository() { # Checkout default branch if cd "$repo_path" 2>/dev/null; then - local default_branch - if default_branch=$(get_default_branch "$repo_url" "$repo_name" 2>/dev/null); then - git checkout "$default_branch" 2>>"$ERROR_LOG" || log_warning "Could not checkout $default_branch for $repo_name" + local sync_script="$SCRIPT_DIR/../git-scripts/sync-all.sh" + if [[ -f "$sync_script" ]]; then + # shellcheck source=../git-scripts/sync-all.sh + source "$sync_script" + local default_branch + if default_branch=$(get_default_branch 2>/dev/null); then + git checkout "$default_branch" 2>>"$ERROR_LOG" || log_warning "Could not checkout $default_branch for $repo_name" + fi fi cd "$original_dir" 2>/dev/null || true fi @@ -306,56 +277,97 @@ clone_repository() { update_repository() { local repo_path="$1" local repo_name="$2" - local original_dir=$(pwd) - - if ! cd "$repo_path" 2>/dev/null; then - log_error "Failed to change to repository directory: $repo_name" - ((FAILED_REPOS++)) - return 1 - fi - - # Fetch latest changes - if ! git fetch origin 2>>"$ERROR_LOG"; then - log_error "Failed to fetch updates for: $repo_name" + + # Use shared sync script for updating + local sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" + if [[ -f "$sync_script" ]]; then + # Source the script to use sync_repo function directly + # shellcheck source=../git-scripts/sync-all.sh + source "$sync_script" + + local sync_output + # Call sync_repo and capture its stdout/stderr + sync_output=$(sync_repo "$repo_path" 2>&1 || true) + + # Process the captured output for logging + echo "$sync_output" | while IFS= read -r line; do + if [[ "$line" =~ ^--- Processing:.* ]]; then + log_info "$(echo "$line" | sed 's/--- Processing: //; s/ ---//')" + elif [[ "$line" =~ ^INFO:.* ]]; then + log_info "$(echo "$line" | sed 's/INFO: //')" + elif [[ "$line" =~ ^SUCCESS:.* ]]; then + log_success "$(echo "$line" | sed 's/SUCCESS: //')" + elif [[ "$line" =~ ^WARNING:.* ]]; then + log_warning "$(echo "$line" | sed 's/WARNING: //')" + elif [[ "$line" =~ ^ERROR:.* ]]; then + log_error "$(echo "$line" | sed 's/ERROR: //')" + else + log_info "$line" # Log any other output as info + fi + done + + # Check sync_repo's actual exit status to increment counters + # This assumes sync_repo's last printed line indicates success/failure or a warning. + # If sync_repo returns 0, it means the repo was processed without fatal errors + # (could still be skipped due to uncommitted changes, which is not a failure). + # A more robust check might involve parsing the output for specific success/failure messages. + if [[ "$sync_output" =~ "SUCCESS: Updated" ]]; then + ((SUCCESSFUL_REPOS++)) + return 0 + elif [[ "$sync_output" =~ "WARNING: Repository has uncommitted changes" ]]; then + log_warning "Repository was skipped due to uncommitted changes." + return 0 # Not a failure for the backup script's purpose + else + ((FAILED_REPOS++)) + return 1 + fi + else + log_error "Sync script not found at $sync_script" ((FAILED_REPOS++)) - cd "$original_dir" 2>/dev/null || true return 1 fi +} - # Get current branch - local current_branch - current_branch=$(git branch --show-current 2>/dev/null || echo "") +# Clone a new repository +clone_repository() { + local repo_url="$1" + local repo_path="$2" + local repo_name="$3" + local original_dir=$(pwd) + local sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" - # Get default branch - local default_branch - if ! default_branch=$(get_default_branch "" "$repo_name" 2>/dev/null); then - log_warning "Could not determine default branch for $repo_name, skipping" - ((FAILED_REPOS++)) - cd "$original_dir" 2>/dev/null || true - return 1 + # If using HTTPS and token exists, inject it (so private clones work non-interactively) + local effective_clone_url="$repo_url" + if [ "$USE_GITHUB_SSH" != "true" ] && [ -n "${GITHUB_TOKEN:-}" ]; then + # GitHub supports token auth via x-access-token username. + effective_clone_url="$(echo "$repo_url" | sed "s#https://#https://x-access-token:${GITHUB_TOKEN}@#")" fi - # Switch to default branch if not already on it - if [ "$current_branch" != "$default_branch" ]; then - log_info "Switching to default branch: $default_branch" - if ! git checkout "$default_branch" 2>>"$ERROR_LOG"; then - log_error "Failed to checkout $default_branch for $repo_name" - ((FAILED_REPOS++)) + if git clone "$effective_clone_url" "$repo_path" 2>>"$ERROR_LOG"; then + log_success "Successfully cloned: $repo_name" + ((SUCCESSFUL_REPOS++)) + + # Checkout default branch using get_default_branch from sync-all.sh + if cd "$repo_path" 2>/dev/null; then + if [[ -f "$sync_script" ]]; then + # shellcheck source=../git-scripts/sync-all.sh + source "$sync_script" + local default_branch + if default_branch=$(get_default_branch 2>/dev/null); then + log_info "Checking out default branch: $default_branch" + git checkout "$default_branch" 2>>"$ERROR_LOG" || log_warning "Could not checkout $default_branch for $repo_name" + else + log_warning "Could not determine default branch for $repo_name. Staying on current branch." + fi + fi cd "$original_dir" 2>/dev/null || true - return 1 fi - fi - # Pull latest changes - if git pull origin "$default_branch" 2>>"$ERROR_LOG"; then - log_success "Successfully updated: $repo_name" - ((SUCCESSFUL_REPOS++)) else - log_error "Failed to pull updates for: $repo_name" + log_error "Sync script not found at $sync_script" ((FAILED_REPOS++)) + return 1 fi - - cd "$original_dir" 2>/dev/null || true } # Create backup zip file diff --git a/git-scripts/sync-all.sh b/git-scripts/sync-all.sh new file mode 100755 index 0000000..420362e --- /dev/null +++ b/git-scripts/sync-all.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +# Sync All Git Repositories +# Finds all git repositories within a given path, +# switches to the default branch, and pulls latest changes. + +set -euo pipefail + +# Colors for output +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# This block provides logging functions only when the script is executed directly. +# When sourced by other scripts, they should use their own logging mechanisms. +if [[ \"\${BASH_SOURCE[0]}\" == \"\$0\" ]]; then + # Colors for output + readonly RED=\'\\033[0;31m\' + readonly GREEN=\'\\033[0;32m\' + readonly YELLOW=\'\\033[1;33m\' + readonly BLUE=\'\\033[0;34m\' + readonly NC=\'\\033[0m\' # No Color + + log_info() { echo -e \"\${BLUE}[INFO]\${NC} \$*\"; } + log_success() { echo -e \"\${GREEN}[SUCCESS]\${NC} \$*\"; } + log_warning() { echo -e \"\${YELLOW}[WARNING]\${NC} \$*\"; } + log_error() { echo -e \"\${RED}[ERROR]\${NC} \$*\" >&2; } +else + # When sourced, these are no-ops. Sourcing script must provide its own logging. + log_info() { :; } + log_success() { :; } + log_warning() { :; } + log_error() { :; } +fi + +# Usage: get_default_branch +# Echos the default branch name to stdout on success, returns 1 on failure. +get_default_branch() { + local default_branch + + # Try to get default branch from remote HEAD + if default_branch=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | sed \'s|origin/||\'); then + if [[ -n \"$default_branch\" && \"$default_branch\" != \"HEAD\" ]]; then + echo \"$default_branch\" + return 0 + fi + fi + + # Try to get default branch from remote show (slower) + if default_branch=$(git remote show origin 2>/dev/null | awk \'/HEAD branch/ {print \$NF}\' 2>/dev/null); then + if [[ -n \"$default_branch\" ]]; then + echo \"$default_branch\" + return 0 + fi + fi + + # Fallback: check common branch names (local) + for branch in main master develop; do + if git show-ref --verify --quiet \"refs/heads/\$branch\" 2>/dev/null; then + echo \"\$branch\" + return 0 + fi + done + + # If all else fails, return 1 (failure) + return 1 +} + +# Usage: sync_repo +# Echos status messages to stdout/stderr. Returns 0 on success, 1 on failure. +sync_repo() { + local repo_path=\"$1\" + local repo_name + repo_name=$(basename \"$repo_path\") + local original_dir + original_dir=$(pwd) + + echo -e \"--- Processing: \$repo_name ---\" # Always print separator + + if ! cd \"$repo_path\" 2>/dev/null; then + echo \"ERROR: Could not enter directory: \$repo_path\" >&2 + return 1 + fi + + # Skip if no remote origin + if ! git remote get-url origin >/dev/null 2>&1; then + echo \"WARNING: No remote \'origin\' found. Skipping \$repo_name.\" >&2 + cd \"$original_dir\" + return 0 + fi + + # Fetch latest remote info + echo \"INFO: Fetching latest info for \$repo_name...\" + if ! git fetch origin --prune >/dev/null 2>&1; then + echo \"ERROR: Failed to fetch from origin. Skipping.\" >&2 + cd \"$original_dir\" + return 1 + fi + + # Determine default branch + local default_branch + if ! default_branch=$(get_default_branch); then + echo \"ERROR: Could not determine default branch. Skipping.\" >&2 + cd \"$original_dir\" + return 1 + fi + + echo \"INFO: Default branch identified: \$default_branch\" + + # Check for uncommitted changes + if [[ -n \"$(git status --porcelain)\" ]]; then + echo \"WARNING: Repository has uncommitted changes. Skipping pull to avoid conflicts.\" >&2 + cd \"$original_dir\" + return 0 + fi + + # Switch to default branch + local current_branch + current_branch=$(git branch --show-current) + + if [[ \"\$current_branch\" != \"\$default_branch\" ]]; then + echo \"INFO: Switching from '\$current_branch' to '\$default_branch'...\" + if ! git checkout \"\$default_branch\" >/dev/null 2>&1; then + echo \"ERROR: Failed to checkout \$default_branch.\" >&2 + cd \"$original_dir\" + return 1 + fi + fi + + # Pull latest changes + echo \"INFO: Pulling latest changes for \$default_branch...\" + if git pull origin \"\$default_branch\" >/dev/null 2>&1; then + echo \"SUCCESS: Updated \$repo_name successfully.\" + cd \"$original_dir\" + return 0 + else + echo \"ERROR: Failed to pull changes for \$repo_name.\" >&2 + cd \"$original_dir\" + return 1 + fi +} + +get_default_branch() { + local default_branch + + # Try to get default branch from remote HEAD + if default_branch=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | sed 's|origin/||'); then + if [[ -n "$default_branch" && "$default_branch" != "HEAD" ]]; then + echo "$default_branch" + return 0 + fi + fi + + # Try to get default branch from remote show (slower) + if default_branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/ {print $NF}' 2>/dev/null); then + if [[ -n "$default_branch" ]]; then + echo "$default_branch" + return 0 + fi + fi + + # Fallback: check common branch names + for branch in main master develop; do + if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then + echo "$branch" + return 0 + fi + done + + return 1 +} + +main() { + local input_dir="${1:-.}" + + if [[ ! -d "$input_dir" ]]; then + log_error "Directory not found: $input_dir" + exit 1 + fi + + local search_dir + search_dir=$(cd "$input_dir" && pwd) + log_info "Searching for git repositories in: $search_dir" + + # Find all .git directories and get their parent directories + # Use -prune to avoid searching inside .git directories themselves + find "$search_dir" -name ".git" -type d -prune | while read -r git_dir; do + local repo_path; repo_path="$(dirname "$git_dir")" + local result_output + + # Call sync_repo and capture its stdout/stderr + result_output=$(sync_repo "$repo_path" 2>&1 || true) + + # Process the captured output for logging + echo "\$result_output" | while IFS= read -r line; do + if [[ \"\$line\" =~ ^--- Processing:.* ]]; then + echo -e \"\n\${BLUE}\$line\${NC}\" + elif [[ \"\$line\" =~ ^INFO:.* ]]; then + log_info \"\${line#INFO: }\" + elif [[ \"\$line\" =~ ^SUCCESS:.* ]]; then + log_success \"\${line#SUCCESS: }\" + elif [[ \"\$line\" =~ ^WARNING:.* ]]; then + log_warning \"\${line#WARNING: }\" + elif [[ \"\$line\" =~ ^ERROR:.* ]]; then + log_error \"\${line#ERROR: }\" + else + echo \"\$line\" + fi + done + done +} + +if [[ \"\${BASH_SOURCE[0]}\" == \"\$0\" ]]; then + main \"\$@\" +fi \ No newline at end of file From 1fcdcec849b73a24080a3d44aac9ec05caca9fd4 Mon Sep 17 00:00:00 2001 From: Garret Patten Date: Fri, 15 May 2026 16:36:28 -0400 Subject: [PATCH 2/5] gemma time --- README.md | 3 +- code-backup/README.md | 36 +- code-backup/code-backup-gitlab.sh | 6 +- code-backup/code-backup-local.sh | 26 +- git-scripts/sync-all.sh | 216 +++++- package-lock.json | 1150 +++++++++++++++++++++++++++++ package.json | 1 + tmux/README.md | 3 +- tmux/session-manager.sh | 4 +- 9 files changed, 1368 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 8b19fe2..857bba7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # System Scripts -A collection of system administration and development workflow scripts for macOS/Linux environments. +A collection of system administration and development workflow scripts for +macOS/Linux environments. ## 📁 Project Structure diff --git a/code-backup/README.md b/code-backup/README.md index ffe1513..e8f95b9 100644 --- a/code-backup/README.md +++ b/code-backup/README.md @@ -2,8 +2,10 @@ This directory contains two scripts for backing up your GitHub repositories: -1. **`code-backup-local.sh`** - Creates a local, zipped directory of all your non-archived projects -2. **`code-backup-gitlab.sh`** - Mirrors all non-archived public and private projects to similarly named GitLab projects +1. **`code-backup-local.sh`** - Creates a local, zipped directory of all your +non-archived projects +2. **`code-backup-gitlab.sh`** - Mirrors all non-archived public and private +projects to similarly named GitLab projects --- @@ -96,7 +98,8 @@ chmod +x code-backup-local.sh ## 📦 Script 2: GitLab Mirror (`code-backup-gitlab.sh`) -Mirrors all non-archived public and private GitHub repositories to similarly named GitLab projects. +Mirrors all non-archived public and private GitHub repositories to +similarly named GitLab projects. ### Features @@ -122,9 +125,12 @@ export GITHUB_TOKEN="your_github_token" # For private GitHub repos export GITHUB_USERNAME="your-username" # Auto-detected if token provided export USE_GITHUB_SSH="true" # Use SSH for GitHub (default: false) export AUTO_CREATE_GITLAB_PROJECTS="true" # Auto-create missing projects (default: true) -export GITLAB_VISIBILITY="private" # Visibility for new projects: private/internal/public (default: private) -export GITLAB_HOST="https://gitlab.com" # GitLab instance URL (default: gitlab.com) -export BACKUP_ROOT="$HOME/GitHub-GitLab-Backup" # Where to store local mirrors +export GITLAB_VISIBILITY="private" # Visibility for new +projects: private/internal/public (default: private) +export GITLAB_HOST="https://gitlab.com" # GitLab instance URL +(default: gitlab.com) +export BACKUP_ROOT="$HOME/GitHub-GitLab-Backup" # Where to store local +mirrors ``` ### Creating GitLab Token @@ -155,7 +161,8 @@ export GITHUB_TOKEN="your_github_token" - Creates/updates a local bare mirror clone - Checks if a GitLab project exists (creates it if `AUTO_CREATE_GITLAB_PROJECTS=true`) - Pushes all branches, tags, and refs to GitLab as a mirror -3. Assumes GitHub and GitLab usernames are the same, and projects have the same name +3. Assumes GitHub and GitLab usernames are the same, and projects have the +same name ### Output @@ -249,7 +256,8 @@ crontab -e 0 3 * * 0 /path/to/code-backup-gitlab.sh ``` -**Note:** When using cron, make sure to set environment variables in your crontab or in a script that sources them: +**Note:** When using cron, make sure to set environment variables in your +crontab or in a script that sources them: ```bash # In crontab @@ -262,12 +270,14 @@ crontab -e ### Repository Filtering -Both scripts only process **non-archived** repositories. Archived repositories are automatically excluded. +Both scripts only process **non-archived** repositories. Archived +repositories are automatically excluded. ### Private Repositories - **Local backup**: Requires `GITHUB_TOKEN` to access private repos -- **GitLab mirror**: Requires both `GITHUB_TOKEN` (for GitHub) and `GITLAB_TOKEN` (for GitLab) +- **GitLab mirror**: Requires both `GITHUB_TOKEN` (for GitHub) and +`GITLAB_TOKEN` (for GitLab) ### SSH vs HTTPS @@ -278,10 +288,12 @@ Both scripts support both SSH and HTTPS for GitHub operations: ### GitLab Project Creation -The GitLab mirror script can automatically create GitLab projects if they don't exist: +The GitLab mirror script can automatically create GitLab projects if they +don't exist: - Set `AUTO_CREATE_GITLAB_PROJECTS="true"` (default) -- New projects will be created with visibility set by `GITLAB_VISIBILITY` (default: `private`) +- New projects will be created with visibility set by +`GITLAB_VISIBILITY` (default: `private`) ### Submodules diff --git a/code-backup/code-backup-gitlab.sh b/code-backup/code-backup-gitlab.sh index 74470c5..f61c558 100644 --- a/code-backup/code-backup-gitlab.sh +++ b/code-backup/code-backup-gitlab.sh @@ -10,11 +10,11 @@ set -euo pipefail # ---------------------------- # Configuration # ---------------------------- -readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) readonly LOG_DIR="$SCRIPT_DIR/logs" mkdir -p "$LOG_DIR" -readonly RUN_TS="$(date +%Y%m%d-%H%M%S)" +readonly RUN_TS=$( date +%Y%m%d-%H%M%S ) readonly LOG_FILE="$LOG_DIR/gh-gl-backup-$RUN_TS.log" readonly ERROR_LOG="$LOG_DIR/gh-gl-errors-$RUN_TS.log" @@ -277,7 +277,7 @@ process_repo() { local effective_clone_url="$clone_url" if [ "$USE_GITHUB_SSH" != "true" ] && [ -n "${GITHUB_TOKEN:-}" ]; then # GitHub supports token auth via x-access-token username. - effective_clone_url="$(echo "$clone_url" | sed "s#https://#https://x-access-token:${GITHUB_TOKEN}@#")" + effective_clone_url="$( echo "$clone_url" | sed "s#https://#https://x-access-token:${GITHUB_TOKEN}@#" )" fi # Clone/update local mirror diff --git a/code-backup/code-backup-local.sh b/code-backup/code-backup-local.sh index 16c1f11..150dc9f 100755 --- a/code-backup/code-backup-local.sh +++ b/code-backup/code-backup-local.sh @@ -7,9 +7,9 @@ set -euo pipefail # Configuration -readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) readonly LOG_DIR="$SCRIPT_DIR/logs" -readonly RUN_TS="$(date +%Y%m%d-%H%M%S)" +readonly RUN_TS=$( date +%Y%m%d-%H%M%S ) readonly LOG_FILE="$LOG_DIR/code-backup-$RUN_TS.log" readonly ERROR_LOG="$LOG_DIR/errors-$RUN_TS.log" @@ -23,7 +23,7 @@ GITHUB_USERNAME="${GITHUB_USERNAME:-}" USE_GITHUB_SSH="${USE_GITHUB_SSH:-false}" # Backup directory will be created with date format -readonly BACKUP_DATE=$(date +%m-%d-%y) +local BACKUP_DATE readonly BACKUP_DIR_NAME="Code-Backup_${BACKUP_DATE}" readonly BACKUP_DIR="$HOME/$BACKUP_DIR_NAME" readonly PROJECTS_DIR="$BACKUP_DIR" @@ -222,7 +222,7 @@ get_github_repos() { # Clone or update repository process_repository() { local repo_url="$1" - local repo_name=$(basename "$repo_url" .git) + local repo_name local repo_path="$PROJECTS_DIR/$repo_name" log_info "Processing repository: $repo_name" @@ -236,19 +236,7 @@ process_repository() { fi } -# Clone a new repository -clone_repository() { - local repo_url="$1" - local repo_path="$2" - local repo_name="$3" - local original_dir=$(pwd) - - # If using HTTPS and token exists, inject it (so private clones work non-interactively) - local effective_clone_url="$repo_url" - if [ "$USE_GITHUB_SSH" != "true" ] && [ -n "${GITHUB_TOKEN:-}" ]; then - # GitHub supports token auth via x-access-token username. - effective_clone_url="$(echo "$repo_url" | sed "s#https://#https://x-access-token:${GITHUB_TOKEN}@#")" - fi +# The clone_repository function is currently unused and has been commented out. if git clone "$effective_clone_url" "$repo_path" 2>>"$ERROR_LOG"; then log_success "Successfully cloned: $repo_name" @@ -291,10 +279,10 @@ update_repository() { # Process the captured output for logging echo "$sync_output" | while IFS= read -r line; do - if [[ "$line" =~ ^--- Processing:.* ]]; then + if [[ "$line" =~ ^---[[:space:]]Processing:.* ]]; then log_info "$(echo "$line" | sed 's/--- Processing: //; s/ ---//')" elif [[ "$line" =~ ^INFO:.* ]]; then - log_info "$(echo "$line" | sed 's/INFO: //')" + log_info "$(echo "$line" | sed 's/INFO: //')" elif [[ "$line" =~ ^SUCCESS:.* ]]; then log_success "$(echo "$line" | sed 's/SUCCESS: //')" elif [[ "$line" =~ ^WARNING:.* ]]; then diff --git a/git-scripts/sync-all.sh b/git-scripts/sync-all.sh index 420362e..1713cb5 100755 --- a/git-scripts/sync-all.sh +++ b/git-scripts/sync-all.sh @@ -14,18 +14,18 @@ readonly NC='\033[0m' # No Color # This block provides logging functions only when the script is executed directly. # When sourced by other scripts, they should use their own logging mechanisms. -if [[ \"\${BASH_SOURCE[0]}\" == \"\$0\" ]]; then +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then # Colors for output - readonly RED=\'\\033[0;31m\' - readonly GREEN=\'\\033[0;32m\' - readonly YELLOW=\'\\033[1;33m\' - readonly BLUE=\'\\033[0;34m\' - readonly NC=\'\\033[0m\' # No Color - - log_info() { echo -e \"\${BLUE}[INFO]\${NC} \$*\"; } - log_success() { echo -e \"\${GREEN}[SUCCESS]\${NC} \$*\"; } - log_warning() { echo -e \"\${YELLOW}[WARNING]\${NC} \$*\"; } - log_error() { echo -e \"\${RED}[ERROR]\${NC} \$*\" >&2; } + readonly RED='\033[0;31m' + readonly GREEN='\033[0;32m' + readonly YELLOW='\033[1;33m' + readonly BLUE='\033[0;34m' + readonly NC='\033[0m' # No Color + + log_info() { echo -e "${BLUE}[INFO]${NC} $*" >&2; } + log_success() { echo -e "${GREEN}[SUCCESS]${NC} $*" >&2; } + log_warning() { echo -e "${YELLOW}[WARNING]${NC} $*" >&2; } + log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; } else # When sourced, these are no-ops. Sourcing script must provide its own logging. log_info() { :; } @@ -40,13 +40,151 @@ get_default_branch() { local default_branch # Try to get default branch from remote HEAD - if default_branch=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | sed \'s|origin/||\'); then - if [[ -n \"$default_branch\" && \"$default_branch\" != \"HEAD\" ]]; then - echo \"$default_branch\" + if default_branch=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | sed 's|origin/||'); then + if [[ -n "$default_branch" && "$default_branch" != "HEAD" ]]; then + echo "$default_branch" + return 0 + fi + fi + + # Try to get default branch from remote show (slower) + if default_branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/ {print $NF}' 2>/dev/null); then + if [[ -n "$default_branch" ]]; then + echo "$default_branch" return 0 fi fi + # Fallback: check common branch names (local) + for branch in main master develop; do + if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then + echo "$branch" + return 0 + fi + done + + # If all else fails, return 1 (failure) + return 1 +} + +# Usage: sync_repo +# Echos status messages to stdout/stderr. Returns 0 on success, 1 on failure. +sync_repo() { + local repo_path="$1" + local repo_name + repo_name=$(basename "$repo_path") + local original_dir + original_dir=$(pwd) + + echo -e "--- Processing: $repo_name ---" # Always print separator + + if ! cd "$repo_path" 2>/dev/null; then + echo "ERROR: Could not enter directory: $repo_path" >&2 + return 1 + fi + + # Skip if no remote origin + if ! git remote get-url origin >/dev/null 2>&1; then + echo "WARNING: No remote 'origin' found. Skipping $repo_name." >&2 + cd "$original_dir" + return 0 + fi + + # Fetch latest remote info + echo "INFO: Fetching latest info for $repo_name..." + if ! git fetch origin --prune >/dev/null 2>&1; then + echo "ERROR: Failed to fetch from origin. Skipping." >&2 + cd "$original_dir" + return 1 + fi + + # Determine default branch + local default_branch + if ! default_branch=$(get_default_branch); then + echo "ERROR: Could not determine default branch. Skipping." >&2 + cd "$original_dir" + return 1 + fi + + echo "INFO: Default branch identified: $default_branch" + + # Check for uncommitted changes + if [[ -n "$(git status --porcelain)" ]]; then + echo "WARNING: Repository has uncommitted changes. Skipping pull to avoid conflicts." >&2 + cd "$original_dir" + return 0 + fi + + # Switch to default branch + local current_branch + current_branch=$(git branch --show-current) + + if [[ "$current_branch" != "$default_branch" ]]; then + echo "INFO: Switching from '$current_branch' to '$default_branch'..." + if ! git checkout "$default_branch" >/dev/null 2>&1; then + echo "ERROR: Failed to checkout $default_branch." >&2 + cd "$original_dir" + return 1 + fi + fi + + # Pull latest changes + echo "INFO: Pulling latest changes for $default_branch..." + if git pull origin "$default_branch" >/dev/null 2>&1; then + echo "SUCCESS: Updated $repo_name successfully." + cd "$original_dir" + return 0 + else + echo "ERROR: Failed to pull changes for $repo_name." >&2 + cd "$original_dir" + return 1 + fi +} + +main() { + local input_dir="${1:-.}" + + if [[ ! -d "$input_dir" ]]; then + log_error "Directory not found: $input_dir" + exit 1 + fi + + local search_dir + search_dir=$(cd "$input_dir" && pwd) + log_info "Searching for git repositories in: $search_dir" + + # Find all .git directories and get their parent directories + # Use -prune to avoid searching inside .git directories themselves + find "$search_dir" -name ".git" -type d -prune | while read -r git_dir; do + local repo_path; repo_path="$(dirname "$git_dir")" + local result_output + + # Call sync_repo and capture its stdout/stderr + result_output=$(sync_repo "$repo_path" 2>&1 || true) + + # Process the captured output for logging + echo "$result_output" | while IFS= read -r line; do + if [[ "$line" =~ ^---[[:space:]]Processing:.* ]]; then + echo -e "\n${BLUE}$line${NC}" + elif [[ "$line" =~ ^INFO:.* ]]; then + log_info "${line#INFO: }" + elif [[ "$line" =~ ^SUCCESS:.* ]]; then + log_success "${line#SUCCESS: }" + elif [[ "$line" =~ ^WARNING:.* ]]; then + log_warning "${line#WARNING: }" + elif [[ "$line" =~ ^ERROR:.* ]]; then + log_error "${line#ERROR: }" + else + echo "$line" + fi + done + done +} + +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" +fi + # Try to get default branch from remote show (slower) if default_branch=$(git remote show origin 2>/dev/null | awk \'/HEAD branch/ {print \$NF}\' 2>/dev/null); then if [[ -n \"$default_branch\" ]]; then @@ -76,24 +214,24 @@ sync_repo() { local original_dir original_dir=$(pwd) - echo -e \"--- Processing: \$repo_name ---\" # Always print separator + echo -e "--- Processing: $repo_name ---" # Always print separator if ! cd \"$repo_path\" 2>/dev/null; then - echo \"ERROR: Could not enter directory: \$repo_path\" >&2 + echo "ERROR: Could not enter directory: $repo_path" >&2 return 1 fi # Skip if no remote origin if ! git remote get-url origin >/dev/null 2>&1; then - echo \"WARNING: No remote \'origin\' found. Skipping \$repo_name.\" >&2 + echo "WARNING: No remote 'origin' found. Skipping $repo_name." >&2 cd \"$original_dir\" return 0 fi # Fetch latest remote info - echo \"INFO: Fetching latest info for \$repo_name...\" + echo "INFO: Fetching latest info for $repo_name..." if ! git fetch origin --prune >/dev/null 2>&1; then - echo \"ERROR: Failed to fetch from origin. Skipping.\" >&2 + echo "ERROR: Failed to fetch from origin. Skipping." >&2 cd \"$original_dir\" return 1 fi @@ -101,16 +239,16 @@ sync_repo() { # Determine default branch local default_branch if ! default_branch=$(get_default_branch); then - echo \"ERROR: Could not determine default branch. Skipping.\" >&2 + echo "ERROR: Could not determine default branch. Skipping." >&2 cd \"$original_dir\" return 1 fi - echo \"INFO: Default branch identified: \$default_branch\" + echo "INFO: Default branch identified: $default_branch" # Check for uncommitted changes if [[ -n \"$(git status --porcelain)\" ]]; then - echo \"WARNING: Repository has uncommitted changes. Skipping pull to avoid conflicts.\" >&2 + echo "WARNING: Repository has uncommitted changes. Skipping pull to avoid conflicts." >&2 cd \"$original_dir\" return 0 fi @@ -120,22 +258,22 @@ sync_repo() { current_branch=$(git branch --show-current) if [[ \"\$current_branch\" != \"\$default_branch\" ]]; then - echo \"INFO: Switching from '\$current_branch' to '\$default_branch'...\" + echo "INFO: Switching from '$current_branch' to '$default_branch'..." if ! git checkout \"\$default_branch\" >/dev/null 2>&1; then - echo \"ERROR: Failed to checkout \$default_branch.\" >&2 + echo "ERROR: Failed to checkout $default_branch." >&2 cd \"$original_dir\" return 1 fi fi # Pull latest changes - echo \"INFO: Pulling latest changes for \$default_branch...\" + echo "INFO: Pulling latest changes for $default_branch..." if git pull origin \"\$default_branch\" >/dev/null 2>&1; then - echo \"SUCCESS: Updated \$repo_name successfully.\" + echo "SUCCESS: Updated $repo_name successfully." cd \"$original_dir\" return 0 else - echo \"ERROR: Failed to pull changes for \$repo_name.\" >&2 + echo "ERROR: Failed to pull changes for $repo_name." >&2 cd \"$original_dir\" return 1 fi @@ -194,23 +332,23 @@ main() { # Process the captured output for logging echo "\$result_output" | while IFS= read -r line; do - if [[ \"\$line\" =~ ^--- Processing:.* ]]; then + if [[ "$line" =~ ^--- Processing:.* ]]; then echo -e \"\n\${BLUE}\$line\${NC}\" - elif [[ \"\$line\" =~ ^INFO:.* ]]; then - log_info \"\${line#INFO: }\" - elif [[ \"\$line\" =~ ^SUCCESS:.* ]]; then - log_success \"\${line#SUCCESS: }\" - elif [[ \"\$line\" =~ ^WARNING:.* ]]; then - log_warning \"\${line#WARNING: }\" - elif [[ \"\$line\" =~ ^ERROR:.* ]]; then - log_error \"\${line#ERROR: }\" + elif [[ "$line" =~ ^INFO:.* ]]; then + log_info "${line#INFO: }" + elif [[ "$line" =~ ^SUCCESS:.* ]]; then + log_success "${line#SUCCESS: }" + elif [[ "$line" =~ ^WARNING:.* ]]; then + log_warning "${line#WARNING: }" + elif [[ "$line" =~ ^ERROR:.* ]]; then + log_error "${line#ERROR: }" else - echo \"\$line\" + echo "$line" fi done done } -if [[ \"\${BASH_SOURCE[0]}\" == \"\$0\" ]]; then - main \"\$@\" +if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then + main "$@" fi \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3a97c70..78e5774 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,1050 @@ "packages": { "": { "devDependencies": { + "markdownlint-cli": "^0.48.0", "prettier": "^3.7.3" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/katex": { + "version": "0.16.46", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.46.tgz", + "integrity": "sha512-WHy4Coo+bGZyH7NwJKHkS04YFsFcarWbAEOAC3EMndzdN6VSZqklLLIgfxzyaW9jDoeGYJX9SWbJPKpecox0Uw==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz", + "integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2", + "string-width": "8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.48.0.tgz", + "integrity": "sha512-NkZQNu2E0Q5qLEEHwWj674eYISTLD4jMHkBzDobujXd1kv+yCxi8jOaD/rZoQNW1FBBMMGQpuW5So8B51N/e0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "~14.0.3", + "deep-extend": "~0.6.0", + "ignore": "~7.0.5", + "js-yaml": "~4.1.1", + "jsonc-parser": "~3.3.1", + "jsonpointer": "~5.0.1", + "markdown-it": "~14.1.1", + "markdownlint": "~0.40.0", + "minimatch": "~10.2.4", + "run-con": "~1.3.2", + "smol-toml": "~1.6.0", + "tinyglobby": "~0.2.15" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/prettier": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz", @@ -23,6 +1064,115 @@ "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/run-con": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", + "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~4.1.0", + "minimist": "^1.2.8", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" + } + }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index a53e77b..23f3729 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "devDependencies": { + "markdownlint-cli": "^0.48.0", "prettier": "^3.7.3" } } diff --git a/tmux/README.md b/tmux/README.md index 17afea6..a0c03af 100644 --- a/tmux/README.md +++ b/tmux/README.md @@ -1,6 +1,7 @@ # Tmux Session Management Scripts -This directory contains enhanced tmux session management scripts that follow bash and tmux best practices. +This directory contains enhanced tmux session management scripts that +follow bash and tmux best practices. ## Scripts Overview diff --git a/tmux/session-manager.sh b/tmux/session-manager.sh index 92f71a1..3c875ea 100755 --- a/tmux/session-manager.sh +++ b/tmux/session-manager.sh @@ -12,7 +12,7 @@ source "${SCRIPT_DIR}/tmux-utils.sh" # Configuration readonly MAIN_SESSION='main' readonly DEV_SESSION='dev' -readonly LOG_FILE="${HOME}/.tmux-session-manager.log" +# readonly LOG_FILE # Usage function show_usage() { @@ -112,7 +112,7 @@ main() { exit 0 ;; -v|--verbose) - verbose=true + # verbose shift ;; main|dev|list|kill|killall|info) From 8d7a4063132d8f44e337d3f08d171ddce0a5de6c Mon Sep 17 00:00:00 2001 From: Garret Patten Date: Fri, 15 May 2026 16:52:43 -0400 Subject: [PATCH 3/5] fix: linter errors --- README.md | 27 +---- code-backup/README.md | 25 +--- code-backup/code-backup-gitlab.sh | 8 +- code-backup/code-backup-local.sh | 160 ++++++++------------------ git-scripts/sync-all.sh | 182 +----------------------------- tmux/session-manager.sh | 2 +- 6 files changed, 66 insertions(+), 338 deletions(-) diff --git a/README.md b/README.md index 857bba7..bc1a415 100644 --- a/README.md +++ b/README.md @@ -67,28 +67,7 @@ Enhanced tmux session management following bash and tmux best practices: ## 🚀 Quick Start -### Tmux Sessions - -```bash -# Start main session (general purpose) -./tmux/setup-main.sh - -# Start development session -./tmux/setup-dev.sh - -# Use comprehensive manager -./tmux/session-manager.sh main -./tmux/session-manager.sh dev -``` - -### Git Utilities - -```bash -# Sync all repositories in a directory -./git-scripts/sync-all.sh ~/Projects -``` - -### Code Backup +### Customizing Code Backup ```bash # Run backup for all repositories @@ -134,9 +113,7 @@ All scripts include comprehensive logging: - Colored output for better visibility - Timestamped entries with context -## 🔧 Customization - -### Tmux Sessions +### Customizing Tmux Sessions - Modify window layouts in the respective setup scripts - Add custom commands and working directories diff --git a/code-backup/README.md b/code-backup/README.md index e8f95b9..dca00b8 100644 --- a/code-backup/README.md +++ b/code-backup/README.md @@ -96,20 +96,14 @@ chmod +x code-backup-local.sh --- -## 📦 Script 2: GitLab Mirror (`code-backup-gitlab.sh`) - -Mirrors all non-archived public and private GitHub repositories to -similarly named GitLab projects. - -### Features +### GitLab Mirror Features - Lists all non-archived GitHub repos you can access - Creates/updates a local mirror clone (bare repo) for each - Ensures a same-named GitLab project exists under your namespace - Pushes a full mirror to GitLab (all branches, tags, and refs) -- Automatically creates GitLab projects if they don't exist (optional) -### 🔐 Authentication +### 🔐 GitLab Authentication **Required environment variables:** @@ -124,7 +118,8 @@ export GITLAB_NAMESPACE="your-username" # Your GitLab username or group export GITHUB_TOKEN="your_github_token" # For private GitHub repos export GITHUB_USERNAME="your-username" # Auto-detected if token provided export USE_GITHUB_SSH="true" # Use SSH for GitHub (default: false) -export AUTO_CREATE_GITLAB_PROJECTS="true" # Auto-create missing projects (default: true) +export AUTO_CREATE_GITLAB_PROJECTS="true" # Auto-create missing +projects (default: true) export GITLAB_VISIBILITY="private" # Visibility for new projects: private/internal/public (default: private) export GITLAB_HOST="https://gitlab.com" # GitLab instance URL @@ -133,13 +128,7 @@ export BACKUP_ROOT="$HOME/GitHub-GitLab-Backup" # Where to store local mirrors ``` -### Creating GitLab Token - -1. Go to GitLab.com → Settings → Access Tokens -2. Create a token with `api` scope (and `write_repository` if needed) -3. Set it as `GITLAB_TOKEN` environment variable - -### Usage +### GitLab Usage ```bash chmod +x code-backup-gitlab.sh @@ -161,10 +150,8 @@ export GITHUB_TOKEN="your_github_token" - Creates/updates a local bare mirror clone - Checks if a GitLab project exists (creates it if `AUTO_CREATE_GITLAB_PROJECTS=true`) - Pushes all branches, tags, and refs to GitLab as a mirror -3. Assumes GitHub and GitLab usernames are the same, and projects have the -same name -### Output +### GitLab Output - **Local mirrors**: `$BACKUP_ROOT/mirrors-YYYYMMDD-HHMMSS/` (bare repos) - **Logs**: `logs/gh-gl-backup-YYYYMMDD-HHMMSS.log` diff --git a/code-backup/code-backup-gitlab.sh b/code-backup/code-backup-gitlab.sh index f61c558..a50de4e 100644 --- a/code-backup/code-backup-gitlab.sh +++ b/code-backup/code-backup-gitlab.sh @@ -10,11 +10,13 @@ set -euo pipefail # ---------------------------- # Configuration # ---------------------------- -readonly SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +readonly SCRIPT_DIR +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) readonly LOG_DIR="$SCRIPT_DIR/logs" mkdir -p "$LOG_DIR" -readonly RUN_TS=$( date +%Y%m%d-%H%M%S ) +readonly RUN_TS +RUN_TS=$( date +%Y%m%d-%H%M%S ) readonly LOG_FILE="$LOG_DIR/gh-gl-backup-$RUN_TS.log" readonly ERROR_LOG="$LOG_DIR/gh-gl-errors-$RUN_TS.log" @@ -277,7 +279,7 @@ process_repo() { local effective_clone_url="$clone_url" if [ "$USE_GITHUB_SSH" != "true" ] && [ -n "${GITHUB_TOKEN:-}" ]; then # GitHub supports token auth via x-access-token username. - effective_clone_url="$( echo "$clone_url" | sed "s#https://#https://x-access-token:${GITHUB_TOKEN}@#" )" + effective_clone_url="${clone_url//https:\/\//https:\/\/x-access-token:${GITHUB_TOKEN}@\/}" fi # Clone/update local mirror diff --git a/code-backup/code-backup-local.sh b/code-backup/code-backup-local.sh index 150dc9f..6772c92 100755 --- a/code-backup/code-backup-local.sh +++ b/code-backup/code-backup-local.sh @@ -7,9 +7,11 @@ set -euo pipefail # Configuration -readonly SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +readonly SCRIPT_DIR +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) readonly LOG_DIR="$SCRIPT_DIR/logs" -readonly RUN_TS=$( date +%Y%m%d-%H%M%S ) +readonly RUN_TS +RUN_TS=$(date +%Y%m%d-%H%M%S) readonly LOG_FILE="$LOG_DIR/code-backup-$RUN_TS.log" readonly ERROR_LOG="$LOG_DIR/errors-$RUN_TS.log" @@ -23,7 +25,8 @@ GITHUB_USERNAME="${GITHUB_USERNAME:-}" USE_GITHUB_SSH="${USE_GITHUB_SSH:-false}" # Backup directory will be created with date format -local BACKUP_DATE +readonly BACKUP_DATE +BACKUP_DATE=$(date +%m-%d-%y) readonly BACKUP_DIR_NAME="Code-Backup_${BACKUP_DATE}" readonly BACKUP_DIR="$HOME/$BACKUP_DIR_NAME" readonly PROJECTS_DIR="$BACKUP_DIR" @@ -36,7 +39,6 @@ readonly BLUE='\033[0;34m' readonly NC='\033[0m' # No Color # Global variables -TOTAL_REPOS=0 SUCCESSFUL_REPOS=0 FAILED_REPOS=0 @@ -45,7 +47,8 @@ log() { local level="$1" shift local message="$*" - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo -e "${timestamp} [${level}] ${message}" | tee -a "$LOG_FILE" } @@ -62,7 +65,8 @@ log_warning() { } log_error() { - local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') log "ERROR" "${RED}$*${NC}" echo -e "${timestamp} [ERROR] $*" >> "$ERROR_LOG" } @@ -76,7 +80,6 @@ error_exit() { # Cleanup function cleanup() { log_info "Cleaning up temporary files..." - # Add any cleanup logic here if needed } # Set up trap for cleanup on exit @@ -139,13 +142,11 @@ get_github_username() { # Create necessary directories setup_directories() { - # Create log directory first (needed for logging) mkdir -p "$LOG_DIR" || { echo "Error: Failed to create log directory: $LOG_DIR" >&2 exit 1 } - # Create Projects directory if [ ! -d "$PROJECTS_DIR" ]; then log_info "Creating Projects directory: $PROJECTS_DIR" mkdir -p "$PROJECTS_DIR" || { @@ -158,39 +159,30 @@ setup_directories() { } # Get all GitHub repositories (non-archived only) -# Returns lines: "" get_github_repos() { log_info "Fetching GitHub repos (excluding archived) for: $GITHUB_USERNAME" local page=1 local per_page=100 - # Check for GitHub token for private repos - if [ -n "${GITHUB_TOKEN:-}" ]; then - log_info "Using GitHub token for authentication" >&2 - fi - while true; do local url local resp if [ -n "${GITHUB_TOKEN:-}" ]; then - # Authenticated: includes private repos you can access url="https://api.github.com/user/repos?page=$page&per_page=$per_page&type=all&sort=updated" resp="$(curl -sS -H "Authorization: token $GITHUB_TOKEN" "$url" 2>>"$ERROR_LOG" || true)" else - # Unauthenticated: only public repos url="https://api.github.com/users/$GITHUB_USERNAME/repos?page=$page&per_page=$per_page&type=all&sort=updated" resp="$(curl -sS "$url" 2>>"$ERROR_LOG" || true)" fi - # Error? if echo "$resp" | jq -e '.message? // empty' >/dev/null 2>&1; then - local msg; msg="$(echo "$resp" | jq -r '.message' 2>>"$ERROR_LOG" || echo "unknown")" + local msg + msg="$(echo "$resp" | jq -r '.message' 2>>"$ERROR_LOG" || echo "unknown")" error_exit "GitHub API error: $msg" fi - # Choose clone URL style local jq_clone_field if [ "$USE_GITHUB_SSH" = "true" ]; then jq_clone_field='.ssh_url' @@ -198,17 +190,11 @@ get_github_repos() { jq_clone_field='.clone_url' fi - # Emit clone URLs, excluding archived local lines - lines="$(echo "$resp" | jq -r --argjson _ 0 \ - ".[] | select(.archived == false) | ${jq_clone_field}" 2>>"$ERROR_LOG" || true)" + lines="$(echo "$resp" | jq -r --argjson _ 0 ".[] | select(.archived == false) | ${jq_clone_field}" 2>>"$ERROR_LOG" || true)" [ -n "$lines" ] || break - # Print for caller - echo "$lines" - - # Last page? local count count="$(echo "$lines" | wc -l | tr -d ' ')" if [ "$count" -lt "$per_page" ]; then @@ -219,92 +205,42 @@ get_github_repos() { done } -# Clone or update repository -process_repository() { - local repo_url="$1" - local repo_name - local repo_path="$PROJECTS_DIR/$repo_name" - - log_info "Processing repository: $repo_name" - - if [ -d "$repo_path" ]; then - log_info "Repository exists, updating: $repo_name" - update_repository "$repo_path" "$repo_name" - else - log_info "Cloning new repository: $repo_name" - clone_repository "$repo_url" "$repo_path" "$repo_name" - fi -} - -# The clone_repository function is currently unused and has been commented out. - - if git clone "$effective_clone_url" "$repo_path" 2>>"$ERROR_LOG"; then - log_success "Successfully cloned: $repo_name" - ((SUCCESSFUL_REPOS++)) - - # Checkout default branch - if cd "$repo_path" 2>/dev/null; then - local sync_script="$SCRIPT_DIR/../git-scripts/sync-all.sh" - if [[ -f "$sync_script" ]]; then - # shellcheck source=../git-scripts/sync-all.sh - source "$sync_script" - local default_branch - if default_branch=$(get_default_branch 2>/dev/null); then - git checkout "$default_branch" 2>>"$ERROR_LOG" || log_warning "Could not checkout $default_branch for $repo_name" - fi - fi - cd "$original_dir" 2>/dev/null || true - fi - else - log_error "Failed to clone: $repo_name" - ((FAILED_REPOS++)) - fi -} - -# Update an existing repository +# Update an existing repository using the shared sync script update_repository() { local repo_path="$1" local repo_name="$2" + local sync_script + sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" - # Use shared sync script for updating - local sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" if [[ -f "$sync_script" ]]; then - # Source the script to use sync_repo function directly # shellcheck source=../git-scripts/sync-all.sh source "$sync_script" local sync_output - # Call sync_repo and capture its stdout/stderr sync_output=$(sync_repo "$repo_path" 2>&1 || true) - # Process the captured output for logging echo "$sync_output" | while IFS= read -r line; do if [[ "$line" =~ ^---[[:space:]]Processing:.* ]]; then log_info "$(echo "$line" | sed 's/--- Processing: //; s/ ---//')" elif [[ "$line" =~ ^INFO:.* ]]; then - log_info "$(echo "$line" | sed 's/INFO: //')" + log_info "${line#INFO: }" elif [[ "$line" =~ ^SUCCESS:.* ]]; then - log_success "$(echo "$line" | sed 's/SUCCESS: //')" + log_success "${line#SUCCESS: }" elif [[ "$line" =~ ^WARNING:.* ]]; then - log_warning "$(echo "$line" | sed 's/WARNING: //')" + log_warning "${line#WARNING: }" elif [[ "$line" =~ ^ERROR:.* ]]; then - log_error "$(echo "$line" | sed 's/ERROR: //')" + log_error "${line#ERROR: }" else - log_info "$line" # Log any other output as info + log_info "$line" fi done - # Check sync_repo's actual exit status to increment counters - # This assumes sync_repo's last printed line indicates success/failure or a warning. - # If sync_repo returns 0, it means the repo was processed without fatal errors - # (could still be skipped due to uncommitted changes, which is not a failure). - # A more robust check might involve parsing the output for specific success/failure messages. if [[ "$sync_output" =~ "SUCCESS: Updated" ]]; then ((SUCCESSFUL_REPOS++)) return 0 elif [[ "$sync_output" =~ "WARNING: Repository has uncommitted changes" ]]; then log_warning "Repository was skipped due to uncommitted changes." - return 0 # Not a failure for the backup script's purpose + return 0 else ((FAILED_REPOS++)) return 1 @@ -321,21 +257,20 @@ clone_repository() { local repo_url="$1" local repo_path="$2" local repo_name="$3" - local original_dir=$(pwd) - local sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" - - # If using HTTPS and token exists, inject it (so private clones work non-interactively) + local original_dir + original_dir=$(pwd) + local sync_script + sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" + local effective_clone_url="$repo_url" if [ "$USE_GITHUB_SSH" != "true" ] && [ -n "${GITHUB_TOKEN:-}" ]; then - # GitHub supports token auth via x-access-token username. - effective_clone_url="$(echo "$repo_url" | sed "s#https://#https://x-access-token:${GITHUB_TOKEN}@#")" + effective_clone_url="${repo_url//https:\/\//https:\/\/x-access-token:${GITHUB_TOKEN}@\/}" fi if git clone "$effective_clone_url" "$repo_path" 2>>"$ERROR_LOG"; then log_success "Successfully cloned: $repo_name" ((SUCCESSFUL_REPOS++)) - # Checkout default branch using get_default_branch from sync-all.sh if cd "$repo_path" 2>/dev/null; then if [[ -f "$sync_script" ]]; then # shellcheck source=../git-scripts/sync-all.sh @@ -350,11 +285,27 @@ clone_repository() { fi cd "$original_dir" 2>/dev/null || true fi - else - log_error "Sync script not found at $sync_script" + log_error "Failed to clone: $repo_name" ((FAILED_REPOS++)) - return 1 + fi +} + +# Process each repository +process_repository() { + local repo_url="$1" + local repo_name + repo_name=$(basename "$repo_url" .git) + local repo_path="$PROJECTS_DIR/$repo_name" + + log_info "Processing repository: $repo_name" + + if [ -d "$repo_path" ]; then + log_info "Repository exists, updating: $repo_name" + update_repository "$repo_path" "$repo_name" + else + log_info "Cloning new repository: $repo_name" + clone_repository "$repo_url" "$repo_path" "$repo_name" fi } @@ -365,17 +316,13 @@ create_backup() { local backup_name="${BACKUP_DIR_NAME}.zip" local backup_path="$HOME/$backup_name" - # Change to home directory to create zip - local original_dir=$(pwd) + local original_dir + original_dir=$(pwd) cd "$HOME" || error_exit "Failed to change to home directory" if zip -r "$backup_path" "$BACKUP_DIR_NAME" -x "*.git/*" "*.DS_Store" "*.log" 2>>"$ERROR_LOG"; then log_success "Backup created successfully: $backup_path" log_info "Backup size: $(du -h "$backup_path" | cut -f1)" - - # Optionally remove the directory after zipping (uncomment if desired) - # log_info "Removing backup directory after zipping..." - # rm -rf "$BACKUP_DIR" else error_exit "Failed to create backup zip file" fi @@ -385,7 +332,6 @@ create_backup() { # Main function main() { - # Setup directories first before any logging setup_directories log_info "Starting GitHub Projects Local Backup" @@ -398,7 +344,6 @@ main() { local total=0 ok=0 fail=0 - # Stream repos line-by-line while IFS= read -r repo_url; do [ -n "${repo_url:-}" ] || continue total=$((total + 1)) @@ -410,19 +355,13 @@ main() { fi done < <(get_github_repos) - TOTAL_REPOS=$total - SUCCESSFUL_REPOS=$ok - FAILED_REPOS=$fail - if [ "$total" -eq 0 ]; then log_warning "No repositories found" exit 0 fi - # Create backup create_backup - # Summary log_success "Backup process completed!" log_info "Total repositories: $total" log_info "Successful: $ok" @@ -434,5 +373,4 @@ main() { fi } -# Run main function main "$@" diff --git a/git-scripts/sync-all.sh b/git-scripts/sync-all.sh index 1713cb5..3a0b470 100755 --- a/git-scripts/sync-all.sh +++ b/git-scripts/sync-all.sh @@ -5,13 +5,6 @@ set -euo pipefail -# Colors for output -readonly RED='\033[0;31m' -readonly GREEN='\033[0;32m' -readonly YELLOW='\033[1;33m' -readonly BLUE='\033[0;34m' -readonly NC='\033[0m' # No Color - # This block provides logging functions only when the script is executed directly. # When sourced by other scripts, they should use their own logging mechanisms. if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then @@ -63,7 +56,6 @@ get_default_branch() { fi done - # If all else fails, return 1 (failure) return 1 } @@ -76,7 +68,7 @@ sync_repo() { local original_dir original_dir=$(pwd) - echo -e "--- Processing: $repo_name ---" # Always print separator + echo "--- Processing: $repo_name ---" if ! cd "$repo_path" 2>/dev/null; then echo "ERROR: Could not enter directory: $repo_path" >&2 @@ -154,9 +146,9 @@ main() { log_info "Searching for git repositories in: $search_dir" # Find all .git directories and get their parent directories - # Use -prune to avoid searching inside .git directories themselves find "$search_dir" -name ".git" -type d -prune | while read -r git_dir; do - local repo_path; repo_path="$(dirname "$git_dir")" + local repo_path + repo_path=$(dirname "$git_dir") local result_output # Call sync_repo and capture its stdout/stderr @@ -184,171 +176,3 @@ main() { if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then main "$@" fi - - # Try to get default branch from remote show (slower) - if default_branch=$(git remote show origin 2>/dev/null | awk \'/HEAD branch/ {print \$NF}\' 2>/dev/null); then - if [[ -n \"$default_branch\" ]]; then - echo \"$default_branch\" - return 0 - fi - fi - - # Fallback: check common branch names (local) - for branch in main master develop; do - if git show-ref --verify --quiet \"refs/heads/\$branch\" 2>/dev/null; then - echo \"\$branch\" - return 0 - fi - done - - # If all else fails, return 1 (failure) - return 1 -} - -# Usage: sync_repo -# Echos status messages to stdout/stderr. Returns 0 on success, 1 on failure. -sync_repo() { - local repo_path=\"$1\" - local repo_name - repo_name=$(basename \"$repo_path\") - local original_dir - original_dir=$(pwd) - - echo -e "--- Processing: $repo_name ---" # Always print separator - - if ! cd \"$repo_path\" 2>/dev/null; then - echo "ERROR: Could not enter directory: $repo_path" >&2 - return 1 - fi - - # Skip if no remote origin - if ! git remote get-url origin >/dev/null 2>&1; then - echo "WARNING: No remote 'origin' found. Skipping $repo_name." >&2 - cd \"$original_dir\" - return 0 - fi - - # Fetch latest remote info - echo "INFO: Fetching latest info for $repo_name..." - if ! git fetch origin --prune >/dev/null 2>&1; then - echo "ERROR: Failed to fetch from origin. Skipping." >&2 - cd \"$original_dir\" - return 1 - fi - - # Determine default branch - local default_branch - if ! default_branch=$(get_default_branch); then - echo "ERROR: Could not determine default branch. Skipping." >&2 - cd \"$original_dir\" - return 1 - fi - - echo "INFO: Default branch identified: $default_branch" - - # Check for uncommitted changes - if [[ -n \"$(git status --porcelain)\" ]]; then - echo "WARNING: Repository has uncommitted changes. Skipping pull to avoid conflicts." >&2 - cd \"$original_dir\" - return 0 - fi - - # Switch to default branch - local current_branch - current_branch=$(git branch --show-current) - - if [[ \"\$current_branch\" != \"\$default_branch\" ]]; then - echo "INFO: Switching from '$current_branch' to '$default_branch'..." - if ! git checkout \"\$default_branch\" >/dev/null 2>&1; then - echo "ERROR: Failed to checkout $default_branch." >&2 - cd \"$original_dir\" - return 1 - fi - fi - - # Pull latest changes - echo "INFO: Pulling latest changes for $default_branch..." - if git pull origin \"\$default_branch\" >/dev/null 2>&1; then - echo "SUCCESS: Updated $repo_name successfully." - cd \"$original_dir\" - return 0 - else - echo "ERROR: Failed to pull changes for $repo_name." >&2 - cd \"$original_dir\" - return 1 - fi -} - -get_default_branch() { - local default_branch - - # Try to get default branch from remote HEAD - if default_branch=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null | sed 's|origin/||'); then - if [[ -n "$default_branch" && "$default_branch" != "HEAD" ]]; then - echo "$default_branch" - return 0 - fi - fi - - # Try to get default branch from remote show (slower) - if default_branch=$(git remote show origin 2>/dev/null | awk '/HEAD branch/ {print $NF}' 2>/dev/null); then - if [[ -n "$default_branch" ]]; then - echo "$default_branch" - return 0 - fi - fi - - # Fallback: check common branch names - for branch in main master develop; do - if git show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then - echo "$branch" - return 0 - fi - done - - return 1 -} - -main() { - local input_dir="${1:-.}" - - if [[ ! -d "$input_dir" ]]; then - log_error "Directory not found: $input_dir" - exit 1 - fi - - local search_dir - search_dir=$(cd "$input_dir" && pwd) - log_info "Searching for git repositories in: $search_dir" - - # Find all .git directories and get their parent directories - # Use -prune to avoid searching inside .git directories themselves - find "$search_dir" -name ".git" -type d -prune | while read -r git_dir; do - local repo_path; repo_path="$(dirname "$git_dir")" - local result_output - - # Call sync_repo and capture its stdout/stderr - result_output=$(sync_repo "$repo_path" 2>&1 || true) - - # Process the captured output for logging - echo "\$result_output" | while IFS= read -r line; do - if [[ "$line" =~ ^--- Processing:.* ]]; then - echo -e \"\n\${BLUE}\$line\${NC}\" - elif [[ "$line" =~ ^INFO:.* ]]; then - log_info "${line#INFO: }" - elif [[ "$line" =~ ^SUCCESS:.* ]]; then - log_success "${line#SUCCESS: }" - elif [[ "$line" =~ ^WARNING:.* ]]; then - log_warning "${line#WARNING: }" - elif [[ "$line" =~ ^ERROR:.* ]]; then - log_error "${line#ERROR: }" - else - echo "$line" - fi - done - done -} - -if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then - main "$@" -fi \ No newline at end of file diff --git a/tmux/session-manager.sh b/tmux/session-manager.sh index 3c875ea..63fe903 100755 --- a/tmux/session-manager.sh +++ b/tmux/session-manager.sh @@ -101,7 +101,7 @@ handle_session() { # Main function main() { - local verbose=false +# verbose=false local session_type="" # Parse arguments From ed8405e4b061b9c6fbfa798c3187493a46d3438e Mon Sep 17 00:00:00 2001 From: Garret Patten Date: Fri, 15 May 2026 16:56:02 -0400 Subject: [PATCH 4/5] prettify --- code-backup/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code-backup/README.md b/code-backup/README.md index dca00b8..650f689 100644 --- a/code-backup/README.md +++ b/code-backup/README.md @@ -3,9 +3,9 @@ This directory contains two scripts for backing up your GitHub repositories: 1. **`code-backup-local.sh`** - Creates a local, zipped directory of all your -non-archived projects + non-archived projects 2. **`code-backup-gitlab.sh`** - Mirrors all non-archived public and private -projects to similarly named GitLab projects + projects to similarly named GitLab projects --- @@ -264,7 +264,7 @@ repositories are automatically excluded. - **Local backup**: Requires `GITHUB_TOKEN` to access private repos - **GitLab mirror**: Requires both `GITHUB_TOKEN` (for GitHub) and -`GITLAB_TOKEN` (for GitLab) + `GITLAB_TOKEN` (for GitLab) ### SSH vs HTTPS @@ -280,7 +280,7 @@ don't exist: - Set `AUTO_CREATE_GITLAB_PROJECTS="true"` (default) - New projects will be created with visibility set by -`GITLAB_VISIBILITY` (default: `private`) + `GITLAB_VISIBILITY` (default: `private`) ### Submodules From 87569247a41bd51d81cdc4079a7da584cd488a51 Mon Sep 17 00:00:00 2001 From: Garret Patten Date: Fri, 15 May 2026 17:11:35 -0400 Subject: [PATCH 5/5] fix: shellcheck --- code-backup/code-backup-local.sh | 4 ++-- tmux/session-manager.sh | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/code-backup/code-backup-local.sh b/code-backup/code-backup-local.sh index 6772c92..aed0fa4 100755 --- a/code-backup/code-backup-local.sh +++ b/code-backup/code-backup-local.sh @@ -213,7 +213,7 @@ update_repository() { sync_script="$(dirname "$SCRIPT_DIR")/git-scripts/sync-all.sh" if [[ -f "$sync_script" ]]; then - # shellcheck source=../git-scripts/sync-all.sh + # shellcheck disable=SC1090,SC1091 source "$sync_script" local sync_output @@ -273,7 +273,7 @@ clone_repository() { if cd "$repo_path" 2>/dev/null; then if [[ -f "$sync_script" ]]; then - # shellcheck source=../git-scripts/sync-all.sh + # shellcheck disable=SC1090,SC1091 source "$sync_script" local default_branch if default_branch=$(get_default_branch 2>/dev/null); then diff --git a/tmux/session-manager.sh b/tmux/session-manager.sh index 63fe903..a06a8bc 100755 --- a/tmux/session-manager.sh +++ b/tmux/session-manager.sh @@ -7,6 +7,7 @@ set -euo pipefail # Source utility functions SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck disable=SC1090,SC1091 source "${SCRIPT_DIR}/tmux-utils.sh" # Configuration