diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e1052fb..c1edde5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,13 +58,19 @@ jobs: brew upgrade cloudsmith-cli ``` - ### Rollback to this version + ### Rollback + + Homebrew has no downgrade command, so a rollback uninstalls first and + then installs a pinned formula from the tap: - Download `cloudsmith-cli.rb` from this release, then: ```bash brew uninstall cloudsmith-cli - brew install --formula ./cloudsmith-cli.rb + brew install cloudsmith-io/cloudsmith-cli/cloudsmith-cli@1.20.1 + brew pin cloudsmith-cli@1.20.1 ``` + + See the [README](https://github.com/cloudsmith-io/homebrew-cloudsmith-cli#holding-or-rolling-back-a-version) + for the available pinned versions and for rolling back to any other version. draft: false prerelease: false files: | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee8739b..780e83a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,92 +1,58 @@ # Contributing to the Cloudsmith CLI Homebrew Tap -This repository contains the Homebrew formula for installing the [Cloudsmith CLI](https://docs.cloudsmith.com/developer-tools/cli). Most maintenance work here is bumping the formula to a newly released Cloudsmith CLI version. +This repository is the Homebrew tap for the [Cloudsmith CLI](https://docs.cloudsmith.com/developer-tools/cli). -## Setup +`Formula/cloudsmith-cli.rb` is **generated**, not hand-maintained. Everything else here is maintained by hand. -Clone the tap repository: +## How the Main Formula Is Released -```bash -git clone https://github.com/cloudsmith-io/homebrew-cloudsmith-cli.git -cd homebrew-cloudsmith-cli -``` - -Install the formula locally for testing: - -```bash -brew install --build-from-source ./Formula/cloudsmith-cli.rb -``` - -To reinstall after formula changes: - -```bash -brew uninstall cloudsmith-cli -brew install --build-from-source ./Formula/cloudsmith-cli.rb -``` - -Run Homebrew audit checks when this repository is your active Homebrew tap checkout: - -```bash -brew audit --strict --online cloudsmith-cli -``` - -If you are working from a regular clone outside Homebrew's tap directory, `brew audit cloudsmith-cli` audits the tapped copy instead of this checkout. +The Cloudsmith CLI release workflow owns `Formula/cloudsmith-cli.rb`. Its +`publish-homebrew` job renders +[`packaging/homebrew/cloudsmith-cli.rb.tmpl`](https://github.com/cloudsmith-io/cloudsmith-cli/blob/master/packaging/homebrew/cloudsmith-cli.rb.tmpl) +with the new version and each platform's SHA256, verifies those checksums against +the published downloads, and opens a bump pull request here as `cloudsmith-bot`. +Review it and merge with a squash merge. There is nothing to run in this +repository for a version bump. -## Bumping the CLI Version +Because that job copies the rendered file over `Formula/cloudsmith-cli.rb` +wholesale, **any change to the main formula must be made in the upstream +template**, or the next release will silently drop it. -Use the release helper from a clean, up-to-date `main` branch: +If a formula fix is urgent, land it here to unblock users and open the matching +template change upstream in the same sitting. Add `revision 1` (or the next +number) when the CLI version itself has not changed, so that Homebrew treats it +as an upgrade and existing installs pick the fix up. The next release removes the +revision on its own. -```bash -./scripts/bump-cloudsmith-cli.sh -``` - -This is the standard release bump workflow. The helper will: - -1. Find the latest released version from [`cloudsmith-io/cloudsmith-cli`](https://github.com/cloudsmith-io/cloudsmith-cli/releases). -2. Download the released `cloudsmith.pyz` asset. -3. Calculate the SHA256 for the asset used by Homebrew. -4. Create a release branch named `release/cloudsmith-cli-v`. -5. Update `Formula/cloudsmith-cli.rb`. -6. Run `ruby -c Formula/cloudsmith-cli.rb`. -7. Run `brew audit --strict --online cloudsmith-cli` when this checkout is the active Homebrew tap. -8. Stage the formula change. -9. Print the commit, push, and PR commands for you to run after review. - -To target a specific version: - -```bash -./scripts/bump-cloudsmith-cli.sh --version v1.17.0 -``` - -To preview the release and SHA without editing files: - -```bash -./scripts/bump-cloudsmith-cli.sh --dry-run -``` - -After the helper finishes, review the staged diff: - -```bash -git diff --cached -``` +## What Is Maintained Here -Then run the commands printed by the helper. The script intentionally stops before committing or pushing so maintainers can do a final review first. +- `Formula/cloudsmith-cli@.rb` — pinned rollback targets. The release + job does not touch these, so they persist across releases. See the + [README](README.md#holding-or-rolling-back-a-version). +- `.github/workflows/formula-test.yml` — installs, uninstalls, upgrades, + downgrades and pins the formulae on macOS arm64 and Intel. This is what catches + breakage that a syntax check cannot, so trust it over local spot checks. +- `Aliases/`, `README.md`, and this file. -## Manual Fallback +## Testing Locally -If the helper cannot be used, update the `url` and `sha256` in `Formula/cloudsmith-cli.rb` manually. The SHA256 must be calculated from the released `cloudsmith.pyz` asset, not the source archive: +Homebrew only loads formulae from a tap, so work inside the tap checkout rather +than a plain clone: ```bash -curl -L -o cloudsmith.pyz https://github.com/cloudsmith-io/cloudsmith-cli/releases/download//cloudsmith.pyz -shasum -a 256 cloudsmith.pyz +brew tap cloudsmith-io/cloudsmith-cli +cd "$(brew --repository cloudsmith-io/cloudsmith-cli)" ``` -Then run: +Then, after editing: ```bash -ruby -c Formula/cloudsmith-cli.rb +brew style Formula/cloudsmith-cli.rb brew audit --strict --online cloudsmith-cli -git add Formula/cloudsmith-cli.rb +brew install cloudsmith-io/cloudsmith-cli/cloudsmith-cli +brew test cloudsmith-io/cloudsmith-cli/cloudsmith-cli ``` -Only run the `brew audit` command from the active Homebrew tap checkout; otherwise use it after syncing the tap. +A prebuilt bundle can install cleanly and still be wrong in ways only the real +lifecycle shows, so let CI exercise the upgrade, downgrade and pin paths on both +architectures before merging. diff --git a/Formula/cloudsmith-cli.rb b/Formula/cloudsmith-cli.rb index d2ded63..d271fb5 100644 --- a/Formula/cloudsmith-cli.rb +++ b/Formula/cloudsmith-cli.rb @@ -8,6 +8,9 @@ class CloudsmithCli < Formula homepage "https://docs.cloudsmith.com/developer-tools/cli" version "1.20.2" license "Apache-2.0" + # Formula-only fix on an unchanged CLI release. The next release drops this, + # because publish-homebrew upstream overwrites this file from its template. + revision 1 if OS.mac? && Hardware::CPU.arm? url "https://dl.cloudsmith.io/public/cloudsmith/cli/raw/names/cloudsmith-cli-macos-arm64/versions/1.20.2/cloudsmith-1.20.2-macos-arm64.tar.gz" diff --git a/scripts/bump-cloudsmith-cli.sh b/scripts/bump-cloudsmith-cli.sh deleted file mode 100755 index 3fd5910..0000000 --- a/scripts/bump-cloudsmith-cli.sh +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -CLI_REPO="cloudsmith-io/cloudsmith-cli" -FORMULA_NAME="cloudsmith-cli" -FORMULA_PATH="Formula/cloudsmith-cli.rb" -ASSET_NAME="cloudsmith.pyz" - -requested_version="" -dry_run=0 -run_checks=1 -create_branch=1 -allow_dirty=0 - -usage() { - cat <<'USAGE' -Usage: ./scripts/bump-cloudsmith-cli.sh [options] - -Fetch the latest Cloudsmith CLI release, update the Homebrew formula URL and -sha256 for cloudsmith.pyz, create a release branch, run quick checks, stage the -formula change, and print the final commit/push/PR commands. - -Options: - --version VERSION Bump to a specific tag, for example v1.17.0 or 1.17.0. - --dry-run Resolve the release and SHA without changing files. - --skip-checks Skip ruby syntax and brew audit checks. - --no-branch Do not create or switch to the release branch. - --allow-dirty Allow running with existing uncommitted changes. - -h, --help Show this help text. -USAGE -} - -die() { - printf 'Error: %s\n' "$*" >&2 - exit 1 -} - -require_command() { - command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" -} - -normalize_tag() { - local tag="$1" - - case "$tag" in - v*) ;; - *) tag="v${tag}" ;; - esac - - printf '%s' "$tag" | grep -Eq '^v[0-9]+(\.[0-9]+)+([-.][0-9A-Za-z.]+)?$' \ - || die "Version must look like v1.17.0; got ${tag}" - - printf '%s\n' "$tag" -} - -fetch_latest_tag() { - curl -fsSL \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/repos/${CLI_REPO}/releases/latest" \ - | ruby -rjson -e ' - release = JSON.parse(STDIN.read) - tag = release["tag_name"] - abort "GitHub release response did not include tag_name" if tag.nil? || tag.empty? - puts tag - ' -} - -extract_formula_tag() { - ruby -ne ' - if $_ =~ %r{github\.com/cloudsmith-io/cloudsmith-cli/releases/download/(v[^/]+)/cloudsmith\.pyz} - puts $1 - exit - end - ' "$FORMULA_PATH" -} - -extract_formula_sha() { - ruby -ne ' - if $_ =~ /^ sha256 "([0-9a-f]{64})"/ - puts $1 - exit - end - ' "$FORMULA_PATH" -} - -canonical_path() { - (cd "$1" 2>/dev/null && pwd -P) -} - -run_brew_audit() { - local tap_repo - - if ! command -v brew >/dev/null 2>&1; then - printf 'Skipping brew audit because brew is not available on PATH.\n' - return - fi - - tap_repo="$(brew --repository cloudsmith-io/cloudsmith-cli 2>/dev/null || true)" - if [ -n "$tap_repo" ] && [ "$(canonical_path "$tap_repo")" = "$(canonical_path "$repo_root")" ]; then - printf 'Running brew audit...\n' - brew audit --strict --online "$FORMULA_NAME" - else - printf 'Skipping brew audit because this checkout is not the active Homebrew tap.\n' - printf 'This avoids auditing a different local copy of %s.\n' "$FORMULA_NAME" - fi -} - -update_formula() { - DOWNLOAD_URL="$1" TARGET_SHA="$2" FORMULA_PATH="$FORMULA_PATH" ruby <<'RUBY' -path = ENV.fetch("FORMULA_PATH") -download_url = ENV.fetch("DOWNLOAD_URL") -target_sha = ENV.fetch("TARGET_SHA") - -content = File.read(path) -raise "Could not find formula url line" unless content.match?(/^ url ".*"$/) -raise "Could not find formula sha256 line" unless content.match?(/^ sha256 ".*"$/) - -content = content.sub(/^ url ".*"$/, %( url "#{download_url}")) -content = content.sub(/^ sha256 ".*"$/, %( sha256 "#{target_sha}")) - -File.write(path, content) -RUBY -} - -while [ "$#" -gt 0 ]; do - case "$1" in - --version) - [ "$#" -ge 2 ] || die "--version requires a value" - requested_version="$2" - shift 2 - ;; - --version=*) - requested_version="${1#*=}" - shift - ;; - --dry-run) - dry_run=1 - shift - ;; - --skip-checks) - run_checks=0 - shift - ;; - --no-branch) - create_branch=0 - shift - ;; - --allow-dirty) - allow_dirty=1 - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - die "Unknown option: $1" - ;; - esac -done - -repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" \ - || die "Run this script from inside the homebrew-cloudsmith-cli repository" -cd "$repo_root" - -[ -f "$FORMULA_PATH" ] || die "Formula not found: $FORMULA_PATH" - -require_command curl -require_command git -require_command ruby -require_command shasum -require_command awk - -if [ "$dry_run" -eq 0 ] && [ "$allow_dirty" -eq 0 ]; then - if ! git diff --quiet --exit-code || ! git diff --cached --quiet --exit-code; then - die "Working tree has uncommitted changes. Commit or stash them first, or re-run intentionally with --allow-dirty." - fi -fi - -current_branch="$(git branch --show-current)" - -if [ "$dry_run" -eq 0 ] && [ "$create_branch" -eq 0 ]; then - case "$current_branch" in - main|master) - die "--no-branch would stage release changes on ${current_branch}. Run without --no-branch or switch to a release branch first." - ;; - esac -fi - -current_tag="$(extract_formula_tag)" -[ -n "$current_tag" ] || die "Could not find the current cloudsmith.pyz release tag in $FORMULA_PATH" - -current_sha="$(extract_formula_sha)" -[ -n "$current_sha" ] || die "Could not find the current sha256 in $FORMULA_PATH" - -if [ -n "$requested_version" ]; then - target_tag="$(normalize_tag "$requested_version")" -else - printf 'Fetching latest Cloudsmith CLI release...\n' - target_tag="$(normalize_tag "$(fetch_latest_tag)")" -fi - -if [ "$current_tag" = "$target_tag" ]; then - printf 'Formula already points at %s. Nothing to bump.\n' "$target_tag" - exit 0 -fi - -download_url="https://github.com/${CLI_REPO}/releases/download/${target_tag}/${ASSET_NAME}" -branch_name="release/cloudsmith-cli-${target_tag}" -tmp_dir="$(mktemp -d)" - -cleanup() { - rm -rf "$tmp_dir" -} -trap cleanup EXIT - -asset_path="${tmp_dir}/${ASSET_NAME}" - -printf 'Downloading %s...\n' "$download_url" -curl -fsSL --retry 3 --retry-delay 2 -o "$asset_path" "$download_url" -target_sha="$(shasum -a 256 "$asset_path" | awk '{print $1}')" - -printf '%s' "$target_sha" | grep -Eq '^[0-9a-f]{64}$' \ - || die "Calculated SHA256 does not look valid: ${target_sha}" - -if [ "$dry_run" -eq 1 ]; then - cat < $target_tag - -SHA256: - $current_sha -> $target_sha - -Review before committing: - git diff --cached - -Copy/paste when ready: - git commit -m "Bump cloudsmith-cli to $target_tag" - git push -u origin "$active_branch" - gh pr create --fill -EOF