install.sh 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. REPO_OWNER="christianlempa"
  4. REPO_NAME="boilerplates"
  5. VERSION="${VERSION:-latest}"
  6. usage() {
  7. cat <<USAGE
  8. Usage: install.sh [OPTIONS]
  9. Install the boilerplates CLI from GitHub releases via pipx.
  10. Options:
  11. --version VER Version to install (default: "latest")
  12. -h, --help Show this message
  13. Examples:
  14. curl -qfsSL https://raw.githubusercontent.com/$REPO_OWNER/$REPO_NAME/main/scripts/install.sh | bash
  15. curl -qfsSL https://raw.githubusercontent.com/$REPO_OWNER/$REPO_NAME/main/scripts/install.sh | bash -s -- --version v1.0.0
  16. Uninstall:
  17. pipx uninstall boilerplates
  18. USAGE
  19. }
  20. log() { printf '[boilerplates] %s\n' "$*" >&2; }
  21. error() { printf '[boilerplates][error] %s\n' "$*" >&2; exit 1; }
  22. check_dependencies() {
  23. command -v tar >/dev/null 2>&1 || error "tar is required but not found"
  24. command -v mktemp >/dev/null 2>&1 || error "mktemp is required but not found"
  25. command -v python3 >/dev/null 2>&1 || error "Python 3 is required. Install: sudo apt install python3 python3-pip"
  26. python3 -m pip --version >/dev/null 2>&1 || error "pip is required. Install: sudo apt install python3-pip"
  27. if command -v pipx >/dev/null 2>&1; then
  28. PIPX_CMD="pipx"
  29. elif [[ -x "$(python3 -m site --user-base 2>/dev/null)/bin/pipx" ]]; then
  30. PIPX_CMD="$(python3 -m site --user-base)/bin/pipx"
  31. else
  32. error "pipx is required. Install: pip install --user pipx"
  33. fi
  34. log "All dependencies available"
  35. }
  36. parse_args() {
  37. while [[ $# -gt 0 ]]; do
  38. case "$1" in
  39. --version)
  40. [[ $# -lt 2 ]] && error "--version requires an argument"
  41. [[ "$2" =~ ^- ]] && error "--version requires a version string, not an option"
  42. VERSION="$2"
  43. shift 2
  44. ;;
  45. -h|--help) usage; exit 0 ;;
  46. *) error "Unknown option: $1" ;;
  47. esac
  48. done
  49. }
  50. get_latest_release() {
  51. local api_url="https://api.github.com/repos/$REPO_OWNER/$REPO_NAME/releases/latest"
  52. local result
  53. if command -v curl >/dev/null 2>&1; then
  54. result=$(curl -qfsSL --max-time 10 "$api_url" 2>/dev/null | sed -En 's/.*"tag_name": "([^"]+)".*/\1/p')
  55. elif command -v wget >/dev/null 2>&1; then
  56. result=$(wget --timeout=10 -qO- "$api_url" 2>/dev/null | sed -En 's/.*"tag_name": "([^"]+)".*/\1/p')
  57. else
  58. error "Neither curl nor wget found"
  59. fi
  60. [[ -z "$result" ]] && error "Failed to fetch release information from GitHub"
  61. echo "$result"
  62. }
  63. download_and_extract() {
  64. local version="$1"
  65. # Resolve "latest" to actual version
  66. if [[ "$version" == "latest" ]]; then
  67. log "Fetching latest release..."
  68. version=$(get_latest_release)
  69. log "Latest version: $version"
  70. fi
  71. # Ensure 'v' prefix for URL
  72. local version_tag="$version"
  73. [[ "$version_tag" =~ ^v ]] || version_tag="v$version_tag"
  74. # Strip 'v' prefix for package name
  75. local version_number="${version_tag#v}"
  76. # Download from release assets (sdist)
  77. local url="https://github.com/$REPO_OWNER/$REPO_NAME/releases/download/$version_tag/$REPO_NAME-$version_number.tar.gz"
  78. TEMP_DIR=$(mktemp -d)
  79. local archive="$TEMP_DIR/boilerplates.tar.gz"
  80. log "Downloading $version_tag from release assets..."
  81. if command -v curl >/dev/null 2>&1; then
  82. curl -qfsSL --max-time 30 -o "$archive" "$url" || error "Download failed. URL: $url"
  83. elif command -v wget >/dev/null 2>&1; then
  84. wget --timeout=30 -qO "$archive" "$url" || error "Download failed. URL: $url"
  85. fi
  86. log "Extracting package..."
  87. # Extract the tarball
  88. tar -xzf "$archive" -C "$TEMP_DIR" || error "Extraction failed"
  89. # Find the extracted directory (should be boilerplates-X.Y.Z)
  90. local source_dir=$(find "$TEMP_DIR" -maxdepth 1 -type d -name "$REPO_NAME-*" | head -n1)
  91. [[ -z "$source_dir" ]] && error "Failed to locate extracted files"
  92. # Verify essential files exist
  93. [[ ! -f "$source_dir/setup.py" ]] && [[ ! -f "$source_dir/pyproject.toml" ]] && \
  94. error "Invalid package: missing setup.py or pyproject.toml"
  95. # Return the path to the extracted directory
  96. echo "$source_dir"
  97. }
  98. install_cli() {
  99. local package_path="$1"
  100. local version="$2"
  101. log "Installing CLI via pipx..."
  102. "$PIPX_CMD" ensurepath 2>&1 | grep -v "^$" || true
  103. # Install from tarball
  104. if ! "$PIPX_CMD" install --force "$package_path" >/dev/null 2>&1; then
  105. error "pipx installation failed. Try: pipx uninstall boilerplates && pipx install boilerplates"
  106. fi
  107. log "CLI installed successfully"
  108. # Verify installation
  109. if command -v boilerplates >/dev/null 2>&1; then
  110. log "Command 'boilerplates' is now available"
  111. else
  112. log "Warning: 'boilerplates' command not found in PATH. You may need to restart your shell or run: pipx ensurepath"
  113. fi
  114. }
  115. main() {
  116. parse_args "$@"
  117. # Ensure cleanup on exit
  118. trap '[[ -d "${TEMP_DIR:-}" ]] && rm -rf "$TEMP_DIR"' EXIT
  119. log "Checking dependencies..."
  120. check_dependencies
  121. local package_path=$(download_and_extract "$VERSION")
  122. install_cli "$package_path" "$VERSION"
  123. # Get installed version
  124. local installed_version=$(boilerplates --version 2>/dev/null | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown")
  125. cat <<EOF
  126. \uf05d Installation complete!
  127. Version: $installed_version
  128. Installed via: pipx
  129. Usage:
  130. boilerplates --help
  131. Update:
  132. curl -qfsSL https://raw.githubusercontent.com/$REPO_OWNER/$REPO_NAME/main/scripts/install.sh | bash
  133. Uninstall:
  134. pipx uninstall boilerplates
  135. EOF
  136. }
  137. main "$@"