▍ humdrum codex / custard v0.3.0
license AGPL-3.0

feat(cli): release verb — checks → tag → push (auto-publishes tap)

488700e0b0b09fc3460a3650e15800ce126c505a
humdrum <me@humdrum.me> · 2026-06-18 07:59

parent 35016a1c

feat(cli): release verb — checks → tag → push (auto-publishes tap)

custard release vX.Y.Z: validates semver, requires HEAD pushed, runs checks on
the HEAD export, then tags + pushes the tag so the TASK-006 webhook publishes
the new formula. Completes the verb set (check/preview/promote/release).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

2 files changed

cmd/custard/main.go +1 −2
@@ -30,8 +30,7 @@ 		cli.Preview(os.Args[2:])
 	case "promote":
 		cli.Promote(os.Args[2:])
 	case "release":
-		fmt.Fprintln(os.Stderr, "custard release: not implemented yet")
-		os.Exit(1)
+		cli.Release(os.Args[2:])
 	case "-h", "--help", "help":
 		usage()
 	default:
internal/cli/verbs.go +24 −0
@@ -85,6 +85,30 @@ 	step("promoting to production", func() (string, error) { return sh(".", cmd) })
 	fmt.Println(okStyle.Render("✓ promoted to production"))
 }
 
+// Release runs checks against HEAD, then tags and pushes — the Soft Serve
+// webhook auto-publishes the new version to the Homebrew tap.
+func Release(args []string) {
+	if len(args) == 0 {
+		fail("usage: custard release vX.Y.Z")
+	}
+	ver := args[0]
+	if !release.IsReleaseTag(ver) {
+		fail("version must be semver vX.Y.Z (got %q)", ver)
+	}
+	cfg := mustLoad()
+	requirePushed()
+	fmt.Println(headStyle.Render("▍ release " + ver))
+
+	dir := exportHEAD()
+	defer os.RemoveAll(dir)
+	runChecks(cfg.CI, dir)
+
+	step("tagging "+ver, func() (string, error) { return sh(".", "git tag -a "+ver+" -m "+ver) })
+	step("pushing tag", func() (string, error) { return sh(".", "git push origin "+ver) })
+	fmt.Println(okStyle.Render("✓ released " + ver))
+	fmt.Println(dimStyle.Render("  tag pushed — the tap updates automatically (brew upgrade shortly)"))
+}
+
 // --- helpers ---
 
 func mustLoad() release.RepoConfig {