Add release automation and installation tooling#19
Conversation
WalkthroughAdds a GitHub Actions release workflow and GoReleaser config, provides an install script and HTML redirect, and expands README installation instructions. The workflow triggers on tag pushes (v*), builds releases via Goreleaser, and the installer fetches and installs release binaries. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant GH as GitHub
participant GA as GitHub Actions
participant GR as GoReleaser
Dev->>GH: Push tag vX.Y.Z
GH-->>GA: Trigger release workflow (on tag v*)
GA->>GA: Checkout repo (fetch-depth: 0)
GA->>GA: Setup Go (version from go.mod)
GA->>GA: Restore/setup Go module cache
GA->>GR: Install goreleaser action
GA->>GR: Run `goreleaser release --clean` (GITHUB_TOKEN)
GR-->>GH: Create GitHub release & upload artifacts
sequenceDiagram
autonumber
actor U as User
participant SH as install.sh
participant GH as GitHub Releases
participant OS as Local System
U->>SH: Execute install.sh (DEVX_VERSION?, DEVX_INSTALL_DIR?)
SH->>SH: Detect OS & arch, normalize inputs
alt DEVX_VERSION == "latest"
SH->>GH: Query latest tag
GH-->>SH: vX.Y.Z
end
SH->>GH: Download release tarball `devx_<ver>_<os>_<arch>.tar.gz`
GH-->>SH: Return tarball
SH->>SH: Extract binary to temp dir
SH->>OS: Create install dir and install binary (0755)
SH-->>U: Print installed path and verify version
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
|
@coderabbitai help |
ChatThere are 3 ways to chat with CodeRabbit:
CodeRabbit commands
Other keywords and placeholders
CodeRabbit configuration file (
|
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
.github/workflows/release.yml(1 hunks).goreleaser.yaml(1 hunks)README.md(2 hunks)install/install-redirect.html(1 hunks)install/install.sh(1 hunks)
| brews: | ||
| - name: devx | ||
| homepage: https://github.com/jfox85/devx | ||
| description: "A macOS development environment manager" | ||
| license: "MIT" | ||
| commit_author: | ||
| name: toneclone-bot | ||
| email: opensource@toneclone.dev | ||
| tap: | ||
| owner: toneclone | ||
| name: toneclone | ||
| test: | | ||
| system "#{bin}/devx", "--version" | ||
| install: | | ||
| bin.install "devx" | ||
| dependencies: | ||
| - name: go | ||
| type: optional |
There was a problem hiding this comment.
Point the Homebrew tap at the correct repository
Lines 51‑52 set the tap to toneclone/toneclone, but the Homebrew tap repo is typically toneclone/homebrew-toneclone (matching the documented brew tap toneclone/toneclone, which strips the homebrew- prefix). GoReleaser expects the actual GitHub repo name here; with the current value it will try to push the formula to github.com/toneclone/toneclone, so tap updates will fail. Update the tap name to the real repo.
brews:
- name: devx
homepage: https://github.com/jfox85/devx
description: "A macOS development environment manager"
license: "MIT"
commit_author:
name: toneclone-bot
email: opensource@toneclone.dev
tap:
owner: toneclone
- name: toneclone
+ name: homebrew-toneclone📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| brews: | |
| - name: devx | |
| homepage: https://github.com/jfox85/devx | |
| description: "A macOS development environment manager" | |
| license: "MIT" | |
| commit_author: | |
| name: toneclone-bot | |
| email: opensource@toneclone.dev | |
| tap: | |
| owner: toneclone | |
| name: toneclone | |
| test: | | |
| system "#{bin}/devx", "--version" | |
| install: | | |
| bin.install "devx" | |
| dependencies: | |
| - name: go | |
| type: optional | |
| brews: | |
| - name: devx | |
| homepage: https://github.com/jfox85/devx | |
| description: "A macOS development environment manager" | |
| license: "MIT" | |
| commit_author: | |
| name: toneclone-bot | |
| email: opensource@toneclone.dev | |
| tap: | |
| owner: toneclone | |
| name: homebrew-toneclone | |
| test: | | |
| system "#{bin}/devx", "--version" | |
| install: | | |
| bin.install "devx" | |
| dependencies: | |
| - name: go | |
| type: optional |
🤖 Prompt for AI Agents
.goreleaser.yaml lines 42 to 59: the Homebrew tap is incorrectly set to
toneclone/toneclone so GoReleaser will push the formula to
github.com/toneclone/toneclone; change the tap name to the actual repository
name by setting tap.owner to toneclone and tap.name to homebrew-toneclone (so
GoReleaser targets github.com/toneclone/homebrew-toneclone) and keep the rest of
the brew config as-is.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (8)
install/install.sh (5)
18-19: Add dependency check forinstall.Script uses
installbut only checks for curl/tar.command -v curl >/dev/null 2>&1 || { echo "curl is required to install devx" >&2; exit 1; } command -v tar >/dev/null 2>&1 || { echo "tar is required to install devx" >&2; exit 1; } +command -v install >/dev/null 2>&1 || { echo "install is required to install devx" >&2; exit 1; }
77-79: Preflight writable install dir; guide user if not.Avoid silent failures when INSTALL_DIR isn’t writable (common for /usr/local/bin).
-mkdir -p "${INSTALL_DIR}" -install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx" +mkdir -p "${INSTALL_DIR}" +if [[ ! -w "${INSTALL_DIR}" ]]; then + echo "No write access to ${INSTALL_DIR}. Re-run with sudo or set DEVX_INSTALL_DIR (e.g., \$HOME/.local/bin)." >&2 + exit 1 +fi +install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx"
51-56: Handle GitHub API rate limits and add a fallback.Unauthenticated calls can hit rate limits. Allow using GITHUB_TOKEN and add a fallback to the releases/latest redirect.
-if [[ "$VERSION" == "latest" ]]; then - VERSION="$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | \ +if [[ "$VERSION" == "latest" ]]; then + auth_hdr=() + [[ -n "${GITHUB_TOKEN:-}" ]] && auth_hdr=(-H "Authorization: Bearer ${GITHUB_TOKEN}") + VERSION="$(curl -fsSL "${auth_hdr[@]}" "https://api.github.com/repos/${REPO}/releases/latest" | \ grep -m1 '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')" if [[ -z "$VERSION" ]]; then - echo "Unable to determine latest release version" >&2 - exit 1 + # Fallback: follow redirect to tag URL and extract the tag + VERSION="$(curl -fsI "https://github.com/${REPO}/releases/latest" | tr -d '\r' | awk -F/ '/^location: /{print $NF}' )" + [[ -z "$VERSION" ]] && { echo "Unable to determine latest release version" >&2; exit 1; } fi fi
71-73: Harden downloads with retries and clearer failures.Improve resilience and error messages.
-echo "Downloading ${asset}..." -curl -fsSL "${url}" -o "${workdir}/${asset}" +echo "Downloading ${asset}..." +curl --fail --location --retry 3 --retry-delay 1 --connect-timeout 10 \ + --output "${workdir}/${asset}" "${url}"
71-79: Optional: Verify archive integrity using GoReleaser checksums.If you publish
*_checksums.txt, verify before installing.echo "Downloading ${asset}..." curl --fail --location --retry 3 --retry-delay 1 --connect-timeout 10 \ --output "${workdir}/${asset}" "${url}" + +# Optional integrity verification (requires published checksums file) +checksums="devx_${asset_version}_checksums.txt" +checksums_url="https://github.com/${REPO}/releases/download/${VERSION}/${checksums}" +if curl -fsSL "${checksums_url}" -o "${workdir}/${checksums}"; then + (cd "${workdir}" && grep " ${asset}\$" "${checksums}" | shasum -a 256 -c -) || { + echo "Checksum verification failed for ${asset}" >&2 + exit 1 + } +fiREADME.md (3)
49-60: Add a pinned install example and sudo guidance.For reproducibility and least surprise:
- Show a pinned install using a tagged script URL.
- Note sudo or alternate INSTALL_DIR when writing to /usr/local/bin.
### Universal install script ```bash -curl -fsSL https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh | bash +# Pinned to a specific version (reproducible): +# curl -fsSL https://raw.githubusercontent.com/jfox85/devx/v1.2.3/install/install.sh | bashSet
DEVX_INSTALL_DIRorDEVX_VERSIONto customize the installation:curl -fsSL https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh \ | DEVX_INSTALL_DIR=$HOME/.local/bin DEVX_VERSION=v1.2.3 bash +# If installing to /usr/local/bin and you lack write permission, use: +# curl -fsSL .../install.sh | sudo DEVX_VERSION=v1.2.3 bash
62-66: Clarify archive naming for direct downloads.Mention the archive pattern to help users pick the right file.
-Download the appropriate archive from the [releases page](https://github.com/jfox85/devx/releases), extract it, and move the -`devx` binary into your `PATH`. +Download the appropriate archive from the [releases page](https://github.com/jfox85/devx/releases) +(format: `devx_<version>_<os>_<arch>.tar.gz`, e.g., `devx_1.2.3_darwin_arm64.tar.gz`), extract it, and move the `devx` binary into your `PATH`.
36-41: Homebrew tip (optional).Consider adding
brew updatebefore install for first-time tap users.brew tap jfox85/homebrew-devx +brew update brew install devx
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.goreleaser.yaml(1 hunks)README.md(2 hunks)install/install.sh(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- .goreleaser.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Build
- GitHub Check: Go Vet
- GitHub Check: Test Windows
- GitHub Check: Test (1.23)
- GitHub Check: Build (linux, arm64)
🔇 Additional comments (1)
install/install.sh (1)
64-66: Asset name construction fix looks good.Stripping the leading “v” before composing the archive name resolves the 404 from mismatched asset names. Nice catch.
| echo "Extracting devx..." | ||
| tar -xzf "${workdir}/${asset}" -C "${workdir}" | ||
|
|
||
| mkdir -p "${INSTALL_DIR}" | ||
| install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx" | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Don’t assume archive layout; make extraction robust.
If the archive contains a leading directory (common with GoReleaser), ${workdir}/devx won’t exist and install will fail. Locate the binary after extraction.
Apply:
-echo "Extracting devx..."
-tar -xzf "${workdir}/${asset}" -C "${workdir}"
-
-mkdir -p "${INSTALL_DIR}"
-install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx"
+echo "Extracting devx..."
+tar -xzf "${workdir}/${asset}" -C "${workdir}"
+# Find the devx binary regardless of archive layout (flat or nested)
+bin_path="$(cd "${workdir}" && find . -type f -name devx -perm -u+x -print -quit)"
+if [[ -z "${bin_path}" ]]; then
+ echo "devx binary not found in archive" >&2
+ exit 1
+fi
+mkdir -p "${INSTALL_DIR}"
+install -m 0755 "${workdir}/${bin_path#./}" "${INSTALL_DIR}/devx"If your GoReleaser archives are guaranteed flat, confirm and we can keep the current approach. Otherwise, this makes installs resilient.
Based on learnings
🏁 Script executed:
#!/bin/bash
grep -R -n "asset=" install/install.sh
sed -n '1,200p' install/install.sh
grep -R -n "GoReleaser" .Length of output: 2069
Don’t assume flat archive layout; locate the binary dynamically
GoReleaser’s tar.gz archives include a top-level directory by default, so ${workdir}/devx may not exist. Replace the hardcoded path with a find-and-install approach:
-echo "Extracting devx..."
-tar -xzf "${workdir}/${asset}" -C "${workdir}"
-
-mkdir -p "${INSTALL_DIR}"
-install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx"
+echo "Extracting devx..."
+tar -xzf "${workdir}/${asset}" -C "${workdir}"
+# Find the devx binary regardless of archive layout
+bin_path="$(cd "${workdir}" && find . -type f -name devx -perm -u+x -print -quit)"
+if [[ -z "${bin_path}" ]]; then
+ echo "devx binary not found in archive" >&2
+ exit 1
+fi
+mkdir -p "${INSTALL_DIR}"
+install -m 0755 "${workdir}/${bin_path#./}" "${INSTALL_DIR}/devx"install/install.sh:74-79
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| echo "Extracting devx..." | |
| tar -xzf "${workdir}/${asset}" -C "${workdir}" | |
| mkdir -p "${INSTALL_DIR}" | |
| install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx" | |
| echo "Extracting devx..." | |
| tar -xzf "${workdir}/${asset}" -C "${workdir}" | |
| # Find the devx binary regardless of archive layout | |
| bin_path="$(cd "${workdir}" && find . -type f -name devx -perm -u+x -print -quit)" | |
| if [[ -z "${bin_path}" ]]; then | |
| echo "devx binary not found in archive" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p "${INSTALL_DIR}" | |
| install -m 0755 "${workdir}/${bin_path#./}" "${INSTALL_DIR}/devx" |
🤖 Prompt for AI Agents
In install/install.sh around lines 74 to 79, the script assumes a flat archive
and uses "${workdir}/devx" which fails when the tarball contains a top-level
directory; change the install step to search the extracted workdir for the devx
binary (e.g., find under "${workdir}" for an executable file named "devx" or
matching pattern, take the first match) and then use that discovered path with
install -m 0755 into "${INSTALL_DIR}/devx" so the script works regardless of
archive layout.
Summary
Testing
https://chatgpt.com/codex/tasks/task_e_68da029468e483339cc24643d7d93332
Summary by CodeRabbit
New Features
Documentation
Chores