1 Commits

Author SHA1 Message Date
Niklas Ye 79e77fadc6 Let the release skill drive this repo, and let make drive the pipeline
CI / test (push) Successful in 4s
Release / test (push) Successful in 4s
Release / binaries (push) Successful in 12s
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.
2026-09-24 08:38:02 +02:00
6 changed files with 99 additions and 77 deletions
+5 -31
View File
@@ -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
+9 -44
View File
@@ -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
View File
@@ -1,2 +1,3 @@
terdut-tui
.graymatter/
dist/
+9
View File
@@ -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
+19
View File
@@ -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+
+56 -2
View File
@@ -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'