Created
September 19, 2025 13:31
-
-
Save adamwdennis/74b7aeeab07c5ce5df5a01d9872e125d to your computer and use it in GitHub Desktop.
List and cleanup deployments on Vercel
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| ############################################################################### | |
| # Title: vercel-api-deployments.sh - Deployment cleanup functions | |
| # Description: This script is used to list and cleanup deployments on Vercel. | |
| # Author: Adam Dennis | |
| # Date: 2025-09-19 | |
| # Version: 1.0.0 | |
| ############################################################################### | |
| # Usage: ./vercel-api-deployments.sh | |
| # Simply set your Vercel API credentials (below) and then run the script to | |
| # see the following menu: | |
| # | |
| # === Deployment Cleanup Menu === | |
| # 1. List all deployments (basic) | |
| # 1a. List all deployments (detailed) | |
| # 1b. List all deployments (comprehensive) | |
| # 2. List deployments by branch | |
| # 3. Remove failed deployments | |
| # 4. Remove old deployments (30 days) | |
| # 5. Remove old deployments (custom days) | |
| # 6. Remove deployments by branch name | |
| # q. Quit | |
| # | |
| # Choose an option: _ | |
| ############################################################################### | |
| ############################################################################### | |
| # Set your Vercel API credentials here | |
| ############################################################################### | |
| VERCEL_TOKEN="YOUR_API_TOKEN" | |
| VERCEL_ORG_ID="YOUR_ORG_ID" | |
| VERCEL_PROJECT_ID="YOUR_PROJECT_ID" | |
| # Function to test Vercel API access | |
| test_vercel_api_access() { | |
| echo "Verifying Vercel API access given your credentials..." | |
| # Test token and org access | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v2/teams/$VERCEL_ORG_ID" | jq '.name // .error' | |
| # Test project access | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v1/projects/$VERCEL_PROJECT_ID?teamId=$VERCEL_ORG_ID" | jq '.name // .error' | |
| # Test deployments endpoint | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID&limit=1" | jq '.deployments[0].uid // .error' | |
| echo "" | |
| echo "=== CLEANUP FUNCTIONS ===" | |
| } | |
| # Function to list all deployments with details including branch name | |
| list_deployments() { | |
| echo "Fetching all deployments..." | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \ | |
| | jq -r '.deployments[] | "\(.uid) | \(.state) | \(.meta.githubCommitRef // "N/A") | \(.url) | \(.createdAt)"' | |
| } | |
| # Alternative: More detailed listing with better formatting | |
| list_deployments_detailed() { | |
| echo "Fetching all deployments..." | |
| echo "UID | State | Branch | URL | Created" | |
| echo "----------------------------------------------------" | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \ | |
| | jq -r '.deployments[] | | |
| "\(.uid[0:8])... | \(.state) | \(.meta.githubCommitRef // "main/prod") | \(.url) | \(.createdAt | strftime("%Y-%m-%d %H:%M"))"' | |
| } | |
| # Function to show deployment metadata (useful for debugging) | |
| show_deployment_meta() { | |
| local deployment_id="$1" | |
| if [ -z "$deployment_id" ]; then | |
| echo "Usage: show_deployment_meta <deployment_id>" | |
| return 1 | |
| fi | |
| echo "Metadata for deployment: $deployment_id" | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" \ | |
| | jq '.meta' | |
| } | |
| # Helper function to display deployment info in comprehensive format | |
| show_deployment_info() { | |
| local deployments_json="$1" | |
| local deployment_ids="$2" | |
| printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" "UID" "State" "Branch" "Commit" "URL" "Created" | |
| echo "------------------------------------------------------------------------------------------------------------" | |
| echo "$deployment_ids" | while read deployment_id; do | |
| if [ ! -z "$deployment_id" ]; then | |
| echo "$deployments_json" | jq -r --arg id "$deployment_id" \ | |
| '.deployments[] | select(.uid == $id) | | |
| "\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url[0:30]) \(.createdAt | strftime("%m-%d %H:%M"))"' \ | |
| | while read line; do | |
| printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" $line | |
| done | |
| fi | |
| done | |
| } | |
| # Function to get all deployments with pagination and filtering | |
| get_all_deployments() { | |
| local state_filter="$1" # Optional: "ERROR", "CANCELED", etc. | |
| local all_deployments="[]" | |
| local until_param="" | |
| while true; do | |
| # Build API URL with optional state filter and pagination | |
| local api_url="https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID&limit=100" | |
| if [ ! -z "$state_filter" ]; then | |
| api_url="${api_url}&state=${state_filter}" | |
| fi | |
| if [ ! -z "$until_param" ]; then | |
| api_url="${api_url}&until=${until_param}" | |
| fi | |
| echo "Fetching deployments... (until: ${until_param:-'latest'})" >&2 | |
| local response=$(curl -s -H "Authorization: Bearer $VERCEL_TOKEN" "$api_url") | |
| local deployments=$(echo "$response" | jq '.deployments // []') | |
| local pagination=$(echo "$response" | jq '.pagination // {}') | |
| # Merge deployments | |
| all_deployments=$(echo "$all_deployments $deployments" | jq -s 'add') | |
| # Check if there are more pages | |
| local next_until=$(echo "$pagination" | jq -r '.next // empty') | |
| if [ -z "$next_until" ] || [ "$next_until" = "null" ]; then | |
| break | |
| fi | |
| until_param="$next_until" | |
| done | |
| echo "$all_deployments" | |
| } | |
| # Updated function to remove failed deployments using API filtering | |
| remove_failed_deployments_optimized() { | |
| echo "Finding all failed deployments..." | |
| # Get failed deployments directly from API | |
| local error_deployments=$(get_all_deployments "ERROR") | |
| local canceled_deployments=$(get_all_deployments "CANCELED") | |
| # Combine both arrays | |
| local all_failed=$(echo "$error_deployments $canceled_deployments" | jq -s 'add | unique_by(.uid)') | |
| local failed_count=$(echo "$all_failed" | jq 'length') | |
| if [ "$failed_count" -eq 0 ]; then | |
| echo "No failed deployments found." | |
| return | |
| fi | |
| echo "Found $failed_count failed deployments:" | |
| # Show comprehensive info | |
| printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" "UID" "State" "Branch" "Commit" "URL" "Created" | |
| echo "------------------------------------------------------------------------------------------------------------" | |
| echo "$all_failed" | jq -r '.[] | | |
| "\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url[0:30]) \(.createdAt | strftime("%m-%d %H:%M"))"' \ | |
| | while read line; do | |
| printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" $line | |
| done | |
| echo "" | |
| read -p "Delete these $failed_count failed deployments? (y/N): " confirm | |
| if [[ $confirm == [yY] ]]; then | |
| echo "$all_failed" | jq -r '.[].uid' | while read deployment_id; do | |
| echo "Deleting $deployment_id..." | |
| curl -s -X DELETE \ | |
| "https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" \ | |
| -H "Authorization: Bearer $VERCEL_TOKEN" | |
| echo "✓ Deleted $deployment_id" | |
| done | |
| echo "✓ All failed deployments deleted." | |
| else | |
| echo "Deletion cancelled." | |
| fi | |
| } | |
| # Function to get deployments by state with comprehensive filtering | |
| get_deployments_by_criteria() { | |
| local criteria="$1" # "failed", "old", "branch:name", "state:ERROR" | |
| case "$criteria" in | |
| "failed") | |
| # Get both ERROR and CANCELED states | |
| local error_deps=$(get_all_deployments "ERROR") | |
| local canceled_deps=$(get_all_deployments "CANCELED") | |
| echo "$error_deps $canceled_deps" | jq -s 'add | unique_by(.uid)' | |
| ;; | |
| "old") | |
| local days_old=${2:-30} | |
| local cutoff_date=$(date -d "$days_old days ago" -u +%s)000 | |
| get_all_deployments | jq --arg cutoff "$cutoff_date" \ | |
| '[.[] | select((.createdAt | tonumber) < ($cutoff | tonumber)) | | |
| select(.state != "READY" or .target != "production")]' | |
| ;; | |
| branch:*) | |
| local branch_name="${criteria#branch:}" | |
| get_all_deployments | jq --arg branch "$branch_name" \ | |
| '[.[] | select(.meta.githubCommitRef == $branch)]' | |
| ;; | |
| state:*) | |
| local state="${criteria#state:}" | |
| get_all_deployments "$state" | |
| ;; | |
| *) | |
| echo "Unknown criteria: $criteria" >&2 | |
| return 1 | |
| ;; | |
| esac | |
| } | |
| # Enhanced function with better filtering options | |
| remove_deployments_enhanced() { | |
| echo "Enhanced deployment cleanup with API filtering..." | |
| echo "1. Failed deployments (ERROR + CANCELED) - API filtered" | |
| echo "2. Deployments by specific state (ERROR, CANCELED, BUILDING, etc.)" | |
| echo "3. Old deployments (custom days)" | |
| echo "4. Deployments by branch name" | |
| echo "5. Custom criteria" | |
| echo "" | |
| read -p "Choose option (1-5): " option | |
| case $option in | |
| 1) | |
| remove_failed_deployments_optimized | |
| ;; | |
| 2) | |
| read -p "Enter state (ERROR, CANCELED, BUILDING, READY, etc.): " state | |
| local deployments=$(get_deployments_by_criteria "state:$state") | |
| show_and_delete_deployments "$deployments" "deployments with state '$state'" | |
| ;; | |
| 3) | |
| read -p "Enter number of days old: " days | |
| local deployments=$(get_deployments_by_criteria "old" "$days") | |
| show_and_delete_deployments "$deployments" "deployments older than $days days" | |
| ;; | |
| 4) | |
| read -p "Enter branch name: " branch | |
| local deployments=$(get_deployments_by_criteria "branch:$branch") | |
| show_and_delete_deployments "$deployments" "deployments for branch '$branch'" | |
| ;; | |
| 5) | |
| echo "Available criteria: failed, old, branch:name, state:STATE" | |
| read -p "Enter criteria: " criteria | |
| local deployments=$(get_deployments_by_criteria "$criteria") | |
| show_and_delete_deployments "$deployments" "deployments matching '$criteria'" | |
| ;; | |
| *) | |
| echo "Invalid option" | |
| ;; | |
| esac | |
| } | |
| # Helper function to show and delete deployments | |
| show_and_delete_deployments() { | |
| local deployments_json="$1" | |
| local description="$2" | |
| local count=$(echo "$deployments_json" | jq 'length') | |
| if [ "$count" -eq 0 ]; then | |
| echo "No $description found." | |
| return | |
| fi | |
| echo "Found $count $description:" | |
| printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" "UID" "State" "Branch" "Commit" "URL" "Created" | |
| echo "------------------------------------------------------------------------------------------------------------" | |
| echo "$deployments_json" | jq -r '.[] | | |
| "\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url[0:30]) \(.createdAt | strftime("%m-%d %H:%M"))"' \ | |
| | while read line; do | |
| printf "%-12s %-10s %-20s %-8s %-30s %-15s\n" $line | |
| done | |
| echo "" | |
| read -p "Delete these $count $description? (y/N): " confirm | |
| if [[ $confirm == [yY] ]]; then | |
| echo "$deployments_json" | jq -r '.[].uid' | while read deployment_id; do | |
| echo "Deleting $deployment_id..." | |
| curl -s -X DELETE \ | |
| "https://api.vercel.com/v13/deployments/$deployment_id?teamId=$VERCEL_ORG_ID" \ | |
| -H "Authorization: Bearer $VERCEL_TOKEN" | |
| echo "✓ Deleted $deployment_id" | |
| done | |
| echo "✓ All $description deleted." | |
| else | |
| echo "Deletion cancelled." | |
| fi | |
| } | |
| # Function to list deployments with comprehensive GitHub info | |
| list_deployments_comprehensive() { | |
| echo "Fetching all deployments with GitHub details..." | |
| printf "%-12s %-10s %-20s %-8s %-50s\n" "UID" "State" "Branch" "Commit" "URL" | |
| echo "--------------------------------------------------------------------------------------------------------" | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \ | |
| | jq -r '.deployments[] | | |
| "\(.uid[0:12]) \(.state) \(.meta.githubCommitRef // "main")[0:20] \(.meta.githubCommitSha[0:8] // "N/A") \(.url)"' \ | |
| | while read line; do | |
| printf "%-12s %-10s %-20s %-8s %-50s\n" $line | |
| done | |
| } | |
| # Function to list deployments for a specific branch | |
| list_deployments_by_branch() { | |
| local branch_name="$1" | |
| if [ -z "$branch_name" ]; then | |
| echo "Usage: list_deployments_by_branch <branch_name>" | |
| return 1 | |
| fi | |
| echo "Deployments for branch: $branch_name" | |
| curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ | |
| "https://api.vercel.com/v6/deployments?projectId=$VERCEL_PROJECT_ID&teamId=$VERCEL_ORG_ID" \ | |
| | jq -r --arg branch "$branch_name" \ | |
| '.deployments[] | select(.meta.githubCommitRef == $branch) | | |
| "\(.uid) | \(.state) | \(.url) | \(.createdAt)"' | |
| } | |
| # Interactive menu | |
| show_menu() { | |
| echo "" | |
| echo "=== Deployment Cleanup Menu ===" | |
| echo "1. List all deployments (basic)" | |
| echo "1a. List all deployments (detailed)" | |
| echo "1b. List all deployments (comprehensive)" | |
| echo "2. List deployments by branch" | |
| echo "3. Remove failed deployments" | |
| echo "4. Remove old deployments (30 days)" | |
| echo "5. Remove old deployments (custom days)" | |
| echo "6. Remove deployments by branch name" | |
| echo "q. Quit" | |
| echo "" | |
| } | |
| # Main interactive loop | |
| test_vercel_api_access | |
| while true; do | |
| show_menu | |
| read -p "Choose an option: " choice | |
| case $choice in | |
| 1) list_deployments ;; | |
| 1a) list_deployments_detailed ;; | |
| 1b) list_deployments_comprehensive ;; | |
| 2) | |
| read -p "Enter branch name: " branch | |
| list_deployments_by_branch "$branch" | |
| ;; | |
| 3) remove_failed_deployments_optimized ;; | |
| 4) remove_old_deployments 30 ;; | |
| 5) | |
| read -p "Enter number of days: " days | |
| remove_old_deployments "$days" | |
| ;; | |
| 6) | |
| read -p "Enter branch name: " branch | |
| remove_branch_deployments "$branch" | |
| ;; | |
| q|Q) echo "Goodbye!"; break ;; | |
| *) echo "Invalid option" ;; | |
| esac | |
| done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment