Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions scripts/cargo_update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,30 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROMPT_DIR="${SCRIPT_DIR}/cargo_update"

# --- Colors (disabled when not a tty or NO_COLOR is set) --------------------
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'
C_BLUE=$'\033[34m'; C_MAGENTA=$'\033[35m'; C_CYAN=$'\033[36m'
else
C_RESET=; C_BOLD=; C_DIM=; C_RED=; C_GREEN=; C_YELLOW=; C_BLUE=; C_MAGENTA=; C_CYAN=
fi

# REPO_LABEL can be set by the caller (e.g. cargo_update_all.sh) to prefix logs
LABEL="${REPO_LABEL:-cargo_update}"
log() { echo "${C_CYAN}[${LABEL}]${C_RESET} $*"; }
info() { echo "${C_BLUE}[${LABEL}]${C_RESET} $*"; }
warn() { echo "${C_YELLOW}[${LABEL}]${C_RESET} $*" >&2; }
error() { echo "${C_RED}[${LABEL}] ERROR:${C_RESET} $*" >&2; }

# --- Locate a JS package runner (pnpm dlx preferred, npx fallback) ----------
find_runner() {
if command -v pnpm &>/dev/null; then
echo "pnpm dlx"
elif command -v npx &>/dev/null; then
echo "npx"
else
echo "Error: neither pnpm nor npx found. Install one of them first." >&2
error "neither pnpm nor npx found. Install one of them first."
exit 1
fi
}
Expand Down Expand Up @@ -72,36 +88,42 @@ open_url() {
ALLOWED_TOOLS="Read Edit Write Bash"

# --- Execute or dry-run -----------------------------------------------------
print_config() {
info "Runner: ${C_BOLD}${RUNNER}${C_RESET}"
info "Target: ${C_BOLD}${TARGET_DIR}${C_RESET}"
info "Prompt length: ${C_BOLD}${#PROMPT}${C_RESET} chars"
info "Allowed tools: ${C_DIM}${ALLOWED_TOOLS}${C_RESET}"
}

if [[ "$EXECUTE" == true ]]; then
echo "Using runner: ${RUNNER}"
echo "Target: ${TARGET_DIR}"
echo "Prompt length: ${#PROMPT} chars"
echo "Allowed tools: ${ALLOWED_TOOLS}"
echo "---"
log "${C_GREEN}${C_BOLD}▶ EXECUTE${C_RESET} updating ${C_MAGENTA}${LABEL}${C_RESET}"
print_config
log "${C_DIM}──── claude output ────${C_RESET}"
cd "$TARGET_DIR"
OUTPUT="$(echo "${PROMPT}" | ${RUNNER} @anthropic-ai/claude-code --print \
--allowed-tools ${ALLOWED_TOOLS})"
echo "$OUTPUT"
log "${C_DIM}──── end output ────${C_RESET}"

# Try to extract a PR URL from the output and open it
PR_URL="$(echo "$OUTPUT" | grep -oE 'https://github\.com/[^ ]+/pull/[0-9]+' | head -1 || true)"
if [[ -n "$PR_URL" ]]; then
echo "Opening PR: ${PR_URL}"
log "${C_GREEN}✔ PR for ${C_MAGENTA}${LABEL}${C_GREEN}:${C_RESET} ${C_BOLD}${PR_URL}${C_RESET}"
log "Opening in browser…"
open_url "$PR_URL"
else
log "${C_YELLOW}No PR URL detected in output for ${LABEL}${C_RESET}"
fi
else
echo "=== DRY RUN ==="
echo ""
log "${C_YELLOW}${C_BOLD}=== DRY RUN ===${C_RESET} (${C_MAGENTA}${LABEL}${C_RESET})"
echo
echo "${PROMPT}"
echo "---"
echo "Runner: ${RUNNER}"
echo "Target: ${TARGET_DIR}"
echo "Prompt length: ${#PROMPT} chars"
echo "Allowed tools: ${ALLOWED_TOOLS}"
echo ""
echo "Would run:"
echo " cd ${TARGET_DIR}"
echo " echo \"\${PROMPT}\" | ${RUNNER} @anthropic-ai/claude-code --print --allowed-tools ${ALLOWED_TOOLS}"
echo ""
echo "Pass --execute to run this against Claude Code."
log "${C_DIM}────────────────${C_RESET}"
print_config
echo
log "Would run:"
echo " ${C_DIM}cd ${TARGET_DIR}${C_RESET}"
echo " ${C_DIM}echo \"\${PROMPT}\" | ${RUNNER} @anthropic-ai/claude-code --print --allowed-tools ${ALLOWED_TOOLS}${C_RESET}"
echo
log "Pass ${C_BOLD}--execute${C_RESET} to run this against Claude Code."
fi
86 changes: 69 additions & 17 deletions scripts/cargo_update_all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,27 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOWNSTREAM_FILE="${SCRIPT_DIR}/downstream.txt"
CARGO_UPDATE="${SCRIPT_DIR}/cargo_update.sh"

# --- Colors (disabled when not a tty or NO_COLOR is set) --------------------
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
C_RESET=$'\033[0m'
C_BOLD=$'\033[1m'
C_DIM=$'\033[2m'
C_RED=$'\033[31m'
C_GREEN=$'\033[32m'
C_YELLOW=$'\033[33m'
C_BLUE=$'\033[34m'
C_MAGENTA=$'\033[35m'
C_CYAN=$'\033[36m'
else
C_RESET=; C_BOLD=; C_DIM=; C_RED=; C_GREEN=; C_YELLOW=; C_BLUE=; C_MAGENTA=; C_CYAN=
fi

log() { echo "${C_CYAN}[update-all]${C_RESET} $*"; }
warn() { echo "${C_YELLOW}[update-all]${C_RESET} $*" >&2; }
error() { echo "${C_RED}[update-all] ERROR:${C_RESET} $*" >&2; }

if [[ ! -f "$DOWNSTREAM_FILE" ]]; then
echo "Error: ${DOWNSTREAM_FILE} not found" >&2
error "${DOWNSTREAM_FILE} not found"
exit 1
fi

Expand All @@ -15,46 +34,79 @@ ARGS=("$@")

# Create a shared tmp directory for all clones
WORK_DIR="$(mktemp -d)"
echo "Clone directory: ${WORK_DIR}"
log "Clone directory: ${C_DIM}${WORK_DIR}${C_RESET}"
if [[ ${#ARGS[@]} -gt 0 ]]; then
log "Forwarding args: ${C_BOLD}${ARGS[*]}${C_RESET}"
else
log "Mode: ${C_YELLOW}dry-run${C_RESET} (pass --execute to apply)"
fi

PIDS=()
# Collect repos first so we can report a plan
REPOS=()
CLONE_DIRS=()

while IFS= read -r repo_url; do
# Skip empty lines and comments
[[ -z "$repo_url" || "$repo_url" == \#* ]] && continue
REPOS+=("$repo_url")
done < "$DOWNSTREAM_FILE"

log "Found ${C_BOLD}${#REPOS[@]}${C_RESET} downstream repo(s):"
for r in "${REPOS[@]}"; do
echo " ${C_DIM}•${C_RESET} $r"
done

PIDS=()
CLONE_DIRS=()
REPO_NAMES=()

for repo_url in "${REPOS[@]}"; do
# Derive repo name from URL (e.g. https://github.com/sksizer/rust-dir-aspect/ -> rust-dir-aspect)
REPO_NAME="$(basename "${repo_url%/}")"
REPO_NAME="$(basename "${repo_url%/}" .git)"
CLONE_PATH="${WORK_DIR}/${REPO_NAME}"
LOG_FILE="${WORK_DIR}/${REPO_NAME}.log"

echo "Cloning: ${repo_url} -> ${CLONE_PATH}"
log "${C_MAGENTA}▶ ${REPO_NAME}${C_RESET} — cloning ${C_DIM}${repo_url}${C_RESET}"
(
git clone --quiet "$repo_url" "$CLONE_PATH"
bash "$CARGO_UPDATE" ${ARGS[@]+"${ARGS[@]}"} "$CLONE_PATH"
{
echo "${C_BOLD}${C_MAGENTA}=== ${REPO_NAME} ===${C_RESET}"
echo "${C_CYAN}[${REPO_NAME}]${C_RESET} repo: ${repo_url}"
echo "${C_CYAN}[${REPO_NAME}]${C_RESET} clone path: ${CLONE_PATH}"
git clone --quiet "$repo_url" "$CLONE_PATH"
echo "${C_CYAN}[${REPO_NAME}]${C_RESET} running cargo_update.sh ${ARGS[*]:-}"
REPO_LABEL="$REPO_NAME" bash "$CARGO_UPDATE" ${ARGS[@]+"${ARGS[@]}"} "$CLONE_PATH"
} >"$LOG_FILE" 2>&1
) &
PIDS+=($!)
REPOS+=("$repo_url")
REPO_NAMES+=("$REPO_NAME")
CLONE_DIRS+=("$CLONE_PATH")
done < "$DOWNSTREAM_FILE"
done

# Wait for all and report results
FAILED=0
for i in "${!PIDS[@]}"; do
NAME="${REPO_NAMES[$i]}"
URL="${REPOS[$i]}"
LOG_FILE="${WORK_DIR}/${NAME}.log"
if wait "${PIDS[$i]}"; then
echo "Done: ${REPOS[$i]}"
echo
echo "${C_GREEN}${C_BOLD}✔ ${NAME}${C_RESET} ${C_DIM}(${URL})${C_RESET}"
[[ -f "$LOG_FILE" ]] && sed "s/^/ ${C_DIM}│${C_RESET} /" "$LOG_FILE"
else
echo "FAILED: ${REPOS[$i]}" >&2
echo
echo "${C_RED}${C_BOLD}✘ ${NAME} FAILED${C_RESET} ${C_DIM}(${URL})${C_RESET}" >&2
[[ -f "$LOG_FILE" ]] && sed "s/^/ ${C_RED}│${C_RESET} /" "$LOG_FILE" >&2
FAILED=$((FAILED + 1))
fi
done

echo "---"
echo "Finished: $((${#PIDS[@]} - FAILED))/${#PIDS[@]} succeeded"
echo
echo "${C_BOLD}────────────────────────────────────────${C_RESET}"
if [[ $FAILED -eq 0 ]]; then
log "${C_GREEN}All ${#PIDS[@]} repo(s) succeeded${C_RESET}"
else
log "${C_YELLOW}$((${#PIDS[@]} - FAILED))/${#PIDS[@]} succeeded, ${C_RED}${FAILED} failed${C_RESET}"
fi

# Clean up clones
echo "Cleaning up: ${WORK_DIR}"
log "Cleaning up: ${C_DIM}${WORK_DIR}${C_RESET}"
rm -rf "$WORK_DIR"

exit $FAILED
Loading