From 5586826a06aa74764e4dc860ab1c342399a37263 Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Mon, 27 Jul 2026 01:57:06 -0700 Subject: [PATCH] chore(versions): refresh pins and make versions-pin show its work - pin sweep: claude-code 2.1.220, cctrace 0.23.0, gemini 0.52.0, grok 0.2.112, kimi 0.29.2, ccx v0.12.0, playwright 1.62.0, cloakbrowser wrapper 0.5.2 (which is also the Chromium pin) - update-version-pins.sh: grouped per-tool output with old -> new, fetch failures called out instead of silently keeping the old value, and a summary line - versions-pin CHANGELOG=1 prints upstream changelogs for the tools that actually moved, via the release-utils registry -- release notes stop being archaeology - keep the soft-fail contract: `new_val=$(fetch ...) || true`. A failing command substitution in a plain assignment takes its exit status and set -e kills the run, which had made the fetch-failed branch dead code and turned one dead registry into an aborted sweep - always write versions.env, even when nothing moved. The heredoc is a second copy of the file layout, so the rewrite is what proves no pin was dropped; skipping it left that guard unexercised on exactly the runs where every fetch failed - say "N fetches failed, upstream state unknown" instead of claiming "all pins up-to-date" when nothing was actually checked Verified: full round-trip byte-identical under a simulated total registry outage; tests/version-upgrade.sh green. --- Makefile | 5 +- scripts/update-version-pins.sh | 245 +++++++++++++++++++++++++++++---- versions.env | 16 +-- 3 files changed, 230 insertions(+), 36 deletions(-) mode change 100644 => 100755 scripts/update-version-pins.sh diff --git a/Makefile b/Makefile index f9d95e7..5c9eef7 100644 --- a/Makefile +++ b/Makefile @@ -294,8 +294,7 @@ versions: ./scripts/version-report.sh versions-pin: - @bash ./scripts/update-version-pins.sh - @echo "✅ Updated $(VERSION_PINS_FILE)" + @./scripts/update-version-pins.sh $(if $(filter 1 yes true,$(CHANGELOG)),--changelog) toolchains: @bash ./scripts/toolchain-report.sh @@ -459,6 +458,7 @@ help: @echo " versions-up Build both images with latest upstream agent versions" @echo " ONLY=cctrace upgrades one tool, rest stay pinned" @echo " versions-pin Refresh $(VERSION_PINS_FILE) from upstream" + @echo " CHANGELOG=1 also shows changelogs for updated tools" @echo " scripts List repo helper scripts" @echo " commands Alias for help" @echo " test Test main image" @@ -511,6 +511,7 @@ help: @echo " make toolchains # Show pinned toolchain inventory" @echo " make scripts # List helper scripts" @echo " make versions-pin # Refresh shared pin file" + @echo " make versions-pin CHANGELOG=1 # Refresh pins + show changelogs" @echo " make versions # Check current versions" @echo " make PLAYWRIGHT_VERSION=1.60.0 build-rust # Override rust browser tooling" @echo " make versions-up # Upgrade to latest upstream versions" diff --git a/scripts/update-version-pins.sh b/scripts/update-version-pins.sh old mode 100644 new mode 100755 index c169e7a..b525ea8 --- a/scripts/update-version-pins.sh +++ b/scripts/update-version-pins.sh @@ -1,23 +1,42 @@ #!/usr/bin/env bash # update-version-pins.sh - Refresh shared version pins from upstream sources +# +# Verbose TUI with real-time fetch progress, grouped version comparison, +# and optional changelog display for updated tools. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1091 source "$SCRIPT_DIR/version-pins.sh" +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/release-utils.sh" DRY_RUN=0 +SHOW_CHANGELOG=0 +IS_TTY=0 +[[ -t 1 ]] && IS_TTY=1 + +N_UPDATED=0 +N_UNCHANGED=0 +N_FAILED=0 +UPDATED_VARS=() usage() { cat <<'EOF' -Usage: update-version-pins.sh [--dry-run] +Usage: update-version-pins.sh [OPTIONS] Refresh shared version pins from upstream sources and rewrite versions.env. -Only versions we intentionally pin are updated here. + +Options: + --dry-run Preview changes without writing versions.env + --changelog Show changelogs for updated tools + -h, --help Show this help EOF } +# ── Fetch helpers (soft-fail: empty string on error) ───────────────────── + fetch_npm_version() { curl -fsSL --max-time 10 \ "https://registry.npmjs.org/-/package/$1/dist-tags" 2>/dev/null | \ @@ -43,13 +62,130 @@ fetch_latest_commit() { git ls-remote "$1" "$2" 2>/dev/null | awk 'NR == 1 { print $1 }' } -refresh_pin() { - local var_name=$1 fetched=$2 - if [[ -n "$fetched" ]]; then - printf -v "$var_name" '%s' "$fetched" +# ── Pin: fetch one version and display result ──────────────────────────── +# Usage: pin NAME VAR_NAME FETCH_TYPE [FETCH_ARGS...] + +pin() { + local name=$1 var=$2 fetch_type=$3 + shift 3 + local old_val=${!var:-} + local pad + pad=$(printf '%-16s' "$name") + + # Live progress (tty only -- overwritten when done) + if [[ $IS_TTY -eq 1 ]]; then + echo -en " ${CYAN}│${RESET} ${DIM}⟳ ${pad} fetching...${RESET}" + fi + + # Fetch upstream. `|| true` is load-bearing: a failing command + # substitution in a plain assignment takes its exit status, and under + # `set -e` that aborts the whole run -- which would make the fetch-failed + # branch below unreachable and kill the soft-fail contract. One dead + # registry must degrade to a warning, not end the sweep. + local new_val="" + case $fetch_type in + go) new_val=$(fetch_go_version) || true ;; + npm) new_val=$(fetch_npm_version "$1") || true ;; + git-tag) new_val=$(fetch_latest_git_tag "$1") || true ;; + git-commit) new_val=$(fetch_latest_commit "$1" "$2") || true ;; + esac + + # Clear fetching line + if [[ $IS_TTY -eq 1 ]]; then + echo -en "\r\033[K" + fi + + # Fetch failed -- keep old value, warn + if [[ -z "$new_val" ]]; then + echo -e " ${CYAN}│${RESET} ${YELLOW}!${RESET} ${WHITE}${pad}${RESET} ${DIM}${old_val:-?}${RESET} ${YELLOW}(fetch failed)${RESET}" + N_FAILED=$((N_FAILED + 1)) + return + fi + + # Update variable in caller's scope + printf -v "$var" '%s' "$new_val" + + # Short hash for commits, semver for everything else + local old_disp=$old_val new_disp=$new_val + if [[ $fetch_type == "git-commit" ]]; then + old_disp="${old_val:0:7}" + new_disp="${new_val:0:7}" + fi + + if [[ "$old_val" == "$new_val" ]]; then + echo -e " ${CYAN}│${RESET} ${DIM}· ${pad} ${new_disp} (up-to-date)${RESET}" + N_UNCHANGED=$((N_UNCHANGED + 1)) + else + echo -e " ${CYAN}│${RESET} ${GREEN}▲${RESET} ${WHITE}${pad}${RESET} ${RED}${old_disp:-new}${RESET} ${DIM}->${RESET} ${GREEN}${new_disp}${RESET}" + N_UPDATED=$((N_UPDATED + 1)) + UPDATED_VARS+=("${var}|${old_val}|${new_val}") + fi +} + +# ── Changelog display ──────────────────────────────────────────────────── + +registry_tool() { + case $1 in + CLAUDE_CODE_VERSION) echo "claude-code" ;; + CCTRACE_VERSION) echo "cctrace" ;; + CODEX_VERSION) echo "codex" ;; + GEMINI_CLI_VERSION) echo "gemini-cli" ;; + GROK_CLI_VERSION) echo "grok-cli" ;; + KIMI_CODE_VERSION) echo "kimi-code" ;; + CCX_VERSION) echo "ccx" ;; + COPILOT_API_VERSION) echo "copilot-api" ;; + PLAYWRIGHT_VERSION) echo "playwright" ;; + esac +} + +show_changelogs() { + local shown=0 + for entry in ${UPDATED_VARS[@]+"${UPDATED_VARS[@]}"}; do + IFS='|' read -r var old new <<< "$entry" + + local tool + tool=$(registry_tool "$var") + [[ -z "$tool" ]] && continue + + local changelog_source + changelog_source=$(get_tool_field "$tool" changelog 2>/dev/null) || true + [[ -z "$changelog_source" ]] && continue + + local name + name=$(get_display_name "$tool") + + [[ $shown -eq 0 ]] && echo "" + shown=1 + + section "$name ${old} -> ${new}" + + if [[ $IS_TTY -eq 1 ]]; then + echo -en " ${DIM}fetching changelog...${RESET}" + fi + + local changes + changes=$(fetch_changelog "$tool" "$old" "$new") + + if [[ $IS_TTY -eq 1 ]]; then + echo -en "\r\033[K" + fi + + if [[ -n "$changes" ]]; then + echo "$changes" | indent + else + echo -e " ${DIM}(changelog unavailable)${RESET}" + fi + echo "" + done + + if [[ $shown -eq 0 ]]; then + echo -e "${DIM}No changelogs available for updated tools.${RESET}" + echo "" fi } +# ── Write versions.env ─────────────────────────────────────────────────── + write_version_pins() { cat > "$VERSION_PINS_FILE" <&2 usage >&2 @@ -100,28 +233,88 @@ while [[ $# -gt 0 ]]; do esac done +# ── Main ───────────────────────────────────────────────────────────────── + main() { load_version_pins - refresh_pin GO_VERSION "$(fetch_go_version)" - refresh_pin DELTA_VERSION "$(fetch_latest_git_tag https://github.com/dandavison/delta.git)" - refresh_pin CLAUDE_CODE_VERSION "$(fetch_npm_version @anthropic-ai/claude-code)" - refresh_pin CCTRACE_VERSION "$(fetch_npm_version @thevibeworks/cctrace)" - refresh_pin CODEX_VERSION "$(fetch_npm_version @openai/codex)" - refresh_pin GEMINI_CLI_VERSION "$(fetch_npm_version @google/gemini-cli)" - refresh_pin GROK_CLI_VERSION "$(fetch_npm_version @xai-official/grok)" - refresh_pin KIMI_CODE_VERSION "$(fetch_npm_version @moonshot-ai/kimi-code)" - refresh_pin CCX_VERSION "$(fetch_latest_git_tag https://github.com/thevibeworks/ccx.git)" - refresh_pin COPILOT_API_VERSION "$(fetch_latest_commit https://github.com/ericc-ch/copilot-api.git refs/heads/master)" - refresh_pin PLAYWRIGHT_VERSION "$(fetch_npm_version playwright)" - refresh_pin CLOAKBROWSER_WRAPPER_VERSION "$(fetch_npm_version cloakbrowser)" + echo -e "${CYAN}${BOLD}╔══════════════════════════════════════════════════╗${RESET}" + echo -e "${CYAN}${BOLD}║ Refreshing Version Pins ║${RESET}" + echo -e "${CYAN}${BOLD}╚══════════════════════════════════════════════════╝${RESET}" + echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S')${RESET}" + [[ $DRY_RUN -eq 1 ]] && echo -e "${YELLOW}(dry run)${RESET}" + echo "" + + # ── Toolchains ─────────────────────────────────────────────────────── + echo -e " ${CYAN}┌─${BOLD} Toolchains ${RESET}${CYAN}──────────────────────────────────────${RESET}" + + pin "Go" GO_VERSION go + pin "delta" DELTA_VERSION git-tag "https://github.com/dandavison/delta.git" + + # ── Agent CLIs ─────────────────────────────────────────────────────── + echo -e " ${CYAN}│${RESET}" + echo -e " ${CYAN}├─${BOLD} Agent CLIs ${RESET}${CYAN}──────────────────────────────────────${RESET}" + + pin "Claude Code" CLAUDE_CODE_VERSION npm "@anthropic-ai/claude-code" + pin "cctrace" CCTRACE_VERSION npm "@thevibeworks/cctrace" + pin "Codex" CODEX_VERSION npm "@openai/codex" + pin "Gemini CLI" GEMINI_CLI_VERSION npm "@google/gemini-cli" + pin "Grok CLI" GROK_CLI_VERSION npm "@xai-official/grok" + pin "Kimi Code" KIMI_CODE_VERSION npm "@moonshot-ai/kimi-code" + pin "CCX" CCX_VERSION git-tag "https://github.com/thevibeworks/ccx.git" + pin "Copilot API" COPILOT_API_VERSION git-commit "https://github.com/ericc-ch/copilot-api.git" "refs/heads/master" + + # ── Browser Tools ──────────────────────────────────────────────────── + echo -e " ${CYAN}│${RESET}" + echo -e " ${CYAN}├─${BOLD} Browser Tools ${RESET}${CYAN}───────────────────────────────────${RESET}" + + pin "Playwright" PLAYWRIGHT_VERSION npm "playwright" + pin "CloakBrowser" CLOAKBROWSER_WRAPPER_VERSION npm "cloakbrowser" + # ── Summary footer ─────────────────────────────────────────────────── + echo -e " ${CYAN}│${RESET}" + + local parts=() + [[ $N_UPDATED -gt 0 ]] && parts+=("${GREEN}${N_UPDATED} updated${RESET}") + [[ $N_UNCHANGED -gt 0 ]] && parts+=("${DIM}${N_UNCHANGED} unchanged${RESET}") + [[ $N_FAILED -gt 0 ]] && parts+=("${YELLOW}${N_FAILED} failed${RESET}") + local summary="" + for i in "${!parts[@]}"; do + [[ $i -gt 0 ]] && summary+=", " + summary+="${parts[$i]}" + done + echo -e " ${CYAN}└─${RESET} ${summary} ${CYAN}──────────────────────────────────────${RESET}" + echo "" + + # ── Changelogs (opt-in) ────────────────────────────────────────────── + if [[ $SHOW_CHANGELOG -eq 1 ]] && [[ $N_UPDATED -gt 0 ]]; then + show_changelogs + fi + + # ── Result ─────────────────────────────────────────────────────────── if [[ $DRY_RUN -eq 1 ]]; then + echo -e "${YELLOW}Dry run — would write:${RESET}" + echo "" emit_version_pins return 0 fi + # Write even when nothing moved. The heredoc in write_version_pins is a + # second copy of the file layout, so a rewrite is what proves no pin was + # dropped from it; skipping the write when N_UPDATED=0 would leave that + # guard (tests/version-upgrade.sh) unexercised on exactly the runs where + # every fetch failed. write_version_pins + if [[ $N_UPDATED -eq 0 ]]; then + if [[ $N_FAILED -gt 0 ]]; then + echo -e "${YELLOW}No pins moved, but ${N_FAILED} fetch(es) failed -- upstream state unknown for those.${RESET}" + else + echo -e "${GREEN}All pins up-to-date.${RESET}" + fi + return 0 + fi + echo -e "${GREEN}Updated ${VERSION_PINS_FILE##*/}${RESET}" + echo -e "${DIM}Run 'make versions-up' to rebuild images.${RESET}" } main "$@" diff --git a/versions.env b/versions.env index 0183027..6c68605 100644 --- a/versions.env +++ b/versions.env @@ -8,19 +8,19 @@ DELTA_VERSION=0.19.2 TMUX_VERSION=3.6a TMUX_SHA256=b6d8d9c76585db8ef5fa00d4931902fa4b8cbe8166f528f44fc403961a3f3759 -CLAUDE_CODE_VERSION=2.1.217 -CCTRACE_VERSION=0.19.0 +CLAUDE_CODE_VERSION=2.1.220 +CCTRACE_VERSION=0.23.0 CODEX_VERSION=0.145.0 -GEMINI_CLI_VERSION=0.51.0 -GROK_CLI_VERSION=0.2.106 -KIMI_CODE_VERSION=0.28.1 -CCX_VERSION=v0.11.0 +GEMINI_CLI_VERSION=0.52.0 +GROK_CLI_VERSION=0.2.112 +KIMI_CODE_VERSION=0.29.2 +CCX_VERSION=v0.12.0 COPILOT_API_VERSION=0ea08febdd7e3e055b03dd298bf57e669500b5c1 -PLAYWRIGHT_VERSION=1.61.1 +PLAYWRIGHT_VERSION=1.62.0 # CloakBrowser npm wrapper version. This also pins the Chromium binary: # the wrapper hardcodes per-arch free-binary versions (linux-x64 146.x.x.5, # linux-arm64 146.x.x.3), so bumping the wrapper is what moves Chromium. -CLOAKBROWSER_WRAPPER_VERSION=0.4.12 +CLOAKBROWSER_WRAPPER_VERSION=0.5.2 RUST_TOOLCHAINS=stable RUST_DEFAULT_TOOLCHAIN=stable