diff --git a/README.md b/README.md index c95cfc8..2af4752 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ by `vyb`. | `init` | Create `.vyb/metadata.yaml` in the project root | | `update` | Re-scan workspace, merge & (re)generate annotations | | `remove` | Delete `.vyb` completely | +| `version` | Print binary version | | `code` | Implement `TODO(vyb)`s or the file passed as argument | | `document` | Generate / refresh `README.md` files | | `refine` | Polish `SPEC.md` content | diff --git a/cmd/README.md b/cmd/README.md index 3357e36..9d0e9d9 100644 --- a/cmd/README.md +++ b/cmd/README.md @@ -9,6 +9,8 @@ subcommands, implemented using the Cobra library. metadata (metadata.yaml). - remove: Deletes all .vyb metadata from the current project root (or forcibly from the entire directory hierarchy using --force-root). -- root: The root command that prints help if no subcommand is specified. -- template: Registers specialized commands for AI-based tasks such - as 'refine', 'code', 'summarize', etc. +- update: Updates the vyb project metadata. +- version: Prints the vyb CLI version. +- template-based commands: A dynamic set of commands for AI-based tasks + such as 'refine', 'code', 'document', etc., are registered from `.vyb` + template files. diff --git a/cmd/root.go b/cmd/root.go index 9d65687..ab7339a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,4 +34,5 @@ func init() { rootCmd.AddCommand(initCmd) rootCmd.AddCommand(updateCmd) rootCmd.AddCommand(removeCmd) + rootCmd.AddCommand(versionCmd) } diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..5fce283 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,74 @@ +package cmd + +import ( + "fmt" + "os" + "runtime/debug" + "strings" + "time" + + "github.com/spf13/cobra" +) + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Prints the vyb CLI version.", + Run: Version, +} + +// Version is the cobra handler for `vyb version`. +func Version(_ *cobra.Command, _ []string) { + if version, err := deriveVersion(); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "%q", err) + } else { + fmt.Println(version) + } + +} + +func deriveVersion() (string, error) { + info, ok := debug.ReadBuildInfo() + if !ok { + return "", fmt.Errorf("could not read build info") + } + + if info.Main.Version != "" && info.Main.Version != "(devel)" { + return info.Main.Version, nil + } + + return derivePseudoVersionFromVCS(info) +} + +// derivePseudoVersionFromVCS produces a pseudo version based on VCS tags, +// as described at https://go.dev/ref/mod#pseudo-versions +func derivePseudoVersionFromVCS(info *debug.BuildInfo) (string, error) { + var revision, at string + for _, s := range info.Settings { + if s.Key == "vcs.revision" { + revision = s.Value + } + if s.Key == "vcs.time" { + at = s.Value + } + } + + if revision == "" && at == "" { + return "", fmt.Errorf("version information is not available") + } + + buf := strings.Builder{} + buf.WriteString("0.0.0") + if revision != "" { + buf.WriteString("-") + buf.WriteString(revision[:12]) + } + if at != "" { + // the commit time is of the form 2023-01-25T19:57:54Z + p, err := time.Parse(time.RFC3339, at) + if err == nil { + buf.WriteString("-") + buf.WriteString(p.Format("20060102150405")) + } + } + return buf.String(), nil +}