mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-08 01:51:14 +00:00
* fix: add error logging to empty catch blocks in test helpers Previously, test helper functions had 14 empty catch blocks that silently swallowed all errors during cleanup operations (reading and deleting temporary stderr files). This change adds error logging that: - Allows expected errors (ENOENT for missing files, exit code 1 for cat) - Logs unexpected errors to console for debugging This improves test reliability by surfacing unexpected filesystem or permission errors that could indicate real problems, while still allowing the intended best-effort cleanup behavior. Fixes: Empty catch blocks in 6 test files Impact: Better test debugging and error visibility Agent: code-health Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: improve error handling in Python fallback and directory deletion 1. Python arithmetic fallback (shared/common.sh:713): - Changed from: || echo "$((elapsed + 1))" - Changed to: explicit if/else with error detection - Impact: Python errors are now properly caught instead of masked by || 2. Unvalidated directory deletion (cli/install.sh:142): - Added path validation before rm -rf - Checks: path is within dest directory AND directory exists - Impact: Prevents accidental deletion if variables are malformed Both changes improve safety and error visibility without breaking existing functionality. Agent: code-health Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: spawn-bot <bot@openrouter.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
217 lines
6.7 KiB
Bash
Executable file
217 lines
6.7 KiB
Bash
Executable file
#!/bin/bash
|
|
# Installer for the spawn CLI
|
|
#
|
|
# Usage:
|
|
# curl -fsSL https://raw.githubusercontent.com/OpenRouterTeam/spawn/main/cli/install.sh | bash
|
|
#
|
|
# This installs spawn via bun. If bun is not available, it auto-installs it first.
|
|
#
|
|
# Override install directory:
|
|
# SPAWN_INSTALL_DIR=/usr/local/bin curl -fsSL ... | bash
|
|
|
|
set -eo pipefail
|
|
|
|
SPAWN_REPO="OpenRouterTeam/spawn"
|
|
SPAWN_RAW_BASE="https://raw.githubusercontent.com/${SPAWN_REPO}/main"
|
|
MIN_BUN_VERSION="1.2.0"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
CYAN='\033[0;36m'
|
|
|
|
log_info() { printf "${GREEN}[spawn]${NC} %s\n" "$1"; }
|
|
log_step() { printf "${CYAN}[spawn]${NC} %s\n" "$1"; }
|
|
log_warn() { printf "${YELLOW}[spawn]${NC} %s\n" "$1"; }
|
|
log_error() { printf "${RED}[spawn]${NC} %s\n" "$1"; }
|
|
|
|
# --- Helper: compare semver strings ---
|
|
# Returns 0 (true) if $1 >= $2
|
|
version_gte() {
|
|
local IFS='.'
|
|
local a=($1) b=($2)
|
|
local i=0
|
|
while [ $i -lt ${#b[@]} ]; do
|
|
local av="${a[$i]:-0}"
|
|
local bv="${b[$i]:-0}"
|
|
if [ "$av" -lt "$bv" ]; then
|
|
return 1
|
|
elif [ "$av" -gt "$bv" ]; then
|
|
return 0
|
|
fi
|
|
i=$((i + 1))
|
|
done
|
|
return 0
|
|
}
|
|
|
|
# --- Helper: ensure bun meets minimum version ---
|
|
ensure_min_bun_version() {
|
|
local current
|
|
current="$(bun --version)"
|
|
if ! version_gte "$current" "$MIN_BUN_VERSION"; then
|
|
log_warn "bun ${current} is below minimum ${MIN_BUN_VERSION}, upgrading..."
|
|
bun upgrade
|
|
current="$(bun --version)"
|
|
if ! version_gte "$current" "$MIN_BUN_VERSION"; then
|
|
log_error "Failed to upgrade bun to >= ${MIN_BUN_VERSION} (got ${current})"
|
|
echo ""
|
|
echo "Please upgrade bun manually:"
|
|
echo " bun upgrade"
|
|
echo ""
|
|
echo "Then re-run:"
|
|
echo " curl -fsSL ${SPAWN_RAW_BASE}/cli/install.sh | bash"
|
|
exit 1
|
|
fi
|
|
log_info "bun upgraded to ${current}"
|
|
fi
|
|
}
|
|
|
|
# --- Helper: find the best install directory ---
|
|
# Picks the first directory that exists AND is in PATH
|
|
find_install_dir() {
|
|
if [ -n "${SPAWN_INSTALL_DIR:-}" ]; then
|
|
echo "${SPAWN_INSTALL_DIR}"
|
|
return
|
|
fi
|
|
# Check common bin dirs in order of preference
|
|
local dirs=(
|
|
"${HOME}/.local/bin"
|
|
"$(bun pm bin -g 2>/dev/null)"
|
|
"${HOME}/.bun/bin"
|
|
"${HOME}/bin"
|
|
)
|
|
for dir in "${dirs[@]}"; do
|
|
[ -z "$dir" ] && continue
|
|
if echo "${PATH}" | tr ':' '\n' | grep -qx "$dir"; then
|
|
echo "$dir"
|
|
return
|
|
fi
|
|
done
|
|
# Nothing in PATH — default to ~/.local/bin and warn later
|
|
echo "${HOME}/.local/bin"
|
|
}
|
|
|
|
# --- Helper: show PATH instructions if spawn isn't findable ---
|
|
ensure_in_path() {
|
|
local install_dir="$1"
|
|
if echo "${PATH}" | tr ':' '\n' | grep -qx "${install_dir}"; then
|
|
echo ""
|
|
"${install_dir}/spawn" version
|
|
echo ""
|
|
printf "${GREEN}[spawn]${NC} Run ${BOLD}spawn${NC} to get started\n"
|
|
else
|
|
echo ""
|
|
log_warn "${BOLD}${install_dir}${NC}${YELLOW} is not in your PATH${NC}"
|
|
echo ""
|
|
case "${SHELL:-/bin/bash}" in
|
|
*/zsh)
|
|
echo " Run this, then reopen your terminal:"
|
|
echo ""
|
|
echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.zshrc"
|
|
;;
|
|
*/fish)
|
|
echo " Run this, then reopen your terminal:"
|
|
echo ""
|
|
echo " fish_add_path ${install_dir}"
|
|
;;
|
|
*)
|
|
echo " Run this, then reopen your terminal:"
|
|
echo ""
|
|
echo " echo 'export PATH=\"${install_dir}:\$PATH\"' >> ~/.bashrc"
|
|
;;
|
|
esac
|
|
echo ""
|
|
echo " Or run directly: ${install_dir}/spawn"
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
clone_cli() {
|
|
local dest="$1"
|
|
if command -v git &>/dev/null; then
|
|
log_step "Cloning CLI source..."
|
|
git clone --depth 1 --filter=blob:none --sparse \
|
|
"https://github.com/${SPAWN_REPO}.git" "${dest}/repo" 2>/dev/null
|
|
cd "${dest}/repo"
|
|
git sparse-checkout set cli 2>/dev/null
|
|
mv cli "${dest}/cli"
|
|
cd "${dest}"
|
|
# Safety check: only delete if path contains 'repo' and is within dest directory
|
|
if [[ "${dest}/repo" == "${dest}/"* ]] && [[ -d "${dest}/repo" ]]; then
|
|
rm -rf "${dest}/repo"
|
|
fi
|
|
else
|
|
log_step "Downloading CLI source..."
|
|
mkdir -p "${dest}/cli/src"
|
|
# Download all source files via GitHub API
|
|
local files
|
|
files=$(curl -fsSL "https://api.github.com/repos/${SPAWN_REPO}/contents/cli/src" \
|
|
| grep '"name"' | grep '\.ts"' | grep -v '__tests__' \
|
|
| sed 's/.*"name": "//;s/".*//')
|
|
curl -fsSL "${SPAWN_RAW_BASE}/cli/package.json" -o "${dest}/cli/package.json"
|
|
curl -fsSL "${SPAWN_RAW_BASE}/cli/bun.lock" -o "${dest}/cli/bun.lock"
|
|
curl -fsSL "${SPAWN_RAW_BASE}/cli/tsconfig.json" -o "${dest}/cli/tsconfig.json"
|
|
for f in $files; do
|
|
curl -fsSL "${SPAWN_RAW_BASE}/cli/src/${f}" -o "${dest}/cli/src/${f}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# --- Helper: build and install the CLI using bun ---
|
|
build_and_install() {
|
|
tmpdir=$(mktemp -d)
|
|
trap 'rm -rf "${tmpdir}"' EXIT
|
|
|
|
clone_cli "${tmpdir}"
|
|
|
|
cd "${tmpdir}/cli"
|
|
bun install
|
|
|
|
if ! bun run build 2>/dev/null; then
|
|
log_warn "Local build failed, downloading pre-built binary..."
|
|
curl -fsSL "https://github.com/${SPAWN_REPO}/releases/download/cli-latest/cli.js" -o cli.js
|
|
if [ ! -s cli.js ]; then
|
|
log_error "Failed to download pre-built binary"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
INSTALL_DIR="$(find_install_dir)"
|
|
mkdir -p "${INSTALL_DIR}"
|
|
cp cli.js "${INSTALL_DIR}/spawn"
|
|
chmod +x "${INSTALL_DIR}/spawn"
|
|
|
|
log_info "Installed spawn to ${INSTALL_DIR}/spawn"
|
|
ensure_in_path "${INSTALL_DIR}"
|
|
}
|
|
|
|
# --- Install bun if not present ---
|
|
if ! command -v bun &>/dev/null; then
|
|
log_step "bun not found. Installing bun..."
|
|
curl -fsSL https://bun.sh/install | bash
|
|
|
|
# Source the updated PATH so bun is available immediately
|
|
export BUN_INSTALL="${HOME}/.bun"
|
|
export PATH="${BUN_INSTALL}/bin:${PATH}"
|
|
|
|
if ! command -v bun &>/dev/null; then
|
|
log_error "Failed to install bun automatically"
|
|
echo ""
|
|
echo "Please install bun manually:"
|
|
echo " curl -fsSL https://bun.sh/install | bash"
|
|
echo ""
|
|
echo "Then re-run:"
|
|
echo " curl -fsSL ${SPAWN_RAW_BASE}/cli/install.sh | bash"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "bun installed successfully"
|
|
fi
|
|
|
|
ensure_min_bun_version
|
|
|
|
log_step "Installing spawn via bun..."
|
|
build_and_install
|