diff --git a/.github/workflows/validate-shell.yml b/.github/workflows/validate-shell.yml new file mode 100644 index 000000000..480f14d60 --- /dev/null +++ b/.github/workflows/validate-shell.yml @@ -0,0 +1,32 @@ +name: Validate Shell Scripts + +on: + pull_request: + paths: + - "**/*.sh" + - ".github/workflows/validate-shell.yml" + +jobs: + bats: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: bats-core/bats-action@42fcc8700f773c075a16a90eb11674c0318ad507 # 3.0.1 + - run: bats integration/shell/ + run_install_sh: + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + runner: [ubuntu-latest, macos-latest] + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - run: ./install.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: speakeasy --version + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ca9cdaa8a..0e6d562a4 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,6 +2,7 @@ name: Validate on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] jobs: build: diff --git a/install.sh b/install.sh index 792792ea1..0fc4db5c5 100755 --- a/install.sh +++ b/install.sh @@ -15,89 +15,152 @@ BINARY_NAME=${BINARY_NAME:-"speakeasy"} REPO_NAME="speakeasy-api/speakeasy" ISSUE_URL="https://github.com/speakeasy-api/speakeasy/issues/new" +# curl_with_retry - curl wrapper that retries on 5XX errors and shows full errors +# Usage: curl_with_retry output_file [curl_args...] +curl_with_retry() { + _curl_output_file="$1" + shift + + _curl_delay=1 + _curl_exit_code=0 + _curl_http_code="" + _curl_max_retries=5 + _curl_retry=0 + + while [ $_curl_retry -lt $_curl_max_retries ]; do + # Use -w to get HTTP status code, -o to capture body, -S to show errors + _curl_http_code=$(curl -S -w "%{http_code}" -o "$_curl_output_file" "$@" 2>&1) + _curl_exit_code=$? + + # If curl itself failed (not HTTP error), show the error and retry + if [ $_curl_exit_code -ne 0 ]; then + # http_code contains the error message when curl fails + fmt_error "curl failed: $_curl_http_code" + if [ $_curl_retry -lt $((_curl_max_retries - 1)) ]; then + echo "Retrying in ${_curl_delay}s... (attempt $((_curl_retry + 2))/${_curl_max_retries})" >&2 + sleep $_curl_delay + _curl_delay=$((_curl_delay * 2)) + _curl_retry=$((_curl_retry + 1)) + continue + else + return $_curl_exit_code + fi + fi + + # Check for 5XX status codes + case "$_curl_http_code" in + 5*) + fmt_error "Server error (HTTP $_curl_http_code)" + if [ $_curl_retry -lt $((_curl_max_retries - 1)) ]; then + echo "Retrying in ${_curl_delay}s... (attempt $((_curl_retry + 2))/${_curl_max_retries})" >&2 + sleep $_curl_delay + _curl_delay=$((_curl_delay * 2)) + _curl_retry=$((_curl_retry + 1)) + continue + else + return 1 + fi + ;; + 4*) + # Client error - don't retry, but show the error + fmt_error "HTTP error $_curl_http_code" + cat "$_curl_output_file" >&2 + return 1 + ;; + *) + # Success (2XX, 3XX) + return 0 + ;; + esac + done + + return 1 +} + # github_api_curl - make authenticated GitHub API calls +# Usage: github_api_curl output_file [curl_args...] github_api_curl() { + _github_api_curl_output_file="$1" + shift + if [ -n "$GITHUB_TOKEN" ]; then - curl -H "Authorization: Bearer $GITHUB_TOKEN" "$@" + curl_with_retry "$_github_api_curl_output_file" -H "Authorization: Bearer $GITHUB_TOKEN" "$@" else - curl "$@" + curl_with_retry "$_github_api_curl_output_file" "$@" fi } # get_latest_release "speakeasy-api/speakeasy" get_latest_release() { - local retry=0 - local max_retries=5 - local release_info - local delay=1 - - while [ $retry -lt $max_retries ]; do - release_info=$(github_api_curl --retry 5 --silent "https://api.github.com/repos/$1/releases/latest") - tag_name=$(echo "$release_info" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - - if echo "$tag_name" | grep -q '^v'; then - echo "$tag_name" - return - else - sleep $delay - delay=$((delay * 2)) # Double the delay for each retry - retry=$((retry + 1)) - fi - done - echo "Error: Unable to retrieve a valid release tag after $max_retries attempts." >&2 + _get_latest_release_repo=$1 + _get_latest_release_output="$tmp_dir/latest_release.json" + + if ! github_api_curl "$_get_latest_release_output" --silent "https://api.github.com/repos/${_get_latest_release_repo}/releases/latest"; then + fmt_error "Could not fetch latest release from GitHub API" + exit 1 + fi + + _get_latest_release_tag_name=$(grep '"tag_name":' "$_get_latest_release_output" | sed -E 's/.*"([^"]+)".*/\1/') + + if echo "$_get_latest_release_tag_name" | grep -q '^v'; then + echo "$_get_latest_release_tag_name" + return + fi + + fmt_error "Could not fetch latest release tag from GitHub API" exit 1 } get_asset_name() { - echo "speakeasy_$1_$2.zip" + _get_asset_name_version="$1" + _get_asset_name_platform="$2" + echo "speakeasy_${_get_asset_name_version}_${_get_asset_name_platform}.zip" } get_download_url() { - local asset_name=$(get_asset_name $2 $3) - echo "https://github.com/speakeasy-api/speakeasy/releases/download/v$1/${asset_name}" + _get_download_url_version="$1" + _get_download_url_asset_name=$(get_asset_name "$2" "$3") + echo "https://github.com/speakeasy-api/speakeasy/releases/download/v${_get_download_url_version}/${_get_download_url_asset_name}" } get_checksum_url() { - echo "https://github.com/speakeasy-api/speakeasy/releases/download/v$1/checksums.txt" + _get_checksum_url_version="$1" + echo "https://github.com/speakeasy-api/speakeasy/releases/download/v${_get_checksum_url_version}/checksums.txt" } command_exists() { command -v "$@" >/dev/null 2>&1 } -fmt_error() { - echo ${RED}"Error: $@"${RESET} >&2 -} +check_dependencies() { + command_exists curl || { + fmt_error "curl is not installed" + exit 1 + } -fmt_warning() { - echo ${YELLOW}"Warning: $@"${RESET} >&2 + command_exists unzip || { + fmt_error "unzip is not installed" + exit 1 + } } -fmt_underline() { - echo "$(printf '\033[4m')$@$(printf '\033[24m')" +fmt_error() { + printf "%sError: %s%s\n" "${RED}" "$@" "${RESET}" >&2 } -fmt_code() { - echo "\`$(printf '\033[38;5;247m')$@${RESET}\`" +fmt_warning() { + printf "%sWarning: %s%s\n" "${YELLOW}" "$@" "${RESET}" >&2 } setup_color() { # Only use colors if connected to a terminal if [ -t 1 ]; then RED=$(printf '\033[31m') - GREEN=$(printf '\033[32m') YELLOW=$(printf '\033[33m') - BLUE=$(printf '\033[34m') - MAGENTA=$(printf '\033[35m') - BOLD=$(printf '\033[1m') RESET=$(printf '\033[m') else RED="" - GREEN="" YELLOW="" - BLUE="" - MAGENTA="" - BOLD="" RESET="" fi } @@ -122,23 +185,26 @@ get_machine() { esac } -get_tmp_dir() { - echo $(mktemp -d) -} - do_checksum() { - checksum_url=$(get_checksum_url $version) - expected_checksum=$(curl -sfL $checksum_url | grep $asset_name | awk '{print $1}') + checksum_url=$(get_checksum_url "$version") + checksum_output="$tmp_dir/checksums.txt" + + if ! curl_with_retry "$checksum_output" -fL "$checksum_url"; then + fmt_error "Failed to retrieve checksums from $checksum_url" + exit 1 + fi + + expected_checksum=$(grep "$asset_name" "$checksum_output" | awk '{print $1}') if [ -z "$expected_checksum" ]; then - fmt_error "Failed to retrieve expected checksum for $asset_name from $checksum_url" + fmt_error "Failed to find checksum for $asset_name in $checksum_url" exit 1 fi if command_exists sha256sum; then - checksum=$(sha256sum $asset_name | awk '{print $1}') + checksum=$(sha256sum "$asset_name" | awk '{print $1}') elif command_exists shasum; then - checksum=$(shasum -a 256 $asset_name | awk '{print $1}') + checksum=$(shasum -a 256 "$asset_name" | awk '{print $1}') else fmt_warning "Could not find a checksum program. Install shasum or sha256sum to validate checksum." return 0 @@ -151,52 +217,36 @@ do_checksum() { } do_install_binary() { - asset_name=$(get_asset_name $os $machine) - download_url=$(get_download_url $version $os $machine) - - command_exists curl || { - fmt_error "curl is not installed" - exit 1 - } - - command_exists unzip || { - fmt_error "unzip is not installed" - exit 1 - } - - local tmp_dir=$(get_tmp_dir) + asset_name=$(get_asset_name "$os" "$machine") + download_url=$(get_download_url "$version" "$os" "$machine") # Download zip to tmp directory echo "Downloading $download_url" - set +e - (cd $tmp_dir && curl -sfL -O "$download_url") - exit_code=$? - set -e - if [ $exit_code -ne 0 ]; then - fmt_error "Failed to download $download_url (curl exit code: $exit_code)" - rm -rf $tmp_dir + if ! curl_with_retry "$tmp_dir/$asset_name" -fL "$download_url"; then + fmt_error "Failed to download $download_url" exit 1 fi - (cd $tmp_dir && do_checksum) + (cd "$tmp_dir" && do_checksum) # Extract download - (cd $tmp_dir && unzip -q "$asset_name") + (cd "$tmp_dir" && unzip -q "$asset_name") # Install binary sudo_cmd='mv '"$tmp_dir/$BINARY_NAME"' '"$INSTALL_DIR"' && chmod a+x '"$INSTALL_DIR/$BINARY_NAME" sudo -p "sudo password required for installing to $INSTALL_DIR: " -- sh -c "$sudo_cmd" echo "Installed speakeasy to $INSTALL_DIR" - - # Cleanup - rm -rf $tmp_dir } main() { setup_color + check_dependencies + + tmp_dir="$(mktemp -d)" + trap 'rm -rf "$tmp_dir"' EXIT - latest_tag=$(get_latest_release $REPO_NAME) - latest_version=$(echo $latest_tag | sed 's/v//') + latest_tag=$(get_latest_release "$REPO_NAME") + latest_version=$(echo "$latest_tag" | sed 's/v//') version=${VERSION:-$latest_version} os=$(get_os) @@ -214,7 +264,7 @@ main() { fi do_install_binary - printf "$YELLOW" + printf "%s" "${YELLOW}" cat <<'EOF' .-. .--''-. .' '. /' `. @@ -228,7 +278,7 @@ main() { \ \ \ ' ' 'MJP EOF - printf "$RESET" + printf "%s" "${RESET}" } diff --git a/integration/shell/install_test.bats b/integration/shell/install_test.bats new file mode 100644 index 000000000..1239ee848 --- /dev/null +++ b/integration/shell/install_test.bats @@ -0,0 +1,387 @@ +#!/usr/bin/env bats + +# Load test helper +load test_helper + +setup() { + setup_mocks + source_install_functions +} + +teardown() { + teardown_mocks +} + +# ============================================================================= +# get_os tests +# ============================================================================= + +@test "get_os returns linux for Linux system" { + mock_uname "Linux" "x86_64" + run get_os + [ "$status" -eq 0 ] + [ "$output" = "linux" ] +} + +@test "get_os returns linux for linux (lowercase) system" { + mock_uname "linux" "x86_64" + run get_os + [ "$status" -eq 0 ] + [ "$output" = "linux" ] +} + +@test "get_os returns darwin for Darwin system" { + mock_uname "Darwin" "arm64" + run get_os + [ "$status" -eq 0 ] + [ "$output" = "darwin" ] +} + +@test "get_os returns darwin for darwin (lowercase) system" { + mock_uname "darwin" "arm64" + run get_os + [ "$status" -eq 0 ] + [ "$output" = "darwin" ] +} + +@test "get_os returns empty for unsupported system" { + mock_uname "Windows" "x86_64" + run get_os + [ "$status" -eq 0 ] + [ "$output" = "" ] +} + +# ============================================================================= +# get_machine tests +# ============================================================================= + +@test "get_machine returns amd64 for x86_64" { + mock_uname "Linux" "x86_64" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "amd64" ] +} + +@test "get_machine returns amd64 for amd64" { + mock_uname "Linux" "amd64" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "amd64" ] +} + +@test "get_machine returns amd64 for x64" { + mock_uname "Linux" "x64" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "amd64" ] +} + +@test "get_machine returns 386 for i386" { + mock_uname "Linux" "i386" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "386" ] +} + +@test "get_machine returns 386 for i686" { + mock_uname "Linux" "i686" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "386" ] +} + +@test "get_machine returns 386 for i86pc" { + mock_uname "Linux" "i86pc" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "386" ] +} + +@test "get_machine returns 386 for x86" { + mock_uname "Linux" "x86" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "386" ] +} + +@test "get_machine returns arm64 for arm64" { + mock_uname "Darwin" "arm64" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "arm64" ] +} + +@test "get_machine returns arm64 for aarch64" { + mock_uname "Linux" "aarch64" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "arm64" ] +} + +@test "get_machine returns arm64 for armv6l" { + mock_uname "Linux" "armv6l" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "arm64" ] +} + +@test "get_machine returns empty for unsupported architecture" { + mock_uname "Linux" "riscv64" + run get_machine + [ "$status" -eq 0 ] + [ "$output" = "" ] +} + +# ============================================================================= +# command_exists tests +# ============================================================================= + +@test "command_exists returns 0 for existing command" { + run command_exists ls + [ "$status" -eq 0 ] +} + +@test "command_exists returns non-zero for non-existing command" { + run command_exists definitely_not_a_real_command_12345 + [ "$status" -ne 0 ] +} + +# ============================================================================= +# get_asset_name tests +# ============================================================================= + +@test "get_asset_name formats correctly for darwin arm64" { + run get_asset_name "darwin" "arm64" + [ "$status" -eq 0 ] + [ "$output" = "speakeasy_darwin_arm64.zip" ] +} + +@test "get_asset_name formats correctly for linux amd64" { + run get_asset_name "linux" "amd64" + [ "$status" -eq 0 ] + [ "$output" = "speakeasy_linux_amd64.zip" ] +} + +@test "get_asset_name formats correctly for linux 386" { + run get_asset_name "linux" "386" + [ "$status" -eq 0 ] + [ "$output" = "speakeasy_linux_386.zip" ] +} + +# ============================================================================= +# get_download_url tests +# ============================================================================= + +@test "get_download_url formats correctly" { + run get_download_url "1.2.3" "darwin" "arm64" + [ "$status" -eq 0 ] + [ "$output" = "https://github.com/speakeasy-api/speakeasy/releases/download/v1.2.3/speakeasy_darwin_arm64.zip" ] +} + +@test "get_download_url includes version correctly" { + run get_download_url "10.20.30" "linux" "amd64" + [ "$status" -eq 0 ] + [ "$output" = "https://github.com/speakeasy-api/speakeasy/releases/download/v10.20.30/speakeasy_linux_amd64.zip" ] +} + +# ============================================================================= +# get_checksum_url tests +# ============================================================================= + +@test "get_checksum_url formats correctly" { + run get_checksum_url "1.2.3" + [ "$status" -eq 0 ] + [ "$output" = "https://github.com/speakeasy-api/speakeasy/releases/download/v1.2.3/checksums.txt" ] +} + +@test "get_checksum_url includes version correctly" { + run get_checksum_url "10.20.30" + [ "$status" -eq 0 ] + [ "$output" = "https://github.com/speakeasy-api/speakeasy/releases/download/v10.20.30/checksums.txt" ] +} + +# ============================================================================= +# setup_color tests +# ============================================================================= + +@test "setup_color sets empty colors when not connected to terminal" { + # Run in a subshell to not affect other tests + ( + # Force non-terminal + exec 1>/dev/null + setup_color + [ -z "$RED" ] + [ -z "$YELLOW" ] + [ -z "$RESET" ] + ) +} + +# ============================================================================= +# fmt_error tests +# ============================================================================= + +@test "fmt_error outputs to stderr" { + # Initialize colors first (empty since not a terminal) + RED="" + RESET="" + run fmt_error "test error message" + [ "$status" -eq 0 ] + [[ "$output" == *"Error: test error message"* ]] +} + +@test "fmt_error includes the message" { + RED="" + RESET="" + run fmt_error "specific error text here" + [[ "$output" == *"specific error text here"* ]] +} + +# ============================================================================= +# fmt_warning tests +# ============================================================================= + +@test "fmt_warning outputs warning message" { + YELLOW="" + RESET="" + run fmt_warning "test warning message" + [ "$status" -eq 0 ] + [[ "$output" == *"Warning: test warning message"* ]] +} + +# ============================================================================= +# curl_with_retry tests +# ============================================================================= + +@test "curl_with_retry succeeds on 200 response" { + mock_curl 0 "200" "response body" + local output_file="$MOCK_DIR/output.txt" + run curl_with_retry "$output_file" -fsSL "https://example.com" + [ "$status" -eq 0 ] + [[ "$(cat "$output_file")" == *"response body"* ]] +} + +@test "curl_with_retry succeeds on 201 response" { + mock_curl 0 "201" "created response" + local output_file="$MOCK_DIR/output.txt" + run curl_with_retry "$output_file" -fsSL "https://example.com" + [ "$status" -eq 0 ] + [[ "$(cat "$output_file")" == *"created response"* ]] +} + +@test "curl_with_retry fails on 404 response" { + RED="" + RESET="" + mock_curl 0 "404" "not found" + local output_file="$MOCK_DIR/output.txt" + run curl_with_retry "$output_file" -fsSL "https://example.com" + [ "$status" -ne 0 ] +} + +@test "curl_with_retry fails on 500 response after retries" { + RED="" + RESET="" + # Create a curl mock that always returns 500 + cat > "$MOCK_DIR/curl" <<'EOF' +#!/bin/sh +output_file="" +while [ $# -gt 0 ]; do + case "$1" in + -o) output_file="$2"; shift 2 ;; + -w) shift 2 ;; + *) shift ;; + esac +done +if [ -n "$output_file" ]; then + echo "server error" > "$output_file" +fi +echo "500" +exit 0 +EOF + chmod +x "$MOCK_DIR/curl" + + # Override sleep to speed up test + create_mock sleep 0 + + local output_file="$MOCK_DIR/output.txt" + run curl_with_retry "$output_file" -fsSL "https://example.com" + [ "$status" -ne 0 ] +} + +@test "curl_with_retry retries on curl failure" { + RED="" + RESET="" + # Create a curl that fails with exit code 6 (couldn't resolve host) + cat > "$MOCK_DIR/curl" <<'EOF' +#!/bin/sh +echo "curl: (6) Could not resolve host" >&2 +exit 6 +EOF + chmod +x "$MOCK_DIR/curl" + + # Override sleep to speed up test + create_mock sleep 0 + + local output_file="$MOCK_DIR/output.txt" + run curl_with_retry "$output_file" -fsSL "https://example.com" + [ "$status" -ne 0 ] +} + +# ============================================================================= +# github_api_curl tests +# ============================================================================= + +@test "github_api_curl uses GITHUB_TOKEN when set" { + # Create a curl mock that captures arguments + cat > "$MOCK_DIR/curl" <<'EOF' +#!/bin/sh +# Output all arguments to stderr so we can check them +echo "ARGS: $*" >&2 +# Find and write to output file +output_file="" +while [ $# -gt 0 ]; do + case "$1" in + -o) output_file="$2"; shift 2 ;; + -w) shift 2 ;; + *) shift ;; + esac +done +if [ -n "$output_file" ]; then + echo "response" > "$output_file" +fi +echo "200" +exit 0 +EOF + chmod +x "$MOCK_DIR/curl" + + export GITHUB_TOKEN="test_token_123" + local output_file="$MOCK_DIR/output.txt" + run github_api_curl "$output_file" --silent "https://api.github.com/test" + + # The output includes stderr which should show the auth header + [[ "$output" == *"Authorization"* ]] || [[ "$output" == *"Bearer"* ]] || [ "$status" -eq 0 ] + unset GITHUB_TOKEN +} + +@test "github_api_curl works without GITHUB_TOKEN" { + mock_curl 0 "200" "api response" + unset GITHUB_TOKEN + local output_file="$MOCK_DIR/output.txt" + run github_api_curl "$output_file" --silent "https://api.github.com/test" + [ "$status" -eq 0 ] +} + +# ============================================================================= +# Integration-style tests +# ============================================================================= + +@test "INSTALL_DIR defaults to /usr/local/bin" { + [ "$INSTALL_DIR" = "/usr/local/bin" ] +} + +@test "BINARY_NAME defaults to speakeasy" { + [ "$BINARY_NAME" = "speakeasy" ] +} + +@test "REPO_NAME is set correctly" { + [ "$REPO_NAME" = "speakeasy-api/speakeasy" ] +} diff --git a/integration/shell/test_helper.bash b/integration/shell/test_helper.bash new file mode 100644 index 000000000..29781664e --- /dev/null +++ b/integration/shell/test_helper.bash @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Test helper for install.sh BATS tests + +# Get the directory of this script +BATS_TEST_DIRNAME="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)" +INSTALL_SCRIPT="$PROJECT_ROOT/install.sh" + +# Source functions from install.sh without executing main +# This extracts functions by removing the final 'main' call +source_install_functions() { + # Create temp file with functions only (remove 'set -e' and final 'main' call) + local temp_script + temp_script=$(mktemp) + + # Copy install.sh but comment out 'set -e' and the final 'main' call + sed -e 's/^set -e$/# set -e (disabled for testing)/' \ + -e 's/^main$/# main (disabled for testing)/' \ + "$INSTALL_SCRIPT" > "$temp_script" + + # shellcheck source=/dev/null + source "$temp_script" + rm -f "$temp_script" +} + +# Setup mock directory for fake commands +setup_mocks() { + MOCK_DIR="$(mktemp -d)" + export PATH="$MOCK_DIR:$PATH" + export MOCK_DIR +} + +# Cleanup mock directory +teardown_mocks() { + if [[ -n "${MOCK_DIR:-}" && -d "$MOCK_DIR" ]]; then + rm -rf "$MOCK_DIR" + fi +} + +# Create a mock command +# Usage: create_mock command_name exit_code [output] +create_mock() { + local cmd_name="$1" + local exit_code="${2:-0}" + local output="${3:-}" + + cat > "$MOCK_DIR/$cmd_name" < "$MOCK_DIR/uname" < "$MOCK_DIR/curl" <<'OUTER_EOF' +#!/bin/sh +# Parse arguments to find -o and -w flags +output_file="" +write_out="" +while [ $# -gt 0 ]; do + case "$1" in + -o) output_file="$2"; shift 2 ;; + -w) write_out="$2"; shift 2 ;; + *) shift ;; + esac +done +OUTER_EOF + + cat >> "$MOCK_DIR/curl" < "\$output_file" +fi + +# Handle write-out format +if [ "\$write_out" = "%{http_code}" ]; then + echo "$http_code" +fi + +exit $exit_code +EOF + chmod +x "$MOCK_DIR/curl" +} diff --git a/scripts/semver.bash b/scripts/semver.bash index 4f5166fe2..d12e82d77 100755 --- a/scripts/semver.bash +++ b/scripts/semver.bash @@ -172,7 +172,7 @@ function compare_fields { while true do - [ $order -ne 0 ] && { echo $order ; return ; } + [ "$order" -ne 0 ] && { echo "$order" ; return ; } : $(( i++ )) left="${leftfield[$i]}" @@ -392,7 +392,6 @@ function command_get { if [[ "$#" -ne "2" ]] || [[ -z "$1" ]] || [[ -z "$2" ]]; then usage_help - exit 0 fi part="$1" diff --git a/scripts/upgrade.bash b/scripts/upgrade.bash index 469c9ec5b..88bc26334 100755 --- a/scripts/upgrade.bash +++ b/scripts/upgrade.bash @@ -5,11 +5,9 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) ( cd "${SCRIPT_DIR}/.." - export GOPRIVATE=github.com/speakeasy-api/* - read -r CURRENT_VERSION <<<$(git describe --tags | awk '{print substr($1,2); }') - # echo " => CURRENT_VERSION=${CURRENT_VERSION}" + export GOPRIVATE="github.com/speakeasy-api/*" - read -r CURRENT_OPENAPI_GENERATION_VERSION <<<$(cat go.mod | grep github.com/speakeasy-api/openapi-generation/v2 | awk '{ print $2 }' | awk '{print substr($1,2); }') + read -r CURRENT_OPENAPI_GENERATION_VERSION <<<"$(cat go.mod | grep github.com/speakeasy-api/openapi-generation/v2 | awk '{ print $2 }' | awk '{print substr($1,2); }')" # echo " => CURRENT_OPENAPI_GENERATION_VERSION=${CURRENT_OPENAPI_GENERATION_VERSION}" START_DATE=$(gh release view "v${CURRENT_OPENAPI_GENERATION_VERSION}" --repo speakeasy-api/openapi-generation --json createdAt | jq -r '.createdAt') @@ -23,7 +21,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) LATEST_OPENAPI_GENERATION_VERSION=$(gh release list --limit 1 --repo speakeasy-api/openapi-generation --json tagName | jq -r '.[0].tagName') # echo " => LATEST_OPENAPI_GENERATION_VERSION=${LATEST_OPENAPI_GENERATION_VERSION}" - read -r SEMVER_CHANGE <<<$("${SCRIPT_DIR}/semver.bash" diff "${CURRENT_OPENAPI_GENERATION_VERSION}" "${LATEST_OPENAPI_GENERATION_VERSION}") + read -r SEMVER_CHANGE <<<"$("${SCRIPT_DIR}/semver.bash" diff "${CURRENT_OPENAPI_GENERATION_VERSION}" "${LATEST_OPENAPI_GENERATION_VERSION}")" # echo " => SEMVER_CHANGE=${SEMVER_CHANGE}" if [[ "$SEMVER_CHANGE" == "none" || -z $SEMVER_CHANGE ]]; then @@ -31,8 +29,6 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) exit 0 fi - read -r BUMPED_CURRENT_VERSION <<<$("${SCRIPT_DIR}/semver.bash" bump "${SEMVER_CHANGE}" "${CURRENT_VERSION}") -# echo " => BUMPED_CURRENT_VERSION=${BUMPED_CURRENT_VERSION}" echo " ===== Pull Requests ==== " while IFS= read -r PR; do echo -e "${PR}" @@ -42,7 +38,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) SUMMARY=$(gum input --placeholder "Commit Message (please summarize changes above): ") else echo "⚠️ Install gum for a better DX: https://github.com/charmbracelet/gum" - read -p "Commit Message (please summarize changes above): " SUMMARY + read -r -p "Commit Message (please summarize changes above): " SUMMARY fi go get -v "github.com/speakeasy-api/openapi-generation/v2@${LATEST_OPENAPI_GENERATION_VERSION}"