chore: import upstream snapshot with attribution
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
semver "github.com/zzet/gortex/internal/version"
|
||||
)
|
||||
|
||||
var versionShort bool
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print version information (SemVer + commit + build metadata)",
|
||||
Long: `Prints the running build's SemVer identity in canonical form:
|
||||
|
||||
vMAJOR.MINOR.PATCH[-PRERELEASE][+COMMITSHA]
|
||||
|
||||
With --short, only the canonical string is emitted — useful for shell
|
||||
substitution and tool-version checks. Without --short, the command
|
||||
emits a multi-line summary including commit SHA, build timestamp, Go
|
||||
toolchain, and host os/arch for bug reports.`,
|
||||
Run: runVersion,
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionCmd.Flags().BoolVar(&versionShort, "short", false,
|
||||
"print just the canonical SemVer string (useful for scripts)")
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
func runVersion(cmd *cobra.Command, _ []string) {
|
||||
v, err := semver.Compose(versionFromMain(), commitFromMain())
|
||||
if err != nil {
|
||||
// Treat injection failures the same as a dev build rather than
|
||||
// erroring out — users running `gortex version` on a broken
|
||||
// build still need the rest of the summary to report the issue.
|
||||
v = semver.Version{Build: commitFromMain()}
|
||||
}
|
||||
|
||||
if versionShort {
|
||||
fmt.Fprintln(cmd.OutOrStdout(), canonical(v))
|
||||
return
|
||||
}
|
||||
|
||||
w := cmd.OutOrStdout()
|
||||
fmt.Fprintf(w, "gortex %s\n", canonical(v))
|
||||
if v.IsZero() && v.Build == "" {
|
||||
fmt.Fprintln(w, " (dev build — no ldflags injected; build via `make build` or `goreleaser` for a versioned binary)")
|
||||
}
|
||||
fmt.Fprintf(w, " commit: %s\n", printable(commitFromMain(), "(none)"))
|
||||
fmt.Fprintf(w, " built: %s\n", printable(dateFromMain(), "(unknown)"))
|
||||
fmt.Fprintf(w, " go: %s\n", runtime.Version())
|
||||
fmt.Fprintf(w, " os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// canonical renders a Version with a graceful fallback for dev builds.
|
||||
// A zero Version with no build slot renders "v0.0.0-dev" so scripts
|
||||
// using `gortex version --short` always get a parseable-ish string
|
||||
// instead of an unexpected "v0.0.0" that looks like a real release.
|
||||
func canonical(v semver.Version) string {
|
||||
if v.IsZero() && v.Build == "" {
|
||||
return "v0.0.0-dev"
|
||||
}
|
||||
return v.String()
|
||||
}
|
||||
|
||||
// versionFromMain / commitFromMain / dateFromMain are tiny wrappers so
|
||||
// the version logic stays testable — tests that want to rewrite these
|
||||
// for a fake build can shadow them without having to reach into main.
|
||||
func versionFromMain() string { return version }
|
||||
func commitFromMain() string { return commit }
|
||||
func dateFromMain() string { return date }
|
||||
|
||||
// printable picks a non-empty string or returns the provided fallback —
|
||||
// used so empty injection slots render as "(none)" instead of a blank.
|
||||
func printable(s, fallback string) string {
|
||||
if s == "" {
|
||||
return fallback
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// canonicalVersion returns the running binary's SemVer string in
|
||||
// canonical form (e.g. "v0.1.0+abc1234"). Used by the daemon handshake
|
||||
// and any other place where Gortex identifies itself to a peer — keeps
|
||||
// the wire format consistent regardless of whether the caller holds
|
||||
// `main.version` or reconstructs it.
|
||||
func canonicalVersion() string {
|
||||
v, err := semver.Compose(versionFromMain(), commitFromMain())
|
||||
if err != nil {
|
||||
// Fall back to raw version string; better to surface the weird
|
||||
// value than to lie about it.
|
||||
return versionFromMain()
|
||||
}
|
||||
return canonical(v)
|
||||
}
|
||||
|
||||
// --- gortex version bump <kind> -------------------------------------------
|
||||
|
||||
var versionBumpPrerelease string
|
||||
|
||||
var versionBumpCmd = &cobra.Command{
|
||||
Use: "bump <major|minor|patch>",
|
||||
Short: "Bump the version in cmd/gortex/main.go and print the tag-release command",
|
||||
Long: `Rewrites main.version in cmd/gortex/main.go — the single source of truth
|
||||
— by incrementing the requested part. After the file is updated, the
|
||||
output suggests the git commit + tag commands to finish the release.
|
||||
|
||||
Must be run from the repo root (where cmd/gortex/main.go lives); the
|
||||
bump is a source-file edit, not a git operation, so the command
|
||||
refuses to run without that file in place.
|
||||
|
||||
Bump rules:
|
||||
major MAJOR++, MINOR and PATCH → 0, prerelease cleared
|
||||
minor MINOR++, PATCH → 0, prerelease cleared
|
||||
patch PATCH++, prerelease cleared
|
||||
|
||||
--pre adds or replaces a prerelease identifier (e.g. --pre rc.1). Use
|
||||
it to mark a release candidate: "gortex version bump minor --pre rc.1"
|
||||
goes from v0.1.0 to v0.2.0-rc.1.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgs: []string{"major", "minor", "patch"},
|
||||
RunE: runVersionBump,
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionBumpCmd.Flags().StringVar(&versionBumpPrerelease, "pre", "",
|
||||
"attach a prerelease identifier (e.g. rc.1, alpha.2); overrides any existing one")
|
||||
versionCmd.AddCommand(versionBumpCmd)
|
||||
}
|
||||
|
||||
// mainGoVersionRe matches the `version = "..."` declaration in main.go,
|
||||
// whether it lives inside a var (…) block (leading tab / spaces only) or
|
||||
// on a top-level `var version = "..."` line (leading "var "). Capture 2
|
||||
// is the version string itself without its surrounding quotes.
|
||||
//
|
||||
// Writing this as a single regex keeps the rewriter simple; the var-
|
||||
// block form is what Gortex actually ships, but the plain form shows up
|
||||
// in test fixtures and third-party forks.
|
||||
var mainGoVersionRe = regexp.MustCompile(`(?m)^(\s*(?:var\s+)?version\s*=\s*)"([^"]*)"(.*)$`)
|
||||
|
||||
func runVersionBump(cmd *cobra.Command, args []string) error {
|
||||
kind := args[0]
|
||||
switch kind {
|
||||
case "major", "minor", "patch":
|
||||
// ok
|
||||
default:
|
||||
return fmt.Errorf("bump kind must be major, minor, or patch (got %q)", kind)
|
||||
}
|
||||
|
||||
// Locate cmd/gortex/main.go relative to the current working
|
||||
// directory. Refuse to run outside the repo — this is a dev tool.
|
||||
path := filepath.Join("cmd", "gortex", "main.go")
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w (run this from the repo root)", path, err)
|
||||
}
|
||||
|
||||
m := mainGoVersionRe.FindSubmatch(raw)
|
||||
if m == nil {
|
||||
return fmt.Errorf(`could not find a "version = \"...\"" line in %s`, path)
|
||||
}
|
||||
current := string(m[2])
|
||||
|
||||
next, err := bumpSemver(current, kind, versionBumpPrerelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rewrite using FindSubmatchIndex so we only touch the quoted value.
|
||||
idx := mainGoVersionRe.FindSubmatchIndex(raw)
|
||||
// idx[4]/idx[5] are the start/end of the 2nd capture group (the
|
||||
// version string) — surgical replace avoids accidentally rewriting
|
||||
// the comment on the same line.
|
||||
var buf bytes.Buffer
|
||||
buf.Write(raw[:idx[4]])
|
||||
buf.WriteString(next.String()[1:]) // drop leading 'v' — main.go stores the raw semver
|
||||
buf.Write(raw[idx[5]:])
|
||||
|
||||
if err := os.WriteFile(path, buf.Bytes(), 0o644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", path, err)
|
||||
}
|
||||
|
||||
w := cmd.OutOrStdout()
|
||||
fmt.Fprintf(w, "Bumped %s: %s → %s\n", kind, current, next.String()[1:])
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w, "Next steps:")
|
||||
fmt.Fprintf(w, " git add %s\n", path)
|
||||
fmt.Fprintf(w, " git commit -m \"Bump version to %s\"\n", next.String())
|
||||
fmt.Fprintf(w, " git tag %s\n", next.String())
|
||||
fmt.Fprintf(w, " git push && git push origin %s\n", next.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// bumpSemver applies the bump rule to a SemVer string and returns the
|
||||
// resulting Version. Pre-release (if supplied) overrides whatever was
|
||||
// in the source. Build slot is always cleared — it gets filled in at
|
||||
// build time by the ldflag injection, not checked into main.go.
|
||||
func bumpSemver(current, kind, pre string) (semver.Version, error) {
|
||||
v, err := semver.Parse("v" + strings.TrimPrefix(current, "v"))
|
||||
if err != nil {
|
||||
return semver.Version{}, fmt.Errorf("main.version %q is not valid semver: %w", current, err)
|
||||
}
|
||||
switch kind {
|
||||
case "major":
|
||||
v.Major++
|
||||
v.Minor = 0
|
||||
v.Patch = 0
|
||||
case "minor":
|
||||
v.Minor++
|
||||
v.Patch = 0
|
||||
case "patch":
|
||||
v.Patch++
|
||||
}
|
||||
v.Prerelease = pre
|
||||
v.Build = ""
|
||||
return v, nil
|
||||
}
|
||||
Reference in New Issue
Block a user