diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..54cea59 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,53 @@ +# Builds cross-platform binaries and publishes a Gitea release when a version tag +# (e.g. v0.4.0) is pushed. Produces the same assets as `make release` and uploads +# them — so the skill installer (skills/emcli/scripts/install.sh) can fetch them. +# +# Requires: Gitea Actions enabled with a runner that has Go, make, curl, and jq +# (the actions/checkout + actions/setup-go steps need the instance's Actions proxy). +# This workflow has not been exercised against this repo's runners yet; if a step +# is unavailable on your runner, the same result comes from `make release && make +# publish` locally (see RELEASING.md). +name: release +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build release artifacts + run: make release VERSION="${GITHUB_REF_NAME}" + + - name: Create release and upload assets + env: + TOKEN: ${{ github.token }} + SERVER: ${{ github.server_url }} + REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + set -euo pipefail + # Create the release for the pushed tag. + id=$(curl -fsSL -X POST \ + -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${SERVER}/api/v1/repos/${REPO}/releases" \ + -d "{\"tag_name\":\"${TAG}\",\"name\":\"${TAG}\"}" | jq -r '.id') + echo "release id: ${id}" + # Upload every built asset (binaries + checksums.txt). + for f in dist/*; do + name=$(basename "$f") + echo "uploading ${name}" + curl -fsSL -X POST \ + -H "Authorization: token ${TOKEN}" \ + -F "attachment=@${f}" \ + "${SERVER}/api/v1/repos/${REPO}/releases/${id}/assets?name=${name}" >/dev/null + done + echo "published ${TAG}" diff --git a/Makefile b/Makefile index cf4499b..309cd91 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,52 @@ -BINARY := emcli -LDFLAGS := -s -w +BINARY := emcli +DIST := dist +VERPKG := git.dcglab.co.uk/steve/emcli/internal/version + +# VERSION defaults to the current git tag/description; override for a release: +# make release VERSION=v0.4.0 +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo v0.0.0-dev) +VER_NO_V := $(patsubst v%,%,$(VERSION)) + +LDFLAGS := -s -w -X $(VERPKG).String=$(VERSION) + +# Platforms to cross-compile for a release. Must match skills/emcli/scripts/install.sh. +PLATFORMS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 + +.PHONY: build test vet release clean-dist publish -.PHONY: build test vet build: - CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o $(BINARY) ./cmd/emcli + CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY) ./cmd/emcli test: go test ./... vet: go vet ./... + +# release cross-compiles a static binary per platform into dist/, named +# emcli___[.exe], plus a checksums.txt of all assets. +release: clean-dist + @mkdir -p $(DIST) + @for p in $(PLATFORMS); do \ + os=$${p%/*}; arch=$${p#*/}; ext=; \ + [ "$$os" = windows ] && ext=.exe; \ + out=$(DIST)/$(BINARY)_$(VER_NO_V)_$${os}_$${arch}$${ext}; \ + echo " building $$out"; \ + CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \ + go build -trimpath -ldflags "$(LDFLAGS)" -o $$out ./cmd/emcli || exit 1; \ + done + @cd $(DIST) && { command -v sha256sum >/dev/null 2>&1 \ + && sha256sum $(BINARY)_* > checksums.txt \ + || shasum -a 256 $(BINARY)_* > checksums.txt; } + @echo "release $(VERSION) artifacts in $(DIST)/:" + @ls -1 $(DIST) + +clean-dist: + @rm -rf $(DIST) + +# publish creates the Gitea release for $(VERSION) and uploads every dist asset. +# Run `make release VERSION=vX.Y.Z` first. Requires the `tea` CLI to be logged in. +publish: + @test -d $(DIST) || { echo "no $(DIST)/ — run 'make release VERSION=$(VERSION)' first"; exit 1; } + tea releases create --repo steve/emcli --tag $(VERSION) --title $(VERSION) \ + $(foreach a,$(wildcard $(DIST)/*),--asset $(a)) diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..72b32d4 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,55 @@ +# Releasing emcli + +A release publishes one static binary per platform plus a `checksums.txt`, named so the agent skill +installer (`skills/emcli/scripts/install.sh`) can fetch them: + +``` +emcli___[.exe] e.g. emcli_0.4.0_linux_amd64 +checksums.txt sha256, one " " line per asset +``` + +Platforms: `linux/amd64`, `linux/arm64`, `darwin/amd64`, `darwin/arm64`, `windows/amd64`. The binary +is CGO-free, so cross-compilation needs only the Go toolchain. + +## Option A — local (Makefile + tea) + +```bash +# 1. Build the artifacts into dist/ (version is injected into `emcli version`) +make release VERSION=v0.4.0 + +# 2. Create the Gitea release for the tag and upload every dist/ asset +make publish VERSION=v0.4.0 +``` + +`make publish` uses the `tea` CLI (already configured for this repo). It runs: + +``` +tea releases create --repo steve/emcli --tag v0.4.0 --title v0.4.0 --asset dist/ +``` + +Inspect `dist/` and `dist/checksums.txt` before publishing if you want to double-check. + +## Option B — CI (Gitea Actions) + +Push a version tag and let `.gitea/workflows/release.yml` build and publish: + +```bash +git tag v0.4.0 +git push origin v0.4.0 # (push via the tokenized HTTPS URL this repo uses) +``` + +The workflow runs `make release` and uploads the assets to the release via the Gitea API. It needs +Gitea Actions enabled with a runner that provides Go, make, curl, and jq. It hasn't been exercised +against this repo's runners yet — if it doesn't fit your runner setup, fall back to Option A. + +## After a release + +The skill installer defaults to `EMCLI_VERSION=v0.4.0`. When you cut a different version, either +publish under that tag or update the default in `skills/emcli/scripts/install.sh` (and the note in +`skills/emcli/references/install.md`). + +## Versioning + +`VERSION` is injected at build time into `internal/version.String` via `-ldflags -X`, so +`emcli version` prints the released tag. Without an explicit `VERSION`, the Makefile derives one from +`git describe`.