Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write
packages: write

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Set up Go module cache
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Install GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: latest
distribution: goreleaser
install-only: true

- name: Run GoReleaser
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: goreleaser release --clean
62 changes: 62 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
project_name: devx
before:
hooks:
- go test ./...

builds:
- id: devx
main: ./main.go
binary: devx
env:
- CGO_ENABLED=0
goos:
- darwin
- linux
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X github.com/jfox85/devx/version.Version={{.Version}}
- -X github.com/jfox85/devx/version.GitCommit={{.ShortCommit}}
- -X github.com/jfox85/devx/version.BuildDate={{.Date}}

archives:
- format: tar.gz
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
files:
- LICENSE
- README.md
strip_parent_binary_folder: true
wrap_in_directory: false

checksums:
name_template: "checksums.txt"

snapshot:
name_template: "{{ .Tag }}-SNAPSHOT"

changelog:
use: git

brews:
- name: devx
homepage: https://github.com/jfox85/devx
description: "A macOS development environment manager"
license: "MIT"
commit_author:
name: devx-bot
email: devx@jonefox.com
tap:
owner: jfox85
name: homebrew-devx
test: |
system "#{bin}/devx", "--version"
install: |
bin.install "devx"
dependencies:
- name: go
type: optional
Comment on lines 42 to 59
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.


announce:
skip: true
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,39 @@ A macOS CLI tool for managing local development environments with Git worktrees,

## Installation

### Homebrew (macOS & Linux)

```bash
brew tap jfox85/homebrew-devx
brew install devx
```

### go install (requires Go 1.22+)

```bash
go install github.com/jfox85/devx@latest
```

### Universal install script

```bash
curl -fsSL https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh | bash
```

Set `DEVX_INSTALL_DIR` or `DEVX_VERSION` to customize the installation:

```bash
curl -fsSL https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh \
| DEVX_INSTALL_DIR=$HOME/.local/bin DEVX_VERSION=v1.2.3 bash
```

### Direct binary download

Download the appropriate archive from the [releases page](https://github.com/jfox85/devx/releases), extract it, and move the
`devx` binary into your `PATH`.

### Build from source

```bash
git clone https://github.com/jfox85/devx
cd devx
Expand All @@ -41,7 +74,7 @@ make build
mv devx /usr/local/bin/
```

**Alternative installation methods:**
**Alternative build targets:**
```bash
# Development build (no version info)
make dev
Expand Down
14 changes: 14 additions & 0 deletions install/install-redirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="refresh" content="0;url=https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh" />
<title>Install devx</title>
</head>
<body>
<p>
Redirecting to the latest install script. If you are not automatically redirected,
<a href="https://raw.githubusercontent.com/jfox85/devx/main/install/install.sh">click here</a>.
</p>
</body>
</html>
81 changes: 81 additions & 0 deletions install/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail

REPO="jfox85/devx"
INSTALL_DIR="${DEVX_INSTALL_DIR:-/usr/local/bin}"
VERSION="${DEVX_VERSION:-latest}"

usage() {
cat <<USAGE
Install devx from GitHub releases.

Environment variables:
DEVX_VERSION Specific version (defaults to latest release)
DEVX_INSTALL_DIR Target directory for the devx binary (default: /usr/local/bin)
USAGE
}

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; }

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi

os="$(uname -s | tr '[:upper:]' '[:lower:]')"
arch="$(uname -m)"

case "$os" in
linux|darwin)
;;
*)
echo "Unsupported operating system: $os" >&2
exit 1
;;
esac

case "$arch" in
x86_64|amd64)
arch="amd64"
;;
arm64|aarch64)
arch="arm64"
;;
*)
echo "Unsupported architecture: $arch" >&2
exit 1
;;
esac

if [[ "$VERSION" == "latest" ]]; then
VERSION="$(curl -fsSL "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
fi
else
if [[ "$VERSION" != v* ]]; then
VERSION="v${VERSION}"
fi
fi

asset_version="${VERSION#v}"
asset="devx_${asset_version}_${os}_${arch}.tar.gz"
url="https://github.com/${REPO}/releases/download/${VERSION}/${asset}"

workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT

echo "Downloading ${asset}..."
curl -fsSL "${url}" -o "${workdir}/${asset}"

echo "Extracting devx..."
tar -xzf "${workdir}/${asset}" -C "${workdir}"

mkdir -p "${INSTALL_DIR}"
install -m 0755 "${workdir}/devx" "${INSTALL_DIR}/devx"

Comment on lines +74 to +79
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

echo "devx installed to ${INSTALL_DIR}/devx"
"${INSTALL_DIR}/devx" --version || true
Loading