feat(version): replace --version with 'version' command to print build information for uc and uncloudd

This commit is contained in:
Pasha Sviderski
2026-05-21 15:14:08 +10:00
parent 28c88e4e10
commit 2f159157cc
8 changed files with 194 additions and 17 deletions
+4
View File
@@ -0,0 +1,4 @@
_ _ ___
| | | |/ __|
| |_| | (__
\__,_|\___|
+4 -8
View File
@@ -8,7 +8,6 @@ import (
"os" "os"
"strings" "strings"
"charm.land/lipgloss/v2"
"github.com/psviderski/uncloud/cmd/uncloud/caddy" "github.com/psviderski/uncloud/cmd/uncloud/caddy"
cmdcontext "github.com/psviderski/uncloud/cmd/uncloud/context" cmdcontext "github.com/psviderski/uncloud/cmd/uncloud/context"
"github.com/psviderski/uncloud/cmd/uncloud/dns" "github.com/psviderski/uncloud/cmd/uncloud/dns"
@@ -19,6 +18,7 @@ import (
"github.com/psviderski/uncloud/cmd/uncloud/wg" "github.com/psviderski/uncloud/cmd/uncloud/wg"
"github.com/psviderski/uncloud/internal/cli" "github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/config" "github.com/psviderski/uncloud/internal/cli/config"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/fs" "github.com/psviderski/uncloud/internal/fs"
"github.com/psviderski/uncloud/internal/log" "github.com/psviderski/uncloud/internal/log"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
@@ -39,7 +39,6 @@ func main() {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "uc", Use: "uc",
Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.", Short: "A CLI tool for managing Uncloud resources such as machines, services, and volumes.",
Version: version.String(),
SilenceUsage: true, SilenceUsage: true,
SilenceErrors: true, SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
@@ -113,15 +112,11 @@ func main() {
defaultHelpFunc(c, args) defaultHelpFunc(c, args)
// Only show links for the root 'uc' command. // Only show links for the root 'uc' command.
if c.Name() == "uc" { if c.Name() == "uc" {
urlStyle := lipgloss.NewStyle().
Underline(true).
Foreground(lipgloss.Color("12")) // light blue
fmt.Fprintln(c.OutOrStdout()) fmt.Fprintln(c.OutOrStdout())
fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n", fmt.Fprintf(c.OutOrStdout(), "Learn more about Uncloud: %s\n",
urlStyle.Render("https://uncloud.run/docs")) tui.URLStyle.Render(version.DocsURL))
fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n", fmt.Fprintf(c.OutOrStdout(), "Join our Discord community: %s\n",
urlStyle.Render("https://uncloud.run/discord")) tui.URLStyle.Render(version.DiscordURL))
} }
}) })
@@ -151,6 +146,7 @@ func main() {
service.NewScaleCommand("service"), service.NewScaleCommand("service"),
service.NewStartCommand("service"), service.NewStartCommand("service"),
service.NewStopCommand("service"), service.NewStopCommand("service"),
NewVersionCommand(),
volume.NewRootCommand(), volume.NewRootCommand(),
wg.NewRootCommand(), wg.NewRootCommand(),
) )
+82
View File
@@ -0,0 +1,82 @@
package main
import (
"bytes"
_ "embed"
"fmt"
"strings"
"text/template"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/version"
"github.com/spf13/cobra"
)
//go:embed art.txt
var asciiArt string
// NewVersionCommand creates a new command to print the version and build information for the binary.
func NewVersionCommand() *cobra.Command {
var output string
cmd := &cobra.Command{
Use: "version",
Short: "Show version and build information.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
info := version.GetInfo()
w := cmd.OutOrStdout()
switch output {
case "":
fmt.Fprint(w, humanVersion(info))
case "json":
s, err := info.JSONString()
if err != nil {
return err
}
fmt.Fprintln(w, s)
default:
s, err := templateVersion(output, info)
if err != nil {
return err
}
fmt.Fprint(w, s)
}
return nil
},
}
cmd.Flags().StringVarP(&output, "output", "o", "",
"Output format: 'json' or a Go template (e.g. '{{.Version}}').\n"+
"Run with '-o json' to discover the field names available to the template.\n"+
"(default is human-readable)")
return cmd
}
func humanVersion(info version.Info) string {
var b strings.Builder
b.WriteString(asciiArt)
b.WriteString("\n")
b.WriteString(fmt.Sprintf("uc: Uncloud CLI tool for deploying apps and managing resources (%s)",
tui.URLStyle.Render(version.WebsiteURL)))
b.WriteString("\n\n")
b.WriteString(info.String())
return b.String()
}
// templateVersion renders the version info using the provided Go template.
func templateVersion(tmpl string, info version.Info) (string, error) {
t, err := template.New("version").Parse(tmpl)
if err != nil {
return "", fmt.Errorf("parse template: %w", err)
}
var buf bytes.Buffer
if err = t.Execute(&buf, info); err != nil {
return "", fmt.Errorf("execute template: %w", err)
}
return buf.String(), nil
}
+5
View File
@@ -0,0 +1,5 @@
_ _ _
_ _ _ __ ___| | ___ _ _ __| | __| |
| | | | '_ \ / __| |/ _ \| | | |/ _` |/ _` |
| |_| | | | | (__| | (_) | |_| | (_| | (_| |
\__,_|_| |_|\___|_|\___/ \__,_|\__,_|\__,_|
+4 -3
View File
@@ -7,6 +7,7 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/daemon" "github.com/psviderski/uncloud/internal/daemon"
"github.com/psviderski/uncloud/internal/log" "github.com/psviderski/uncloud/internal/log"
"github.com/psviderski/uncloud/internal/machine" "github.com/psviderski/uncloud/internal/machine"
@@ -24,7 +25,7 @@ func main() {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "uncloudd", Use: "uncloudd",
Short: "Uncloud machine daemon.", Short: "Uncloud machine daemon.",
Version: version.String(), Long: "Uncloud machine daemon.\n" + tui.URLStyle.Render(version.WebsiteURL),
SilenceUsage: true, SilenceUsage: true,
SilenceErrors: true, SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@@ -39,11 +40,11 @@ func main() {
}, },
} }
cmd.PersistentFlags().StringVarP(&dataDir, "data-dir", "d", machine.DefaultDataDir, cmd.PersistentFlags().StringVarP(&dataDir, "data-dir", "d", machine.DefaultDataDir,
"Directory for storing persistent machine state") "Directory for storing persistent machine state.")
_ = cmd.MarkFlagDirname("data-dir") _ = cmd.MarkFlagDirname("data-dir")
// Add dial-stdio subcommand.
cmd.AddCommand(newDialStdioCommand()) cmd.AddCommand(newDialStdioCommand())
cmd.AddCommand(newVersionCommand())
// ctx is canceled when the daemon command is interrupted. // ctx is canceled when the daemon command is interrupted.
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
+82
View File
@@ -0,0 +1,82 @@
package main
import (
"bytes"
_ "embed"
"fmt"
"strings"
"text/template"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/version"
"github.com/spf13/cobra"
)
//go:embed art.txt
var asciiArt string
// newVersionCommand creates a new command to print the version and build information for the binary.
func newVersionCommand() *cobra.Command {
var output string
cmd := &cobra.Command{
Use: "version",
Short: "Show version and build information.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
info := version.GetInfo()
w := cmd.OutOrStdout()
switch output {
case "":
fmt.Fprint(w, humanVersion(info))
case "json":
s, err := info.JSONString()
if err != nil {
return err
}
fmt.Fprintln(w, s)
default:
s, err := templateVersion(output, info)
if err != nil {
return err
}
fmt.Fprint(w, s)
}
return nil
},
}
cmd.Flags().StringVarP(&output, "output", "o", "",
"Output format: 'json' or a Go template (e.g., '{{.Version}}').\n"+
"Run with '-o json' to discover the field names available to the template.\n"+
"(default is human-readable)")
return cmd
}
func humanVersion(info version.Info) string {
var b strings.Builder
b.WriteString(asciiArt)
b.WriteString("\n")
b.WriteString(fmt.Sprintf("uncloudd: Uncloud machine daemon (%s)",
tui.URLStyle.Render(version.WebsiteURL)))
b.WriteString("\n\n")
b.WriteString(info.String())
return b.String()
}
// templateVersion renders the version info using the provided Go template.
func templateVersion(tmpl string, info version.Info) (string, error) {
t, err := template.New("version").Parse(tmpl)
if err != nil {
return "", fmt.Errorf("parse template: %w", err)
}
var buf bytes.Buffer
if err = t.Execute(&buf, info); err != nil {
return "", fmt.Errorf("execute template: %w", err)
}
return buf.String(), nil
}
+1
View File
@@ -19,6 +19,7 @@ var (
BoldYellow = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Yellow) BoldYellow = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Yellow)
NameStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("152")) NameStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("152"))
URLStyle = lipgloss.NewStyle().Underline(true).Foreground(lipgloss.BrightBlue)
) )
// FormatImage renders an image reference with the given style, using a faint colon separator for tagged images. // FormatImage renders an image reference with the given style, using a faint colon separator for tagged images.
+12 -6
View File
@@ -9,12 +9,11 @@ import (
goversion "github.com/caarlos0/go-version" goversion "github.com/caarlos0/go-version"
) )
func String() string { const (
if version == "" { WebsiteURL = "https://uncloud.run"
return "999.0.0-dev" DocsURL = "https://uncloud.run/docs"
} DiscordURL = "https://uncloud.run/discord"
return version )
}
// Build-time metadata injected via -ldflags by GoReleaser (see .goreleaser.yaml). // Build-time metadata injected via -ldflags by GoReleaser (see .goreleaser.yaml).
// These are package-level so the same paths can be referenced from any binary's ldflags. // These are package-level so the same paths can be referenced from any binary's ldflags.
@@ -26,6 +25,13 @@ var (
builtBy string builtBy string
) )
func String() string {
if version == "" {
return "999.0.0-dev"
}
return version
}
// Info holds version and build metadata for an Uncloud binary. // Info holds version and build metadata for an Uncloud binary.
type Info struct { type Info struct {
Version string Version string