feat: publish releases with this repository's own SDK code
There is no CI: the instance has no act_runner and none is planned, so releases are cut by hand. That makes `make check` the only thing standing between a mistake and the tracker, and it is one command: gofmt, vet, the suite with the cache defeated, `go mod verify`, a vendored build, and `kettle gen skills --check`. The last one is the invariant worth having — the plugin's SKILL.md command reference is generated from the binary's registry, so a flag that changed cannot ship with documentation that recommends the old one. `cli/cmd/release` publishes to Gitea using the same SDK the binary already vendors, which is a pleasing thing to be able to say: nothing third-party handles the artifacts. It is a second binary rather than a `kettle` subcommand on purpose — `kettle`'s command tree is what generates the plugin's skills, so a verb there ships to every operator, and publishing a release is build infrastructure. It is idempotent end to end: an existing release for the tag is reused, an asset of the same name is replaced rather than doubled, and a retried run converges instead of duplicating. `make release` refuses three things, each with its own message: a dirty working tree, a TAG that is not what `git describe` reports, and a tag the remote does not have. A release built from uncommitted code is unreproducible and nobody finds out until they need to reproduce it. `kettle version` reports the stamp, the toolchain and the VCS revision. The default is `dev`, and a hand build says so and means it — a binary out of somebody's working tree is not a release and must not claim to be one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// Version is what this binary was built as. It is stamped at link time and
|
||||
// defaults to something honest.
|
||||
//
|
||||
// "dev" is the truth for a build from a working tree: a binary somebody built
|
||||
// out of a checkout is not a release and must not claim to be one. A release
|
||||
// build says otherwise by naming this variable:
|
||||
//
|
||||
// go build -ldflags "-X git.noodles.cam/claude-skills/marketplace/cli/internal/cmd.Version=v1.2.3" ./cmd/kettle
|
||||
//
|
||||
// which is what `make build`, `make dist` and `make release` do. The path is
|
||||
// exercised by a test that builds with the flag and reads the answer back,
|
||||
// because a -X whose symbol path is one character wrong is silently ignored and
|
||||
// the binary goes on reporting "dev".
|
||||
var Version = "dev"
|
||||
|
||||
func init() {
|
||||
register(&Command{
|
||||
Name: "version",
|
||||
Group: GroupProject,
|
||||
Short: "print the version this binary was built as",
|
||||
Long: `A binary that cannot say what it is, is a support problem: an operator with an
|
||||
old copy on PATH and a skill written against a newer one has no way to see the
|
||||
mismatch, and neither does anybody reading their transcript.
|
||||
|
||||
The version is stamped at link time. A build from source says "dev" and means
|
||||
it — that is not a placeholder to be edited, it is the answer for a binary that
|
||||
came out of somebody's working tree rather than off a tag.
|
||||
|
||||
The commit is reported when the build recorded one, which ` + "`go build`" + ` does from
|
||||
git and a build from an unpacked tarball cannot. A tree with uncommitted
|
||||
changes in it says so beside the commit.`,
|
||||
Examples: []Example{
|
||||
{"kettle version", "the version, the toolchain and the commit"},
|
||||
{"kettle version --short", "just the version, for a script"},
|
||||
},
|
||||
Setup: func(fs *flag.FlagSet) func([]string) error {
|
||||
short := fs.Bool("short", false, "print the version alone, with nothing around it")
|
||||
|
||||
return func(args []string) error {
|
||||
if len(args) > 0 {
|
||||
return Fail("version takes no arguments")
|
||||
}
|
||||
if *short {
|
||||
fmt.Println(Version)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("kettle %s\n", Version)
|
||||
fmt.Printf("built %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
|
||||
if rev := revision(); rev != "" {
|
||||
fmt.Printf("commit %s\n", rev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// revision is the commit this binary was built from, or "" when the build
|
||||
// recorded none.
|
||||
//
|
||||
// `go build` stamps it out of git; a build from an unpacked tarball has no
|
||||
// repository to ask, and there is nothing to report rather than something to
|
||||
// invent. A dirty tree is named as one: the commit is then a lower bound on
|
||||
// what is in the binary and not a description of it.
|
||||
func revision() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
var rev string
|
||||
var dirty bool
|
||||
for _, s := range info.Settings {
|
||||
switch s.Key {
|
||||
case "vcs.revision":
|
||||
rev = s.Value
|
||||
case "vcs.modified":
|
||||
dirty = s.Value == "true"
|
||||
}
|
||||
}
|
||||
if rev != "" && dirty {
|
||||
rev += " (with uncommitted changes)"
|
||||
}
|
||||
return rev
|
||||
}
|
||||
Reference in New Issue
Block a user