diff --git a/README.md b/README.md index c6345487..ac58e1b1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ -# Shell -Schell Scripts +# 🚀 GitHub API Pro CLI + +![Bash](https://img.shields.io/badge/Shell-Bash-4EAA25?logo=gnu-bash&logoColor=white) +![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) +![Status](https://img.shields.io/badge/Status-Production--Ready-brightgreen) + +A high-performance, secure, and robust Bash CLI tool for interacting with the GitHub REST API. This tool handles complex tasks like pagination, rate limiting, and JSON integrity automatically. + +## ✨ Key Features + +- 🔐 **Secure Auth:** Supports `GITHUB_TOKEN` via env, `.env` files, or secure hidden prompt. No more tokens in shell history! +- 📄 **Smart Pagination:** Automatically fetches all pages and merges them into a single **valid** JSON array using `jq`. +- 📊 **Rate Limit Awareness:** Real-time monitoring of GitHub API limits to prevent unexpected blocks. +- ⚡ **Full HTTP Support:** Easily perform `GET`, `POST`, and `DELETE` requests with custom headers and payloads. +- 🎨 **Enhanced UX:** Color-coded logging, progress spinners, and a detailed `--help` menu. +- 🛡️ **Safe & Clean:** Built with `set -euo pipefail` and automated cleanup of temporary files via `trap`. + +## 📦 Prerequisites + +Ensure you have the following installed: +- `curl` +- `jq` (JSON processor) + +## 🚀 Installation & Usage + +1. **Clone and Permissions:** + ```bash + chmod +x github-api-helper.sh + ``` + +2. **Examples:** + + **Fetch all issues from a repo (handles pagination):** + ```bash + ./github-api-helper.sh /repos/owner/repo/issues + ``` + + **Post a comment to an issue:** + ```bash + ./github-api-helper.sh -X POST -d '{"body": "Fixed in v1.2"}' /repos/owner/repo/issues/1/comments + ``` + + **Save output to a file:** + ```bash + ./github-api-helper.sh -o results.json /orgs/my-org/members + ``` + +## 🛠 Options + +| Flag | Description | +|------|-------------| +| `-t, --token` | GitHub personal access token | +| `-X, --method` | HTTP method (GET, POST, DELETE) | +| `-d, --data` | JSON payload for POST/PUT | +| `-o, --output`| Save results to a specific file | +| `-v, --verbose`| Enable debug logging | + +## 📜 License +This project is licensed under the MIT License. diff --git a/github-api-helper.sh b/github-api-helper.sh new file mode 100644 index 00000000..d608ac24 --- /dev/null +++ b/github-api-helper.sh @@ -0,0 +1,322 @@ +#!/usr/bin/env bash +set -euo pipefail + +############################################# +# GitHub CLI Helper +# Author: Abhishek (Refactored) +# Version: v2 (Production Ready) +############################################# + +SCRIPT_NAME="$(basename "$0")" +TMP_DIR="$(mktemp -d)" +BODY_FILE="$TMP_DIR/body.json" +HEADER_FILE="$TMP_DIR/headers.txt" + +# -------------------------------------------- +# Cleanup +# -------------------------------------------- +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +# -------------------------------------------- +# Colors +# -------------------------------------------- +RED="\033[31m" +GREEN="\033[32m" +YELLOW="\033[33m" +BLUE="\033[34m" +RESET="\033[0m" + +# -------------------------------------------- +# Defaults +# -------------------------------------------- +METHOD="GET" +OUTPUT_FILE="" +VERBOSE=false +SILENT=false +CUSTOM_HEADERS=() +DATA="" +TOKEN="" +ENDPOINT="" + +# -------------------------------------------- +# Logging +# -------------------------------------------- +log_info() { + $SILENT && return + echo -e "${BLUE}[INFO]${RESET} $*" +} + +log_warn() { + $SILENT && return + echo -e "${YELLOW}[WARN]${RESET} $*" >&2 +} + +log_error() { + echo -e "${RED}[ERROR]${RESET} $*" >&2 +} + +log_verbose() { + if $VERBOSE && ! $SILENT; then + echo -e "${GREEN}[DEBUG]${RESET} $*" + fi +} + +# -------------------------------------------- +# Dependency Check +# -------------------------------------------- +check_dependencies() { + for cmd in curl jq; do + if ! command -v "$cmd" >/dev/null 2>&1; then + log_error "Required dependency '$cmd' is not installed." + exit 1 + fi + done +} + +# -------------------------------------------- +# Usage +# -------------------------------------------- +usage() { +cat < + +Example: + $SCRIPT_NAME /repos/octocat/hello-world/issues + $SCRIPT_NAME -X POST -d '{"title":"Bug"}' /repos/org/repo/issues + +Options: + -t, --token TOKEN GitHub token + -X, --method METHOD HTTP method (GET, POST, DELETE) + -H, --header HEADER Custom header + -d, --data DATA JSON payload + -o, --output FILE Save output to file + -v, --verbose Verbose logging + -s, --silent Silent mode + -h, --help Show this help + +Environment: + GITHUB_TOKEN Preferred authentication method + +EOF +} + +# -------------------------------------------- +# Token Loader +# -------------------------------------------- +load_token() { + + if [[ -n "${TOKEN}" ]]; then + return + fi + + if [[ -n "${GITHUB_TOKEN:-}" ]]; then + TOKEN="$GITHUB_TOKEN" + return + fi + + if [[ -f ".env" ]]; then + TOKEN=$(grep GITHUB_TOKEN .env | cut -d '=' -f2) + fi + + if [[ -z "$TOKEN" ]]; then + read -rsp "GitHub Token: " TOKEN + echo + fi + + if [[ -z "$TOKEN" ]]; then + log_error "GitHub token is required." + exit 1 + fi +} + +# -------------------------------------------- +# Spinner +# -------------------------------------------- +spinner() { + local pid=$1 + local delay=0.1 + local spin='-\|/' + + while ps -p "$pid" > /dev/null 2>&1; do + for i in $(seq 0 3); do + printf "\rFetching... %s" "${spin:$i:1}" + sleep $delay + done + done + printf "\r" +} + +# -------------------------------------------- +# Rate Limit Check +# -------------------------------------------- +check_rate_limit() { + + local remaining + local limit + + remaining=$(grep -i '^x-ratelimit-remaining:' "$HEADER_FILE" | awk '{print $2}' | tr -d '\r') + limit=$(grep -i '^x-ratelimit-limit:' "$HEADER_FILE" | awk '{print $2}' | tr -d '\r') + + if [[ -n "$remaining" && -n "$limit" ]]; then + if (( remaining < 50 )); then + log_warn "GitHub API rate limit low ($remaining/$limit remaining)" + else + log_verbose "Rate limit remaining: $remaining/$limit" + fi + fi +} + +# -------------------------------------------- +# Extract Next Page URL +# -------------------------------------------- +get_next_link() { + grep -i '^link:' "$HEADER_FILE" \ + | sed -E 's/Link: //I' \ + | tr ',' '\n' \ + | grep 'rel="next"' \ + | sed -E 's/.*<([^>]+)>.*/\1/' +} + +# -------------------------------------------- +# API Request +# -------------------------------------------- +api_request() { + + local url="$1" + + log_verbose "Request: $METHOD $url" + + curl -sS \ + -X "$METHOD" \ + -D "$HEADER_FILE" \ + -o "$BODY_FILE" \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $TOKEN" \ + "${CUSTOM_HEADERS[@]}" \ + ${DATA:+-d "$DATA"} \ + "$url" +} + +# -------------------------------------------- +# Fetch All Pages +# -------------------------------------------- +fetch_all_pages() { + + local url="https://api.github.com${ENDPOINT}" + local pages=() + + while [[ -n "$url" ]]; do + + api_request "$url" & + pid=$! + + if ! $SILENT; then + spinner $pid + fi + + wait $pid + + check_rate_limit + + pages+=("$BODY_FILE") + + url=$(get_next_link) + + if [[ -n "$url" ]]; then + BODY_FILE="$TMP_DIR/body_$RANDOM.json" + fi + done + + jq -s 'flatten' "${pages[@]}" +} + +# -------------------------------------------- +# Argument Parsing +# -------------------------------------------- +parse_args() { + + while [[ $# -gt 0 ]]; do + case "$1" in + -X|--method) + METHOD="$2" + shift 2 + ;; + -H|--header) + CUSTOM_HEADERS+=(-H "$2") + shift 2 + ;; + -d|--data) + DATA="$2" + shift 2 + ;; + -o|--output) + OUTPUT_FILE="$2" + shift 2 + ;; + -t|--token) + TOKEN="$2" + shift 2 + ;; + -v|--verbose) + VERBOSE=true + shift + ;; + -s|--silent) + SILENT=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + ENDPOINT="$1" + shift + ;; + esac + done + + if [[ -z "$ENDPOINT" ]]; then + usage + exit 1 + fi +} + +# -------------------------------------------- +# Output Handler +# -------------------------------------------- +handle_output() { + + local result="$1" + + if [[ -n "$OUTPUT_FILE" ]]; then + echo "$result" | jq '.' > "$OUTPUT_FILE" + log_info "Saved output to $OUTPUT_FILE" + else + echo "$result" | jq '.' + fi +} + +# -------------------------------------------- +# Main +# -------------------------------------------- +main() { + + parse_args "$@" + check_dependencies + load_token + + log_info "Calling GitHub API: $ENDPOINT" + + result=$(fetch_all_pages) + + handle_output "$result" +} + +main "$@" diff --git a/github-api-integration-module.sh b/github-api-integration-module.sh deleted file mode 100644 index d94d2951..00000000 --- a/github-api-integration-module.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash -################################ -# Author: Abhishek -# Version: v1 -# -# -# -# This script will help users to communicate and retrieve information from GitHub -# Usage: -# Please provide your github token and rest api to the script as input -# -# -################################ - -if [ ${#@} -lt 2 ]; then - echo "usage: $0 [your github token] [REST expression]" - exit 1; -fi - -GITHUB_TOKEN=$1 -GITHUB_API_REST=$2 - -GITHUB_API_HEADER_ACCEPT="Accept: application/vnd.github.v3+json" - -temp=`basename $0` -TMPFILE=`mktemp /tmp/${temp}.XXXXXX` || exit 1 - - -function rest_call { - curl -s $1 -H "${GITHUB_API_HEADER_ACCEPT}" -H "Authorization: token $GITHUB_TOKEN" >> $TMPFILE -} - -# single page result-s (no pagination), have no Link: section, the grep result is empty -last_page=`curl -s -I "https://api.github.com${GITHUB_API_REST}" -H "${GITHUB_API_HEADER_ACCEPT}" -H "Authorization: token $GITHUB_TOKEN" | grep '^Link:' | sed -e 's/^Link:.*page=//g' -e 's/>.*$//g'` - -# does this result use pagination? -if [ -z "$last_page" ]; then - # no - this result has only one page - rest_call "https://api.github.com${GITHUB_API_REST}" -else - - # yes - this result is on multiple pages - for p in `seq 1 $last_page`; do - rest_call "https://api.github.com${GITHUB_API_REST}?page=$p" - done -fi - -cat $TMPFILE