# The gate, and the release, for a repository with no CI.
#
# There is no act_runner on the instance this lives on and none is planned, so
# NOTHING RUNS ON A PUSH. `make check` is the only thing standing between a
# mistake and the tracker, and it is on whoever is committing to run it — the
# same five steps a workflow would have run, in one command, exiting non-zero
# the moment one of them fails.
#
# `make release` is the other half: a release is cut by hand, from a developer's
# machine, by this module's own code (cmd/release) talking to Gitea's API. It
# refuses to publish a dirty tree or a tag that is not the version it is about
# to stamp into the binaries, because a release built from uncommitted code is
# unreproducible and nobody finds out until they need to reproduce it.

SHELL := /bin/sh

# Sequential on purpose: `check` builds the binary and then asks it whether the
# plugin's documentation still matches, and -j would let the second start first.
.NOTPARALLEL:

MODULE := git.noodles.cam/claude-skills/marketplace/cli
SKILLS := ../plugins/kettle/skills
DIST   := dist
BIN    := $(DIST)/kettle
REMOTE ?= origin

# Where `make install` puts the binary. The plugin's skills expect `kettle` on
# PATH and say so when it is not; override for a Go-style layout:
#   make install BINDIR=$(go env GOPATH)/bin
BINDIR ?= $(HOME)/.local/bin

# VERSION is what a binary reports for `kettle version`. It is derived from git
# rather than kept in a file: a number somebody has to remember to bump is a
# number that will be wrong. A tree that is not on a tag says so, and a dirty
# tree says that too — "v0.2.0-4-g1a2b3c4-dirty" is the honest answer, and it is
# exactly what you want to see in a bug report.
VERSION ?= $(shell git describe --tags --dirty --always 2>/dev/null || echo dev)
LDFLAGS := -X $(MODULE)/internal/cmd.Version=$(VERSION)

PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm64

.PHONY: help check fmt vet test verify build gen-check dist release install clean

help:
	@echo 'kettle — there is no CI here; these are what a person runs.'
	@echo
	@echo '  make check                     the gate: fmt, vet, test, modules, build, docs'
	@echo '  make build                     dist/kettle, version stamped'
	@echo '  make install [BINDIR=…]        build straight onto your PATH'
	@echo '  make dist                      cross-compile every platform + SHA256SUMS'
	@echo '  make release TAG=v1.2.3 [NOTES=notes.md] [TITLE="…"]'
	@echo '                                 dist, then publish it to Gitea'
	@echo '  make clean                     remove dist/'
	@echo
	@echo "  version  $(VERSION)"
	@echo "  bindir   $(BINDIR)"

# ---------------------------------------------------------------------------
# the gate
# ---------------------------------------------------------------------------

check: fmt vet test verify build gen-check
	@echo 'check    all clear — $(VERSION)'

# gofmt reports rather than rewrites: a formatting change made silently by a
# build is a change nobody reviewed. cmd and internal, never the module cache.
fmt:
	@out=`gofmt -l cmd internal`; \
	if [ -n "$$out" ]; then \
		echo 'gofmt    these files are not formatted:'; \
		echo "$$out" | sed 's/^/         /'; \
		echo '         run: gofmt -w cmd internal'; \
		exit 1; \
	fi; \
	echo 'gofmt    clean'

vet:
	@echo 'vet      go vet ./...'
	@go vet ./...

# -count=1 defeats the test cache. A gate that can pass because it passed an
# hour ago on different code is not a gate.
test:
	@echo 'test     go test -count=1 ./...'
	@go test -count=1 ./...

# Two checks, because they answer different questions. `go mod verify` says the
# module cache matches go.sum; the vendored build says the committed vendor/ is
# complete and is what actually compiles. A vendor/ that has drifted from go.mod
# fails nothing until somebody builds on a machine with a cold cache.
verify:
	@echo 'modules  go mod verify'
	@go mod verify
	@echo 'vendor   go build -mod=vendor ./...'
	@go build -mod=vendor ./...

build:
	@mkdir -p $(DIST)
	@go build -trimpath -ldflags '$(LDFLAGS)' -o $(BIN) ./cmd/kettle
	@echo 'build    $(BIN) — $(VERSION)'

# The documentation invariant: everything the plugin's SKILL.md files say about
# a kettle command is generated from the registry the binary is built from, and
# this exits 1 the moment the two disagree. Run `kettle gen skills --out …`
# without --check to fix it.
gen-check: build
	@echo 'docs     gen skills --check'
	@$(BIN) gen skills --out $(SKILLS) --check

# ---------------------------------------------------------------------------
# building for other people
# ---------------------------------------------------------------------------

install:
	@mkdir -p $(BINDIR)
	@go build -trimpath -ldflags '$(LDFLAGS)' -o $(BINDIR)/kettle ./cmd/kettle
	@echo 'installed $(BINDIR)/kettle — $(VERSION)'
	@case ":$$PATH:" in \
		*":$(BINDIR):"*) ;; \
		*) echo "note: $(BINDIR) is not on your PATH, so the plugin's skills will not find it" ;; \
	esac

# CGO_ENABLED=0 because these binaries are downloaded by people whose machines
# are not this one: a build that links against the host's libc is a build that
# runs on the host.
dist:
	@rm -f $(DIST)/kettle_* $(DIST)/SHA256SUMS
	@mkdir -p $(DIST)
	@for p in $(PLATFORMS); do \
		os=$${p%/*}; arch=$${p#*/}; \
		out=$(DIST)/kettle_$(VERSION)_$${os}_$${arch}; \
		CGO_ENABLED=0 GOOS=$$os GOARCH=$$arch \
			go build -trimpath -ldflags '$(LDFLAGS)' -o $$out ./cmd/kettle || exit 1; \
		echo "dist     $$out"; \
	done
	@cd $(DIST) && \
		if command -v sha256sum >/dev/null 2>&1; then \
			sha256sum kettle_$(VERSION)_* > SHA256SUMS; \
		else \
			shasum -a 256 kettle_$(VERSION)_* > SHA256SUMS; \
		fi
	@echo 'dist     $(DIST)/SHA256SUMS'

# ---------------------------------------------------------------------------
# cutting one
# ---------------------------------------------------------------------------

# Published by cmd/release, which is this module's own code against Gitea's own
# API — no third-party action, nothing between the tag and what people download
# that is not in this repository.
#
# The three refusals are the point of doing it here rather than by hand:
#
#   dirty tree   what shipped would not be what is committed, and nobody could
#                rebuild it;
#   wrong tag    TAG must be the version `git describe` reports, which is only
#                true when the tag exists and HEAD is standing on it — so this
#                also catches "I forgot to tag" and "I tagged, then committed";
#   unpushed tag a release naming a tag the server does not have is a download
#                page pointing at a commit nobody else can fetch.
release:
	@test -n '$(TAG)' || { echo 'usage: make release TAG=v1.2.3 [NOTES=notes.md] [TITLE="…"]'; exit 2; }
	@if [ -n "`git status --porcelain`" ]; then \
		echo 'refusing: the working tree is dirty — a release built from uncommitted code cannot be rebuilt'; \
		git status --short | sed 's/^/          /'; \
		exit 1; \
	fi
	@if [ '$(VERSION)' != '$(TAG)' ]; then \
		echo 'refusing: TAG is $(TAG) but this commit describes as $(VERSION)'; \
		echo '          the binaries would be stamped $(VERSION) and the release would claim $(TAG).'; \
		echo '          tag this commit first:  git tag -a $(TAG) -m $(TAG)'; \
		exit 1; \
	fi
	@if ! git ls-remote --exit-code --tags $(REMOTE) 'refs/tags/$(TAG)' >/dev/null 2>&1; then \
		echo 'refusing: $(TAG) is not on $(REMOTE) — push it first:  git push $(REMOTE) $(TAG)'; \
		echo '          (another remote? make release TAG=$(TAG) REMOTE=…)'; \
		exit 1; \
	fi
	@$(MAKE) dist
	@echo 'release  publishing $(TAG) with cmd/release'
	@go run ./cmd/release --tag '$(TAG)' \
		$(if $(TITLE),--title '$(TITLE)') \
		$(if $(NOTES),--notes-file '$(NOTES)') \
		$(DIST)/kettle_$(VERSION)_* $(DIST)/SHA256SUMS

clean:
	@rm -rf $(DIST)
	@echo 'clean    $(DIST) removed'
