autoupdate

package
v0.11.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package autoupdate tells a user that a newer release exists, and nothing else.

It never installs. goselfupdate.Update installs, and the command wrapping it is where errors are printed; a failure here is recorded in the state file and swallowed. That single rule is what keeps a development build from printing an update failure on every invocation.

session := autoupdate.Start(ctx, autoupdate.Config{
	Update: goselfupdate.Config{Owner: "you", Repo: "tool", Binary: "tool", Version: version},
})
err := rootCmd.ExecuteContext(ctx)
session.Finish()

The check runs concurrently with the caller's own work and the notice is printed afterwards, so the line is not buried in the command's output. A command that outlasts the check pays nothing for it. One that finishes first waits in Session.Finish for at most Config.Timeout.

A separate package from goselfupdate so that a program wanting only the update half links none of this.

Index

Constants

View Source
const DefaultInterval = 24 * time.Hour

DefaultInterval is how long a check is good for.

View Source
const DefaultTimeout = 5 * time.Second

DefaultTimeout bounds the check. Start gives the check a context with this deadline, and Finish waits at most this long for a check still in flight. A command that outlasts the check never waits for it.

View Source
const Schema = 1

Schema is the version of the on-disk state format.

The same schema is written by pyselfupdate and bashselfupdate, so any tool can read any other tool's state and one dashboard can glob ~/.local/state/*/autoupdate-*.json with no per-tool knowledge. Adding a field is safe; renaming or repurposing one breaks the other two.

Variables

This section is empty.

Functions

func CanonicalMachine added in v0.10.0

func CanonicalMachine(name string) string

CanonicalMachine reduces a hostname from any source to the form Machine records.

A hostname reaches a reader fully qualified as often as bare, so a comparison against a recorded name has to canonicalize both sides or it fails on every host.

func Machine added in v0.10.0

func Machine() string

Machine identifies the box this process is running on: the bare hostname, lowercased, with any domain suffix dropped.

pyselfupdate and bashselfupdate derive it the same way, so the three write interleaved files a single reader can enumerate.

func StateFilename

func StateFilename(machine string) string

StateFilename is the file one machine writes inside a tool's state directory.

The machine is part of the name because a state directory is a synced directory on some installations, and Syncthing has no merge: two boxes writing one path leaves one winner plus a conflict copy nobody reads. Both fields this file carries — the version installed here and the instant this box last checked — describe one machine, so the split costs nothing and makes the collision unreachable.

func StateHome

func StateHome() string

StateHome is the directory state files live under, honoring XDG_STATE_HOME.

func StatePath

func StatePath(tool string) string

StatePath is where this machine's state for a tool lives.

Types

type Config

type Config struct {
	// Update is the underlying update configuration. Required.
	Update goselfupdate.Config

	// Tool names the state directory and the per-tool environment variables.
	// Defaults to Update.Binary.
	Tool string

	// Interval between checks. Zero means DefaultInterval.
	Interval time.Duration

	// Timeout bounds the check. Zero means DefaultTimeout.
	Timeout time.Duration

	// Out receives the one line this package is allowed to print. Defaults to
	// os.Stderr.
	Out io.Writer

	// Interactive overrides terminal detection. Nil detects. Set it to false
	// from a program that already knows it is writing somewhere a human will
	// not read -- into a pager, a log, a structured-output mode -- since
	// nothing about the streams themselves reveals that.
	Interactive *bool

	// Suppress skips the check unconditionally. Intended for a command that
	// must never trigger one: an update command, a shell-completion callback
	// firing on every TAB press, anything a script parses.
	Suppress bool

	// StateDir overrides where state is written. Defaults to [StateHome].
	StateDir string

	// Clock supplies the current time. Defaults to time.Now.
	Clock func() time.Time

	// Environ looks up an environment variable, as os.LookupEnv does.
	Environ func(string) (string, bool)
}

Config describes one updatable tool.

type Outcome

type Outcome struct {
	Checked bool
	Skip    Skip
	Current string
	Latest  string
}

Outcome reports what happened. Returned for tests and dashboards, not for control flow: a caller has nothing useful to do with it.

func (Outcome) UpdateAvailable

func (o Outcome) UpdateAvailable() bool

UpdateAvailable reports whether the check found a newer release.

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session is an in-flight check.

func Start

func Start(ctx context.Context, config Config) *Session

Start runs the gate and, if it passes, begins a check in the background.

It returns immediately and never blocks the command about to run. Call Session.Finish once that command has finished.

func (*Session) Finish

func (s *Session) Finish() Outcome

Finish waits for a check still in flight, for at most Config.Timeout, then prints the notice, if there is one, and returns what happened.

It never returns an error and never panics: an update notice must not be able to break the command the user actually typed. Safe to call more than once.

type Skip

type Skip string

Skip explains why a check did not happen.

const (
	SkipNone       Skip = ""
	SkipDisabled   Skip = "disabled"
	SkipDevBuild   Skip = "dev-build"
	SkipNotATTY    Skip = "not-a-tty"
	SkipCI         Skip = "ci"
	SkipInterval   Skip = "interval"
	SkipFailed     Skip = "failed"
	SkipSuppressed Skip = "suppressed"
)

func Enabled

func Enabled(config Config) (bool, Skip)

Enabled reports whether the tool checks at all, and the Skip reason when it does not, which is what explains a missing notice. It runs every step of Start's gate except the interval: it answers "is this tool opted in", not "is it due", as pyselfupdate and bashselfupdate do. It reads no clock, no state file and no network.

type State

type State struct {
	Schema int    `json:"schema"`
	Tool   string `json:"tool"`

	CheckedAt string `json:"checked_at"`

	// CheckedAtEpoch is the same instant as CheckedAt. Redundant on purpose:
	// BSD date on macOS cannot parse ISO-8601 without -j -f gymnastics, and the
	// bash implementation has to do interval arithmetic with jq and date +%s
	// alone. One duplicated field buys a portable bash sibling.
	CheckedAtEpoch int64 `json:"checked_at_epoch"`

	CurrentVersion string `json:"current_version"`
	LatestVersion  string `json:"latest_version"`

	// LastError is non-empty when the last check failed. There is deliberately
	// no separate "skip reason" field: a gate that declines to check does not
	// write this file at all, which is what makes the absence of a state file
	// observable proof that the network was never touched.
	LastError string `json:"last_error"`
}

State is one tool's record of its last update check.

This is state, not configuration and not cache: it persists across runs, it is not authored by the user, and deleting it changes behavior rather than merely costing a recompute. That is XDG_STATE_HOME by the Base Directory specification, and it is where gh puts the same thing.

func ReadState

func ReadState(tool string) State

ReadState returns a tool's state, or a zero value when it has never been written.

A corrupt or unreadable file reads as empty rather than failing: the file is a throttle, and breaking a user's command because the throttle cannot be read would be worse than checking one extra time.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL