Let the release skill drive this repo, and let make drive the pipeline
The release skill only knew repos that deploy an image through a wrapper chart. terdut-tui publishes binaries to a Gitea release and nothing else, so its first two releases were cut by hand. It now has a .release.conf saying KIND=binary, which the skill treats as gate, tag, wait for the pipeline, then check what was published. The gate had to exist as make targets for that: fmt, lint and test, the same three the other repos have. ci.yaml and release.yaml now call them instead of carrying their own copy of gofmt, vet and the tests, so a green gate locally and a green pipeline are the same code and cannot drift. The gofmt handling moved over as written, including the comment on why both of its failure modes need catching; both fail the target, checked with a misformatted file and an unparseable one. The binaries job calls make dist too. DIST_TARGETS is now the one place that says what a release contains, and dist-assets prints the names dist builds so the skill can verify the published release against a list instead of a count. The names are unchanged, and they are the self-updater's contract with every installed binary: internal/updater matches terdut-tui-<tag>-<goos>-<goarch> exactly. CLAUDE.md gains a Release section, including that the annotated tag's message is what appears on the release page. Not run in the pipeline yet: make is in the golang image, as terdut-server's CI relies on, but this repo's workflows only exercise it on the push that carries this commit, and make dist only on the next tag. A failure in the release workflow's test job stops the publish rather than shipping something unchecked.
This commit is contained in:
@@ -56,34 +56,8 @@ jobs:
|
||||
git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
|
||||
fi
|
||||
|
||||
# This exists because `go vet` does not look at import order: the move to
|
||||
# git.ryuvia.com rewrote every import path without re-sorting, the new path sorts
|
||||
# before github.com/..., and both repos sat unformatted through a green CI run and
|
||||
# a release before anyone noticed.
|
||||
#
|
||||
# Both of gofmt's failure modes need handling, and they are not alike. A file that
|
||||
# is merely misformatted is listed on stdout with exit 0 -- so the failure has to
|
||||
# be raised by hand. A file that does not parse is the opposite: nothing on stdout
|
||||
# and exit 2, which a naive `[ -n "$unformatted" ]` reads as success. The first
|
||||
# draft of this step had exactly that hole.
|
||||
- name: Format
|
||||
run: |
|
||||
if ! unformatted=$(gofmt -l .); then
|
||||
echo "::error::gofmt could not parse the tree"
|
||||
gofmt -l . # re-run unredirected so the parse errors reach the log
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "::error::not gofmt'd:"
|
||||
echo "$unformatted"
|
||||
gofmt -d .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
# Covers the API client against a stub server, the Update state machine, and View
|
||||
# rendering -- all three are pure enough to test without a terminal.
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
# The Makefile is the single definition of the gate -- gofmt with both of its failure
|
||||
# modes handled, go vet, and the tests -- so this is exactly what a developer and the
|
||||
# release skill run. See the comments on the targets for why each is shaped as it is.
|
||||
- name: Format, vet and test
|
||||
run: make fmt lint test
|
||||
|
||||
@@ -8,9 +8,9 @@ name: Release
|
||||
# the job that publishes them. Nothing is handed between jobs.
|
||||
#
|
||||
# The asset names matter beyond being tidy: internal/updater looks for exactly
|
||||
# terdut-tui-<tag>-<goos>-<goarch> in the latest release and reports every available name
|
||||
# when it cannot find one. Renaming the pattern here breaks self-update for every
|
||||
# installed binary.
|
||||
# terdut-tui-<tag>-<goos>-<goarch> in the latest release. The pattern is defined once, by
|
||||
# `make dist` (and `make dist-assets`, which the release skill checks the published release
|
||||
# against) -- see DIST_TARGETS in the Makefile before touching it.
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -41,35 +41,9 @@ jobs:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
|
||||
|
||||
# This exists because `go vet` does not look at import order: the move to
|
||||
# git.ryuvia.com rewrote every import path without re-sorting, the new path sorts
|
||||
# before github.com/..., and both repos sat unformatted through a green CI run and
|
||||
# a release before anyone noticed.
|
||||
#
|
||||
# Both of gofmt's failure modes need handling, and they are not alike. A file that
|
||||
# is merely misformatted is listed on stdout with exit 0 -- so the failure has to
|
||||
# be raised by hand. A file that does not parse is the opposite: nothing on stdout
|
||||
# and exit 2, which a naive `[ -n "$unformatted" ]` reads as success. The first
|
||||
# draft of this step had exactly that hole.
|
||||
- name: Format
|
||||
run: |
|
||||
if ! unformatted=$(gofmt -l .); then
|
||||
echo "::error::gofmt could not parse the tree"
|
||||
gofmt -l . # re-run unredirected so the parse errors reach the log
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$unformatted" ]; then
|
||||
echo "::error::not gofmt'd:"
|
||||
echo "$unformatted"
|
||||
gofmt -d .
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
# Same target CI and the release skill run; a tag that fails it publishes nothing.
|
||||
- name: Format, vet and test
|
||||
run: make fmt lint test
|
||||
|
||||
binaries:
|
||||
needs: test
|
||||
@@ -86,21 +60,12 @@ jobs:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
|
||||
|
||||
# The Makefile owns the target list and the asset names -- see DIST_TARGETS there for
|
||||
# why the naming pattern cannot change.
|
||||
- name: Build every target
|
||||
env:
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -eu
|
||||
mkdir -p dist
|
||||
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do
|
||||
GOOS="${target%/*}"
|
||||
GOARCH="${target#*/}"
|
||||
out="dist/terdut-tui-${REF_NAME}-${GOOS}-${GOARCH}"
|
||||
echo "building $out"
|
||||
GOOS="$GOOS" GOARCH="$GOARCH" go build \
|
||||
-ldflags "-X main.version=${REF_NAME}" \
|
||||
-o "$out" .
|
||||
done
|
||||
run: make dist VERSION="$REF_NAME"
|
||||
|
||||
# Creating the release is made idempotent rather than assumed-new: a re-run of a
|
||||
# failed release must not die on the release that already exists. Assets are
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
terdut-tui
|
||||
.graymatter/
|
||||
dist/
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Read by the `release` skill (~/.claude/skills/release).
|
||||
#
|
||||
# terdut-tui publishes binaries to a Gitea release and nothing else: no image, no Helm
|
||||
# chart, no wrapper in Ryuvia/charts. KIND=binary tells the skill to gate, tag, wait for
|
||||
# release.yaml and verify the published assets, and to skip the chart steps.
|
||||
KIND=binary
|
||||
|
||||
# English, like the rest of the terdut projects.
|
||||
PROSE_LANG=en
|
||||
@@ -23,6 +23,25 @@ All user actions target incidents. Two server behaviours the UI has to respect:
|
||||
manual resolve is **terminal** (hence the confirmation prompt), and snooze is the
|
||||
non-destructive "not now" alternative.
|
||||
|
||||
## Release
|
||||
|
||||
Say **"Release"** (or "Release X.Y.Z") and the `release` skill runs it. This repo is
|
||||
`KIND=binary` in `.release.conf`: it publishes binaries to a Gitea release and has no image,
|
||||
chart or wrapper-chart PR. The run is gate, commit, push, tag, wait for `release.yaml`, then
|
||||
`verify-release`. Preconditions and the plan, without side effects:
|
||||
|
||||
```sh
|
||||
~/.claude/skills/release/scripts/release-preflight # state + suggested version
|
||||
~/.claude/skills/release/scripts/release-preflight vX.Y.Z # validate that release
|
||||
```
|
||||
|
||||
- `make fmt lint test` is the gate, and it **is** what `ci.yaml` and `release.yaml` run.
|
||||
- `make dist VERSION=vX.Y.Z` builds the four binaries; `make dist-assets` lists their names.
|
||||
The pattern `terdut-tui-<tag>-<goos>-<goarch>` is the self-updater's contract with every
|
||||
installed binary, so changing it breaks self-update.
|
||||
- **The annotated tag's message becomes the release notes** (`release.yaml` copies it, minus
|
||||
its subject line). Write it for a reader of the release page. Never move a published tag.
|
||||
|
||||
## Tech stack
|
||||
|
||||
- Go 1.25+
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
VERSION := $(shell git describe --tags --always --dirty)
|
||||
|
||||
.PHONY: build install test
|
||||
.PHONY: build install test lint fmt dist dist-assets release-vars
|
||||
|
||||
build:
|
||||
go build -ldflags "-X main.version=$(VERSION)" -o terdut-tui .
|
||||
@@ -8,5 +8,59 @@ build:
|
||||
install:
|
||||
go install -ldflags "-X main.version=$(VERSION)" .
|
||||
|
||||
test:
|
||||
# ci.yaml and release.yaml run `make fmt lint test`, so a green gate here and a green
|
||||
# pipeline are the same code rather than two descriptions of it. It is also what the
|
||||
# release skill runs before tagging.
|
||||
|
||||
# Covers the API client against a stub server, the sign-in flow, the Update state
|
||||
# machine, and View rendering -- all pure enough to test without a terminal.
|
||||
test: ## Run the test suite
|
||||
go test ./...
|
||||
|
||||
lint: ## go vet
|
||||
go vet ./...
|
||||
|
||||
# This exists because `go vet` does not look at import order: the move to
|
||||
# git.ryuvia.com rewrote every import path without re-sorting, the new path sorts
|
||||
# before github.com/..., and both repos sat unformatted through a green CI run and
|
||||
# a release before anyone noticed.
|
||||
#
|
||||
# Both of gofmt's failure modes need handling, and they are not alike. A file that
|
||||
# is merely misformatted is listed on stdout with exit 0 -- so the failure has to
|
||||
# be raised by hand. A file that does not parse is the opposite: nothing on stdout
|
||||
# and exit 2, which a naive `[ -n "$$unformatted" ]` reads as success. The first
|
||||
# draft of this target had exactly that hole.
|
||||
fmt: ## Fail on files that are not gofmt'd
|
||||
@if ! unformatted=$$(gofmt -l .); then \
|
||||
echo "gofmt could not parse the tree:"; gofmt -l .; exit 1; \
|
||||
fi; \
|
||||
if [ -n "$$unformatted" ]; then \
|
||||
echo "not gofmt'd:"; echo "$$unformatted"; gofmt -d .; exit 1; \
|
||||
fi
|
||||
|
||||
# What a release publishes. The asset names matter beyond being tidy: internal/updater
|
||||
# looks for exactly terdut-tui-<tag>-<goos>-<goarch> in the latest release and reports
|
||||
# every available name when it cannot find one. Renaming the pattern here breaks
|
||||
# self-update for every installed binary.
|
||||
DIST_TARGETS := linux/amd64 linux/arm64 darwin/amd64 darwin/arm64
|
||||
|
||||
dist: ## Build every release binary into dist/ (VERSION=vX.Y.Z to name them)
|
||||
@set -eu; mkdir -p dist; \
|
||||
for target in $(DIST_TARGETS); do \
|
||||
goos="$${target%/*}"; goarch="$${target#*/}"; \
|
||||
out="dist/terdut-tui-$(VERSION)-$$goos-$$goarch"; \
|
||||
echo "building $$out"; \
|
||||
GOOS="$$goos" GOARCH="$$goarch" go build -ldflags "-X main.version=$(VERSION)" -o "$$out" .; \
|
||||
done
|
||||
|
||||
# The names dist produces, one per line, so the release skill can check the published
|
||||
# release has every one of them rather than a count.
|
||||
dist-assets: ## Print the asset names a release of VERSION carries
|
||||
@for target in $(DIST_TARGETS); do \
|
||||
echo "terdut-tui-$(VERSION)-$${target%/*}-$${target#*/}"; \
|
||||
done
|
||||
|
||||
# Read by the release skill for a repo that publishes binaries and no image or chart
|
||||
# (KIND=binary in .release.conf). There is nothing to say about images or charts.
|
||||
release-vars: ## Print the variables the release process reads
|
||||
@printf 'APP=terdut-tui\n'
|
||||
|
||||
Reference in New Issue
Block a user