From 36728489fc477a7fd208af900019ac960a088ace Mon Sep 17 00:00:00 2001 From: James Maes Date: Tue, 26 May 2026 16:52:12 -0500 Subject: [PATCH] build(release): add release automation Closes #9 --- .github/workflows/release.yml | 208 ++++++++++++++++++++++++++++++++++ CLAUDE.md | 23 ++-- CONTRIBUTING.md | 8 +- README.md | 27 ++++- docs/DESIGN.md | 13 +-- docs/DEV-TOOLS.md | 43 +++++++ flake.nix | 4 +- packaging/homebrew/limen.rb | 42 +++++++ 8 files changed, 342 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 docs/DEV-TOOLS.md create mode 100644 packaging/homebrew/limen.rb diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..52036e9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,208 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + test: + name: Go checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: go vet + run: go vet ./... + + - name: go test + run: go test -race -count=1 ./... + + - name: go build + run: go build ./... + + build: + name: Build ${{ matrix.artifact }} + needs: test + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - artifact: limen-linux-amd64 + goos: linux + goarch: amd64 + - artifact: limen-linux-arm64 + goos: linux + goarch: arm64 + - artifact: limen-macos-amd64 + goos: darwin + goarch: amd64 + - artifact: limen-macos-arm64 + goos: darwin + goarch: arm64 + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Resolve version + id: version + run: | + VERSION="${GITHUB_REF_NAME#v}" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Build binary + env: + CGO_ENABLED: "0" + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + VERSION: ${{ steps.version.outputs.version }} + run: | + mkdir -p dist + go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o "dist/${{ matrix.artifact }}" . + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: dist/${{ matrix.artifact }} + + release: + name: Publish GitHub release + needs: build + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release + find artifacts -type f -exec cp {} release/ \; + cd release + shasum -a 256 ./limen-* > checksums.txt + ls -la + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + files: release/* + generate_release_notes: true + + update-homebrew: + name: Update Homebrew tap + needs: release + runs-on: ubuntu-latest + env: + HOMEBREW_TAP_TOKEN_SET: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }} + steps: + - name: Verify Homebrew tap token + run: | + if [ "${HOMEBREW_TAP_TOKEN_SET}" != "true" ]; then + echo "::error::HOMEBREW_TAP_TOKEN repo secret is required to update KofTwentyTwo/homebrew-tap." + exit 1 + fi + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Checkout Homebrew tap + uses: actions/checkout@v4 + with: + repository: KofTwentyTwo/homebrew-tap + token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + path: homebrew-tap + + - name: Update formula + env: + TAG_NAME: ${{ github.ref_name }} + run: | + VERSION="${TAG_NAME#v}" + SHA_MACOS_ARM64=$(shasum -a 256 artifacts/limen-macos-arm64/limen-macos-arm64 | cut -d' ' -f1) + SHA_MACOS_AMD64=$(shasum -a 256 artifacts/limen-macos-amd64/limen-macos-amd64 | cut -d' ' -f1) + SHA_LINUX_ARM64=$(shasum -a 256 artifacts/limen-linux-arm64/limen-linux-arm64 | cut -d' ' -f1) + SHA_LINUX_AMD64=$(shasum -a 256 artifacts/limen-linux-amd64/limen-linux-amd64 | cut -d' ' -f1) + + FORMULA="homebrew-tap/Formula/limen.rb" + mkdir -p "$(dirname "$FORMULA")" + cat > "$FORMULA" < "limen" + end + + test do + assert_match version.to_s, shell_output("#{bin}/limen --version") + end + end + RUBY + + - name: Commit and push + run: | + cd homebrew-tap + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add Formula/limen.rb + if git status -s | grep -q "Formula/limen.rb"; then + git commit -m "Update limen to ${GITHUB_REF_NAME}" + git push + else + echo "Formula already current." + fi diff --git a/CLAUDE.md b/CLAUDE.md index 3b8a608..bc9404b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,29 +8,30 @@ Project memory for AI assistants working in this repo. Read this first. ## Current status -**Design phase.** No Go source yet. The repository contains: +**Early implementation.** The repository contains: - `docs/DESIGN.md` — full design and requirements document. -- Standard repo scaffolding (README, LICENSE, CONTRIBUTING, etc.). -- `.github/` — CI workflow placeholder, issue templates, PR template. +- `internal/` packages for config, state, probing, SSH, tmux parsing, exec planning, formatting, and UI. +- `main.go`, `go.mod`, and `flake.nix` for the working binary and Nix package. +- `.github/` — CI and release workflows, issue templates, PR template. -Implementation work has not started. The next milestone is to: +The next implementation milestones are to: -1. Add `go.mod`, `main.go`, and a minimal `internal/` package skeleton matching §6.3 of `DESIGN.md`. -2. Wire up the Nix flake (`flake.nix`) so `nix run` produces a binary. -3. Implement stages incrementally — config parsing first, then probing, then UI. +1. Harden the terminal UI with real-world tmux and SSH smoke testing. +2. Keep release automation green for tagged releases and downstream package managers. +3. Continue from `docs/DESIGN.md`; if behavior changes, update the design doc in the same PR. ## Key conventions | Topic | Convention | | ------------------ | ------------------------------------------------------------------------- | -| Language | Go 1.21+, no CGO | +| Language | Go 1.24+, no CGO | | TUI framework | `bubbletea` + `lipgloss` | | Default branch | `develop` (PRs target this); `main` reserved for tagged releases | | Commit style | Conventional Commits (`feat:`, `fix:`, `docs:` …) — see `CONTRIBUTING.md` | | License | GPL-3.0 | | Build | Nix flake (`packages.default`) + plain `go build` for dev | -| Release | goreleaser via GitHub Actions on tag push | +| Release | GitHub Actions tag workflow, direct Go cross-builds, GitHub Releases, Homebrew tap | | Test framework | Standard `testing` package; `teatest` for TUI integration | | Go modules | One `internal/` per concern (config, state, probe, ssh, ui, exec, format) | @@ -47,8 +48,8 @@ Implementation work has not started. The next milestone is to: Targets in order of priority: 1. **Nix flake** (`nix run github:KofTwentyTwo/limen`) — primary distribution channel. -2. **Homebrew tap** at `KofTwentyTwo/homebrew-tap` (not yet created) — `brew install KofTwentyTwo/tap/limen`. -3. **GitHub Releases** with goreleaser-built binaries for darwin/linux × amd64/arm64. +2. **Homebrew tap** at `KofTwentyTwo/homebrew-tap` — `brew install KofTwentyTwo/tap/limen`. +3. **GitHub Releases** with direct Go-built binaries for macOS/Linux × amd64/arm64. Eventual goal: submit to `homebrew-core` once the tool stabilizes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca3e703..8a8287b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,6 +39,7 @@ Examples: - Go: standard `gofmt`. `golangci-lint` runs in CI (will be added once Go code lands). - Markdown: 100-column soft wrap; tables and code blocks may exceed. - Comments: explain *why*, not *what*. Code shows what. +- Local tool dependencies are listed in [`docs/DEV-TOOLS.md`](docs/DEV-TOOLS.md). ## Tests @@ -60,7 +61,12 @@ Examples: ## Releases -Tags trigger goreleaser via GitHub Actions, which cross-compiles for darwin/linux × amd64/arm64 and publishes binaries to the GitHub Releases page. See [`docs/DESIGN.md` §10.4](docs/DESIGN.md). +Tags trigger GitHub Actions, which runs the Go checks, builds macOS/Linux binaries for amd64/arm64, publishes GitHub Release assets, and updates the `KofTwentyTwo/homebrew-tap` formula. See [`docs/DESIGN.md` §10.4](docs/DESIGN.md). + +Release prerequisites: +- `KofTwentyTwo/homebrew-tap` must exist. +- The `limen` repository must have a `HOMEBREW_TAP_TOKEN` Actions secret with contents read/write access to `KofTwentyTwo/homebrew-tap`. +- The release tag must use the `vMAJOR.MINOR.PATCH` form, for example `v0.1.0`. ## Code of conduct diff --git a/README.md b/README.md index 4be55bc..51056c4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A terminal launcher TUI for tmux and SSH. The first thing you see when you open a new terminal: pick a host, pick a session, get to work. -**Status:** Design phase. No working binary yet — see [`docs/DESIGN.md`](docs/DESIGN.md) for the spec we're building toward. +**Status:** Early implementation. The binary can launch the host and session picker; see [`docs/DESIGN.md`](docs/DESIGN.md) for the canonical spec. --- @@ -38,11 +38,26 @@ Press `Esc` at any point and `limen` drops you into a fresh unnamed tmux session ## Install -Coming soon. The design targets these distribution channels: +Install with Nix: -- **Nix flake** — `nix run github:KofTwentyTwo/limen` -- **Homebrew tap** — `brew install KofTwentyTwo/tap/limen` (planned) -- **Binary releases** — GitHub Releases page (planned) +```bash +nix run github:KofTwentyTwo/limen +``` + +Install with Homebrew after the first tagged release: + +```bash +brew install KofTwentyTwo/tap/limen +``` + +Prebuilt binaries are published to the GitHub Releases page for: + +| Platform | Architecture | Binary | +|----------|--------------|--------| +| Linux | x64 | `limen-linux-amd64` | +| Linux | ARM64 | `limen-linux-arm64` | +| macOS | Intel | `limen-macos-amd64` | +| macOS | Apple Silicon | `limen-macos-arm64` | ## Configuration @@ -70,7 +85,7 @@ The full design and requirements document lives at [`docs/DESIGN.md`](docs/DESIG ## Status & roadmap -This repository currently contains only the design document and standard repo scaffolding. Implementation will land on the `develop` branch. +Implementation lands on the `develop` branch. Tagged releases publish prebuilt binaries and update the Homebrew tap. ## Contributing diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 85263f2..e95f3aa 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -616,7 +616,7 @@ limen/ ├── .github/ │ └── workflows/ │ ├── ci.yml # go test + go vet on push/PR -│ └── release.yml # tag → goreleaser → binaries + checksums +│ └── release.yml # tag -> Go cross-builds -> releases + Homebrew tap ├── docs/ │ ├── DESIGN.md # This document (or a slightly edited copy) │ └── README.md # User-facing docs @@ -626,7 +626,6 @@ limen/ ├── go.mod ├── go.sum ├── main.go -├── .goreleaser.yaml # Cross-compile darwin + linux, amd64 + arm64 ├── LICENSE # GPL-3.0 (matches KofTwentyTwo OSS standard) └── README.md # Repo top-level readme ``` @@ -679,7 +678,7 @@ home.packages = [ inputs.limen.packages.${system}.default ]; Path A — **personal tap (preferred for initial release):** -The user creates `github.com//homebrew-tap` with a `Formula/limen.rb` that fetches the GitHub release tarball and `go build`s, OR (better) fetches the pre-built binary published by goreleaser. Users install via `brew install /tap/limen`. +The user creates `github.com//homebrew-tap` with a `Formula/limen.rb` that fetches the pre-built binary published to GitHub Releases. Users install via `brew install /tap/limen`. Path B — **homebrew-core (later):** @@ -700,10 +699,10 @@ git push origin v0.1.0 GitHub Actions runs `release.yml`: 1. Checks out at the tag. -2. Runs `goreleaser release --clean`. -3. goreleaser cross-compiles for `darwin/amd64`, `darwin/arm64`, `linux/amd64`, `linux/arm64`. -4. Uploads release artifacts (tarballs + `checksums.txt`) to the GitHub release page. -5. (Optional) Updates the homebrew tap formula via a goreleaser hook. +2. Runs `go vet`, `go test -race -count=1 ./...`, and `go build ./...`. +3. Cross-compiles with CGO disabled for `darwin/amd64`, `darwin/arm64`, `linux/amd64`, `linux/arm64`. +4. Uploads release artifacts and `checksums.txt` to the GitHub release page. +5. Updates the `KofTwentyTwo/homebrew-tap` formula with release URLs and SHA256 hashes using `HOMEBREW_TAP_TOKEN`. --- diff --git a/docs/DEV-TOOLS.md b/docs/DEV-TOOLS.md new file mode 100644 index 0000000..ea7b1a7 --- /dev/null +++ b/docs/DEV-TOOLS.md @@ -0,0 +1,43 @@ +# Development Tools + +This repo is intentionally light on local tooling. The following packages cover +normal development, release verification, and runtime smoke testing. + +## Home Manager Package Set + +```nix +home.packages = with pkgs; [ + go_1_24 + gopls + gotools + + git + gh + ripgrep + + nix + actionlint + shellcheck + + ruby + tmux + openssh +]; +``` + +## Why Each Tool Is Here + +| Tool | Use | +|------|-----| +| `go_1_24` | Builds and tests the Go module declared in `go.mod`. | +| `gopls` | Go language-server support for editors. | +| `gotools` | Standard Go helper tools used by editors and local workflows. | +| `git` | Source control, branches, tags, and tap repo updates. | +| `gh` | GitHub issue, PR, repository, and release workflows. | +| `ripgrep` | Fast repo search while developing and reviewing. | +| `nix` | Flake checks, package builds, and downstream Nix integration testing. | +| `actionlint` | Static validation for GitHub Actions workflows. | +| `shellcheck` | Shell validation used directly and by `actionlint`. | +| `ruby` | Local syntax checks for the Homebrew formula. | +| `tmux` | Runtime dependency for local session creation and attachment. | +| `openssh` | Runtime dependency for remote host connections. | diff --git a/flake.nix b/flake.nix index 6a2cad9..3eca7e3 100644 --- a/flake.nix +++ b/flake.nix @@ -10,17 +10,19 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; + version = "0.1.0"; in { packages.default = pkgs.buildGoModule { pname = "limen"; - version = "0.1.0"; + inherit version; src = ./.; vendorHash = "sha256-zYQ6hMFBFDPNlViB7EmnxbXozardyYeHiUSiG4uS/9Y="; ldflags = [ "-s" "-w" + "-X main.version=${version}" ]; meta = { diff --git a/packaging/homebrew/limen.rb b/packaging/homebrew/limen.rb new file mode 100644 index 0000000..75e61fd --- /dev/null +++ b/packaging/homebrew/limen.rb @@ -0,0 +1,42 @@ +class Limen < Formula + desc "Terminal launcher TUI for tmux and SSH" + homepage "https://github.com/KofTwentyTwo/limen" + version "0.1.0" + license "GPL-3.0-only" + + depends_on "tmux" + + on_macos do + on_arm do + url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-macos-arm64" + sha256 "PLACEHOLDER_MACOS_ARM64_SHA256" + end + + on_intel do + url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-macos-amd64" + sha256 "PLACEHOLDER_MACOS_AMD64_SHA256" + end + end + + on_linux do + on_arm do + url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-linux-arm64" + sha256 "PLACEHOLDER_LINUX_ARM64_SHA256" + end + + on_intel do + url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-linux-amd64" + sha256 "PLACEHOLDER_LINUX_AMD64_SHA256" + end + end + + def install + binary = Dir["limen-*"].first + chmod 0755, binary + bin.install binary => "limen" + end + + test do + assert_match version.to_s, shell_output("#{bin}/limen --version") + end +end -- 2.54.0