543 lines
18 KiB
Bash
Executable File
543 lines
18 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -e
|
||
|
||
# oneper - Install Continue VS Code extension from PR builds or latest pre-release
|
||
# Usage:
|
||
# oneper install --pr X - Install VSIX from PR number X
|
||
# oneper install --latest - Install latest pre-release from marketplace
|
||
|
||
REPO="continuedev/continue"
|
||
ONEPER_DIR="$HOME/.continue/.utils/oneper"
|
||
|
||
show_help() {
|
||
echo "oneper - Install Continue VS Code extension from builds or latest pre-release"
|
||
echo ""
|
||
echo "Usage: oneper <command> [options]"
|
||
echo " oneper -h | --help"
|
||
echo " oneper install # Install latest build from main branch (default)"
|
||
echo ""
|
||
echo "Commands:"
|
||
echo " install Download and install VSIX from latest main branch build (default)"
|
||
echo " install --pr <number> [--platform <platform>]"
|
||
echo " Download and install VSIX from specified PR number"
|
||
echo " Platform options: macos, linux (auto-detected by default)"
|
||
echo " install --latest Install latest pre-release from VS Code marketplace"
|
||
echo " setup-cron [--interval <minutes>] Set up automatic updates (default: 10 minutes)"
|
||
echo " remove-cron Remove automatic updates"
|
||
echo " clean Remove all downloaded VSIX files from oneper cache"
|
||
echo ""
|
||
echo "Options:"
|
||
echo " -h, --help Show this help message"
|
||
echo " --platform <platform> Specify platform for builds (macos, linux)
|
||
--interval <minutes> Set update interval in minutes (for setup-cron)"
|
||
echo ""
|
||
echo "Examples:"
|
||
echo " oneper install # Install latest build from main branch"
|
||
echo " oneper install --pr 123 # Install VSIX from PR #123"
|
||
echo " oneper install --pr 123 --platform linux # Install Linux VSIX from PR #123"
|
||
echo " oneper install --latest # Install latest pre-release"
|
||
echo " oneper setup-cron # Auto-update every 10 minutes
|
||
oneper setup-cron --interval 5 # Auto-update every 5 minutes"
|
||
echo " oneper remove-cron # Stop auto-updates"
|
||
echo " oneper clean # Clear cached VSIX files"
|
||
echo " oneper -h # Show help"
|
||
echo ""
|
||
echo "VSIX files are cached in: ~/.continue/.utils/oneper/"
|
||
}
|
||
|
||
detect_os() {
|
||
case "$(uname -s)" in
|
||
"Darwin")
|
||
echo "macos"
|
||
;;
|
||
"Linux")
|
||
echo "linux"
|
||
;;
|
||
*)
|
||
echo "❌ Unsupported operating system: $(uname -s)" >&2
|
||
echo " Supported platforms: macOS, Linux" >&2
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
check_dependencies() {
|
||
if ! command -v gh &> /dev/null; then
|
||
echo "❌ GitHub CLI (gh) is required but not installed."
|
||
echo " Install with: brew install gh"
|
||
exit 1
|
||
fi
|
||
|
||
if ! command -v code &> /dev/null; then
|
||
echo "❌ VS Code CLI (code) is required but not installed."
|
||
echo " Install VS Code and ensure 'code' command is in PATH"
|
||
exit 1
|
||
fi
|
||
|
||
}
|
||
|
||
ensure_oneper_dir() {
|
||
if [ ! -d "$ONEPER_DIR" ]; then
|
||
echo "📁 Creating oneper directory: $ONEPER_DIR"
|
||
mkdir -p "$ONEPER_DIR"
|
||
fi
|
||
}
|
||
|
||
install_from_main() {
|
||
local platform=${1:-$(detect_os)} # Auto-detect OS if not specified
|
||
|
||
# Map platform names to GitHub runner OS names
|
||
local artifact_suffix
|
||
case "$platform" in
|
||
"macos")
|
||
artifact_suffix="macOS"
|
||
;;
|
||
"linux")
|
||
artifact_suffix="Linux"
|
||
;;
|
||
*)
|
||
echo "❌ Unknown platform: $platform"
|
||
echo " Supported platforms: macos, linux"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
echo "🔍 Finding latest main branch build..."
|
||
|
||
# Get the latest successful workflow run for main branch
|
||
local run_id=$(gh run list \
|
||
--repo "$REPO" \
|
||
--workflow="main-build.yaml" \
|
||
--branch="main" \
|
||
--json databaseId,status,conclusion \
|
||
--jq ".[] | select(.status == \"completed\" and .conclusion == \"success\") | .databaseId" \
|
||
| head -1)
|
||
|
||
if [ -z "$run_id" ]; then
|
||
echo "❌ No successful main branch build found"
|
||
echo " Make sure there are recent commits to main with successful builds"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Found workflow run: https://github.com/$REPO/actions/runs/$run_id"
|
||
|
||
# Try to get the original PR number from the workflow run
|
||
local original_pr=$(gh run view "$run_id" --repo "$REPO" --json displayTitle --jq '.displayTitle' | grep -o "pull request #[0-9]*" | grep -o "[0-9]*" || echo "")
|
||
|
||
if [ -n "$original_pr" ]; then
|
||
echo "📦 This contains artifacts originally from PR #$original_pr"
|
||
fi
|
||
|
||
echo "📥 Downloading vscode-extension-build-$artifact_suffix artifact..."
|
||
|
||
# Create temporary directory for download
|
||
local temp_dir=$(mktemp -d)
|
||
|
||
# Download the artifact
|
||
if ! gh run download "$run_id" \
|
||
--repo "$REPO" \
|
||
--name "vscode-extension-build-$artifact_suffix" \
|
||
--dir "$temp_dir"; then
|
||
echo "❌ Failed to download artifact vscode-extension-build-$artifact_suffix"
|
||
echo " Make sure the workflow run completed successfully for $platform"
|
||
rm -rf "$temp_dir"
|
||
exit 1
|
||
fi
|
||
|
||
# Find the VSIX file in the downloaded artifact
|
||
local vsix_file=$(find "$temp_dir" -name "*.vsix" | head -1)
|
||
|
||
if [ -z "$vsix_file" ]; then
|
||
echo "❌ No VSIX file found in artifact"
|
||
rm -rf "$temp_dir"
|
||
exit 1
|
||
fi
|
||
|
||
# Extract version from the original VSIX filename (e.g., continue-1.1.66.vsix -> 1.1.66)
|
||
local original_filename=$(basename "$vsix_file")
|
||
local version=$(echo "$original_filename" | sed 's/continue-\(.*\)\.vsix/\1/')
|
||
|
||
if [ -z "$version" ]; then
|
||
echo "⚠️ Could not extract version from filename: $original_filename"
|
||
version="unknown"
|
||
fi
|
||
|
||
# Create new VSIX name with version and "main" suffix
|
||
local target_path="$ONEPER_DIR/continue-${version}-main.vsix"
|
||
|
||
# Check if file already exists and notify about overwrite
|
||
if [ -f "$target_path" ]; then
|
||
echo "⚠️ Overwriting existing VSIX: $target_path"
|
||
fi
|
||
|
||
echo "📦 Moving VSIX to: $target_path"
|
||
mv "$vsix_file" "$target_path"
|
||
|
||
# Clean up temp directory
|
||
rm -rf "$temp_dir"
|
||
|
||
# Install the extension
|
||
echo "🚀 Installing VS Code extension..."
|
||
if code --install-extension "$target_path" --force; then
|
||
echo "✅ Successfully installed Continue extension from main branch"
|
||
echo "📄 VSIX file saved to: $target_path"
|
||
echo ""
|
||
echo "💡 To use the new extension version, reload your VS Code window:"
|
||
echo " • Press Ctrl+Shift+P (Cmd+Shift+P on Mac)"
|
||
echo " • Type 'Developer: Reload Window' and press Enter"
|
||
echo " • Or restart VS Code entirely"
|
||
else
|
||
echo "❌ Failed to install extension"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
install_from_pr() {
|
||
local pr_number=$1
|
||
local platform=${2:-$(detect_os)} # Auto-detect OS if not specified
|
||
|
||
if [ -z "$pr_number" ]; then
|
||
echo "❌ PR number is required"
|
||
show_help
|
||
exit 1
|
||
fi
|
||
|
||
# Map platform names to artifact names for the new workflow
|
||
local artifact_name
|
||
case "$platform" in
|
||
"macos")
|
||
artifact_name="darwin-arm64-vsix"
|
||
;;
|
||
"linux")
|
||
artifact_name="linux-x64-vsix"
|
||
;;
|
||
*)
|
||
echo "❌ Unknown platform: $platform"
|
||
echo " Supported platforms: macos, linux"
|
||
exit 1
|
||
;;
|
||
esac
|
||
|
||
echo "🔍 Getting branch name for PR #$pr_number..."
|
||
|
||
# First get the branch name for the PR
|
||
local branch_name=$(gh pr view "$pr_number" --repo "$REPO" --json headRefName --jq '.headRefName')
|
||
|
||
if [ -z "$branch_name" ]; then
|
||
echo "❌ Could not find PR #$pr_number"
|
||
exit 1
|
||
fi
|
||
|
||
echo "🔍 Finding latest workflow run for branch: $branch_name..."
|
||
|
||
# Get the latest successful workflow run for the branch
|
||
local run_id=$(gh run list \
|
||
--repo "$REPO" \
|
||
--workflow="pr-build-upload-vsix.yaml" \
|
||
--branch="$branch_name" \
|
||
--json databaseId,status,conclusion \
|
||
--jq ".[] | select(.status == \"completed\" and .conclusion == \"success\") | .databaseId" \
|
||
| head -1)
|
||
|
||
if [ -z "$run_id" ]; then
|
||
echo "❌ No successful workflow run found for PR #$pr_number"
|
||
echo " Make sure the PR exists and has a successful CI run"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Found workflow run: $run_id"
|
||
echo "📥 Downloading $artifact_name artifact..."
|
||
|
||
# Create temporary directory for download
|
||
local temp_dir=$(mktemp -d)
|
||
|
||
# Download the artifact
|
||
if ! gh run download "$run_id" \
|
||
--repo "$REPO" \
|
||
--name "$artifact_name" \
|
||
--dir "$temp_dir"; then
|
||
echo "❌ Failed to download artifact $artifact_name"
|
||
echo " Make sure the workflow run completed successfully for $platform"
|
||
rm -rf "$temp_dir"
|
||
exit 1
|
||
fi
|
||
|
||
# Find the VSIX file in the downloaded artifact
|
||
local vsix_file=$(find "$temp_dir" -name "*.vsix" | head -1)
|
||
|
||
if [ -z "$vsix_file" ]; then
|
||
echo "❌ No VSIX file found in artifact"
|
||
rm -rf "$temp_dir"
|
||
exit 1
|
||
fi
|
||
|
||
# Extract full version from the original VSIX filename (e.g., continue-1.1.66-a1b2c3d.vsix -> 1.1.66-a1b2c3d)
|
||
local original_filename=$(basename "$vsix_file")
|
||
local full_version=$(echo "$original_filename" | sed 's/continue-\(.*\)\.vsix/\1/')
|
||
|
||
if [ -z "$full_version" ]; then
|
||
echo "⚠️ Could not extract version from filename: $original_filename"
|
||
full_version="unknown-pr${pr_number}"
|
||
fi
|
||
|
||
# Use the full version (which includes commit SHA) for the target filename
|
||
local target_path="$ONEPER_DIR/continue-${full_version}.vsix"
|
||
|
||
# Check if file already exists and notify about overwrite
|
||
if [ -f "$target_path" ]; then
|
||
echo "⚠️ Overwriting existing VSIX: $target_path"
|
||
fi
|
||
|
||
echo "📦 Moving VSIX to: $target_path"
|
||
mv "$vsix_file" "$target_path"
|
||
|
||
# Clean up temp directory
|
||
rm -rf "$temp_dir"
|
||
|
||
# Install the extension
|
||
echo "🚀 Installing VS Code extension..."
|
||
if code --install-extension "$target_path"; then
|
||
echo "✅ Successfully installed Continue extension from PR #$pr_number"
|
||
echo "📄 VSIX file saved to: $target_path"
|
||
echo "🔖 Extension version: ${full_version} (includes commit SHA)"
|
||
echo ""
|
||
echo "💡 To use the new extension version, reload your VS Code window:"
|
||
echo " • Press Ctrl+Shift+P (Cmd+Shift+P on Mac)"
|
||
echo " • Type 'Developer: Reload Window' and press Enter"
|
||
echo " • Or restart VS Code entirely"
|
||
else
|
||
echo "❌ Failed to install extension"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
install_latest() {
|
||
echo "🚀 Installing latest pre-release Continue extension..."
|
||
if code --install-extension --pre-release Continue.continue --force; then
|
||
echo "✅ Successfully installed latest pre-release Continue extension"
|
||
echo ""
|
||
echo "💡 To use the new extension version, reload your VS Code window:"
|
||
echo " • Press Ctrl+Shift+P (Cmd+Shift+P on Mac)"
|
||
echo " • Type 'Developer: Reload Window' and press Enter"
|
||
echo " • Or restart VS Code entirely"
|
||
else
|
||
echo "❌ Failed to install extension"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
get_latest_main_run_id() {
|
||
gh run list \
|
||
--repo "$REPO" \
|
||
--workflow="main-build.yaml" \
|
||
--branch="main" \
|
||
--json databaseId,status,conclusion \
|
||
--jq ".[] | select(.status == \"completed\" and .conclusion == \"success\") | .databaseId" \
|
||
| head -1
|
||
}
|
||
|
||
check_and_install_if_new() {
|
||
local current_run_id=$(get_latest_main_run_id)
|
||
|
||
if [ -z "$current_run_id" ]; then
|
||
echo "$(date): ❌ No successful main branch build found" >&2
|
||
return 1
|
||
fi
|
||
|
||
local last_run_file="$ONEPER_DIR/last_installed_run"
|
||
local last_run_id=""
|
||
|
||
if [ -f "$last_run_file" ]; then
|
||
last_run_id=$(cat "$last_run_file")
|
||
fi
|
||
|
||
if [ "$current_run_id" != "$last_run_id" ]; then
|
||
echo "$(date): 🔄 New build detected (https://github.com/$REPO/runs/$current_run_id), installing..."
|
||
|
||
# Install from main silently
|
||
if install_from_main > /dev/null 2>&1; then
|
||
echo "$current_run_id" > "$last_run_file"
|
||
echo "$(date): ✅ Successfully installed new build from https://github.com/$REPO/actions/runs/$current_run_id"
|
||
else
|
||
echo "$(date): ❌ Failed to install new build" >&2
|
||
return 1
|
||
fi
|
||
else
|
||
echo "$(date): 🔍 No new builds (current: https://github.com/$REPO/actions/runs/$current_run_id)"
|
||
fi
|
||
}
|
||
|
||
setup_cron() {
|
||
local interval=${1:-10} # Default to 10 minutes if not specified
|
||
echo "🔧 Setting up automatic updates every $interval minutes..."
|
||
|
||
# Get the absolute path to this script
|
||
local script_path=$(realpath "$0")
|
||
|
||
# Create a wrapper script that calls the check function
|
||
local cron_script="$ONEPER_DIR/oneper-cron.sh"
|
||
|
||
cat > "$cron_script" << EOF
|
||
#!/bin/bash
|
||
# Auto-generated oneper cron script
|
||
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:\$PATH"
|
||
cd "\$(dirname "$script_path")"
|
||
"$script_path" --cron-check
|
||
EOF
|
||
|
||
chmod +x "$cron_script"
|
||
|
||
# Create log file
|
||
local log_file="$ONEPER_DIR/oneper-cron.log"
|
||
touch "$log_file"
|
||
|
||
# Add to crontab
|
||
local cron_line="*/$interval * * * * $cron_script >> $log_file 2>&1"
|
||
|
||
# Check if cron job already exists
|
||
if crontab -l 2>/dev/null | grep -q "oneper-cron.sh"; then
|
||
echo "⚠️ Oneper cron job already exists. Removing old one..."
|
||
crontab -l 2>/dev/null | grep -v "oneper-cron.sh" | crontab -
|
||
fi
|
||
|
||
# Add new cron job
|
||
(crontab -l 2>/dev/null; echo "$cron_line") | crontab -
|
||
|
||
echo "✅ Automatic updates configured!"
|
||
echo "📊 Logs will be written to: $log_file"
|
||
echo "🔍 Check status with: tail -f $log_file"
|
||
echo ""
|
||
echo "💡 To remove automatic updates: oneper remove-cron"
|
||
}
|
||
|
||
remove_cron() {
|
||
echo "🗑️ Removing automatic updates..."
|
||
|
||
# Remove from crontab
|
||
if crontab -l 2>/dev/null | grep -q "oneper-cron.sh"; then
|
||
crontab -l 2>/dev/null | grep -v "oneper-cron.sh" | crontab -
|
||
echo "✅ Removed cron job"
|
||
else
|
||
echo "ℹ️ No oneper cron job found"
|
||
fi
|
||
|
||
# Clean up files
|
||
local cron_script="$ONEPER_DIR/oneper-cron.sh"
|
||
local log_file="$ONEPER_DIR/oneper-cron.log"
|
||
local last_run_file="$ONEPER_DIR/last_installed_run"
|
||
|
||
[ -f "$cron_script" ] && rm "$cron_script" && echo "🗑️ Removed cron script"
|
||
[ -f "$log_file" ] && rm "$log_file" && echo "🗑️ Removed log file"
|
||
[ -f "$last_run_file" ] && rm "$last_run_file" && echo "🗑️ Removed state file"
|
||
|
||
echo "✅ Automatic updates removed successfully"
|
||
}
|
||
|
||
clean_cache() {
|
||
if [ ! -d "$ONEPER_DIR" ]; then
|
||
echo "📁 Oneper cache directory doesn't exist: $ONEPER_DIR"
|
||
return 0
|
||
fi
|
||
|
||
local file_count=$(find "$ONEPER_DIR" -name "*.vsix" | wc -l | tr -d ' ')
|
||
|
||
if [ "$file_count" -eq 0 ]; then
|
||
echo "✨ Oneper cache is already empty"
|
||
return 0
|
||
fi
|
||
|
||
echo "🧹 Removing $file_count VSIX file(s) from oneper cache..."
|
||
rm -f "$ONEPER_DIR"/*.vsix
|
||
|
||
echo "✅ Oneper cache cleared successfully"
|
||
echo "📁 Cache directory: $ONEPER_DIR"
|
||
}
|
||
|
||
main() {
|
||
# Handle special internal commands first
|
||
case "$1" in
|
||
"--cron-check")
|
||
# Internal command used by cron job
|
||
ensure_oneper_dir
|
||
check_and_install_if_new
|
||
exit $?
|
||
;;
|
||
"-h"|"--help")
|
||
show_help
|
||
exit 0
|
||
;;
|
||
esac
|
||
|
||
# If no arguments, default to installing from main
|
||
if [ $# -eq 0 ]; then
|
||
check_dependencies
|
||
ensure_oneper_dir
|
||
install_from_main
|
||
exit 0
|
||
fi
|
||
|
||
check_dependencies
|
||
ensure_oneper_dir
|
||
|
||
case "$1" in
|
||
"install")
|
||
case "$2" in
|
||
"--pr")
|
||
# Parse PR number and optional platform
|
||
local pr_number="$3"
|
||
local platform=$(detect_os) # auto-detect OS
|
||
|
||
# Check if --platform is specified to override auto-detection
|
||
if [ "$4" = "--platform" ] && [ -n "$5" ]; then
|
||
platform="$5"
|
||
fi
|
||
|
||
install_from_pr "$pr_number" "$platform"
|
||
;;
|
||
"--latest")
|
||
install_latest
|
||
;;
|
||
"--platform")
|
||
# Handle: oneper install --platform <platform> (install from main with specific platform)
|
||
local platform="$3"
|
||
install_from_main "$platform"
|
||
;;
|
||
"")
|
||
# Handle: oneper install (install from main with auto-detected platform)
|
||
install_from_main
|
||
;;
|
||
*)
|
||
echo "❌ Unknown install option: $2"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
;;
|
||
"setup-cron")
|
||
# Check for --interval option
|
||
local interval=10
|
||
if [ "$2" = "--interval" ] && [ -n "$3" ]; then
|
||
interval="$3"
|
||
# Validate interval is a positive integer
|
||
if ! [[ "$interval" =~ ^[1-9][0-9]*$ ]]; then
|
||
echo "❌ Invalid interval: $interval. Must be a positive integer."
|
||
exit 1
|
||
fi
|
||
fi
|
||
setup_cron "$interval"
|
||
;;
|
||
"remove-cron")
|
||
remove_cron
|
||
;;
|
||
"clean")
|
||
clean_cache
|
||
;;
|
||
*)
|
||
echo "❌ Unknown command: $1"
|
||
show_help
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
main "$@" |