notify

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 5, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package notify performs a passive, never-blocking "you're behind" check for a Go CLI and prints a one-line hint when a newer release ref exists.

A ref is an opaque release identifier: usually a semver tag, but it may be a commit hash or another caller-defined token when WithLatestFunc and WithComparator are supplied. The hint is served from a small cache file - an instant disk read, never the network - so it adds no latency to the command. When that cache is stale, a background goroutine refreshes it; the refresh is abandoned at process exit unless the host calls AwaitRefresh. The flush re-reads the cache, so a refresh that finishes while the command runs is shown that same run; otherwise its result appears on the next invocation. The auto-printed hint is itself throttled to once per notify interval (default 24h, WithNotifyInterval), independent of the refresh interval (default 24h, WithRefreshInterval); either interval set non-positive disables its throttle. A tool describes itself with an updater.Tool - the same Config its self-update uses, such as a brew.Config, goinstall.Config, or github.Config - calls Check before dispatching its command, and invokes the returned flush function after:

flush := notify.Check(brew.New(clive.Info{Module: "github.com/example/myapp"}))
defer flush()

The per-tool kill switch MYAPP_NO_UPDATE_CHECK (derived from the binary name) is a global opt-out: Check and Pending do not schedule a refresh and Pending reports no update. A non-terminal stderr only suppresses the auto-printed hint returned by Check; it does not gate Pending.

The verdict is failure-tolerant: a background refresh that fails re-stamps the last successfully fetched ref to honour the cooldown and records the failure, but a known, still-valid newer ref keeps reporting pending. The failure governs only refresh scheduling and the cooldown, never whether an update is shown, so a transient outage cannot silence an update the tool already discovered. A check that has never succeeded has no cached ref and so reports nothing.

Pending exposes the same verdict without printing so hosts can render update state in their own UI. Its Result also carries Failed (the last refresh failed, so the ref may be stale) and Dismissed (the ref was dismissed via Skip), letting a host distinguish no update, a known-but-stale update, and a dismissed update. Skip records a dismissed ref in the same cache. The dismissal suppresses hints only while the latest known ref is exactly the dismissed ref; when the latest ref changes, normal comparator-based behavior resumes, including for non-semver refs where ordering is undefined. The cache file remains mtime-based for cooldowns. New writes use a JSON object holding the active track, latest ref, dismissed ref, and last-refresh failure state; old single-line files are read as "latest ref for this cache file's active track, no dismissal".

By default "latest" is the highest semver tag in the tool's GitHub repository, compared with semver ordering and the existing dev-build base-tag rule. A tool whose releases are not readable GitHub tags - a private repo, rolling commit hashes, or an artifact bucket - overrides lookup, comparison, display, and track with WithLatestFunc, WithComparator, WithRefDisplay, and WithChannel while keeping the same cache and cooldown behavior.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AwaitRefresh

func AwaitRefresh(timeout time.Duration) bool

AwaitRefresh waits for in-flight background refreshes to finish, returning true when there were none or all completed before timeout. It returns false when timeout elapses, and never waits longer than timeout.

func Check

func Check(tool updater.Tool, opts ...Option) func()

Check serves an "update available" hint from the per-tool cache and, when that cache is stale, refreshes it in the background for the next invocation. It never blocks and never touches the network on the calling path. tool describes the tool: its binary name forms the kill-switch env var, the cache namespace, and the `<binary> update` command, and its display name opens the message.

The returned function prints the hint; the caller invokes it after its command completes, so the hint follows the command's own output. It is a no-op when no update is pending, when the check is disabled, or when stderr is not a terminal. Non-terminal stderr suppresses only printing; stale caches may still refresh in the background.

func Skip

func Skip(tool updater.Tool, ref string, opts ...Option) error

Skip dismisses ref on the active track until the latest known ref changes. The dismissal is persisted in the notify cache and survives process restarts.

Types

type LatestFunc

type LatestFunc func(ctx context.Context) (string, error)

LatestFunc reports the latest released ref of the tool, such as "v1.2.3" or a commit hash. It is called only on a stale-cache refresh, off the calling path, and bounded by the same lookupTimeout as the default check.

type Option

type Option func(*checker)

Option configures a Check. The defaults target a real run; the seams exist for testing and for callers that supply their own HTTP client or accent.

func WithCacheDir

func WithCacheDir(dir string) Option

WithCacheDir overrides the directory holding the cache file.

func WithChannel

func WithChannel(name string) Option

WithChannel selects a release track and namespaces the cache for that track. Switching tracks never compares the new track against another track's cached ref; the new track starts stale and refreshes independently.

func WithColor

func WithColor(c color.Color) Option

WithColor overrides the accent colour of the update message. It defaults to the orange lipgloss.Color(updateColor).

func WithComparator

func WithComparator(fn func(current, latest string) bool) Option

WithComparator overrides the decision that latest is an update over current. The default comparator is semver-based and treats dev builds as ahead of their base tag. Non-semver tracks can provide a comparator such as current != latest.

func WithCurrentVersion

func WithCurrentVersion(v string) Option

WithCurrentVersion overrides the running ref compared against the latest ref. It defaults to clive.Current.

func WithLatestFunc

func WithLatestFunc(fn LatestFunc) Option

WithLatestFunc overrides how a refresh discovers the latest ref. By default the latest ref is the highest semver tag in the tool's GitHub repository (clive.Info.LatestTag); a tool whose releases are not published as readable GitHub tags - e.g. a private repo distributed from an artifact bucket - supplies its own lookup here.

func WithNotifyInterval

func WithNotifyInterval(d time.Duration) Option

WithNotifyInterval overrides the minimum gap between repeat auto-printed hints, so a pending update is surfaced at most once per interval rather than on every invocation. It defaults to 24h. A non-positive interval prints on every run. It throttles only the hint returned by Check; Pending always reports the current verdict.

func WithOutdatedHintSymbol added in v0.1.3

func WithOutdatedHintSymbol(symbol string) Option

WithOutdatedHintSymbol overrides the glyph on the update hint (default 💡).

func WithRefDisplay

func WithRefDisplay(fn func(string) string) Option

WithRefDisplay overrides how refs render in the auto-printed hint and in Result display fields. The default display is the raw ref unchanged.

func WithRefreshInterval

func WithRefreshInterval(d time.Duration) Option

WithRefreshInterval overrides the minimum gap between background network refreshes. It defaults to 24h. A non-positive interval refreshes on every invocation. This is independent of WithNotifyInterval: one governs how often the latest ref is re-fetched, the other how often a pending update is printed.

func WithStampDir added in v0.2.6

func WithStampDir(dir string) Option

WithStampDir overrides the subdirectory of the cache dir holding the stamp files (default "last-update"). An empty dir places them in the cache dir itself.

func WithStampNames added in v0.2.6

func WithStampNames(refresh, notify string) Option

WithStampNames overrides the refresh and notify stamp file names (defaults "check" and "notify"). An empty name keeps its default.

func WithTransport

func WithTransport(rt http.RoundTripper) Option

WithTransport sets the HTTP transport, the seam tests use to serve canned tag payloads without touching the network.

type Result

type Result struct {
	// CurrentRef is the running build's raw ref.
	CurrentRef string
	// LatestRef is the latest raw ref known for Track.
	LatestRef string
	// LatestIsUpdate reports the comparator verdict before dismissal is applied.
	LatestIsUpdate bool
	// Failed reports that the last background refresh failed, so LatestRef may be
	// stale - it is the last successfully fetched ref, not a fresh lookup. A known
	// update is still reported through a transient failure; this flag only lets a
	// host annotate that the data behind it could not be confirmed this run.
	Failed bool
	// Dismissed reports that LatestRef was dismissed via [Skip] and still matches
	// the dismissed ref, so the update is suppressed. It distinguishes "you
	// dismissed this" from "no update". A newer LatestRef clears it.
	Dismissed bool
	// Track is the active release track name.
	Track string
	// CurrentDisplay is CurrentRef after [WithRefDisplay].
	CurrentDisplay string
	// LatestDisplay is LatestRef after [WithRefDisplay].
	LatestDisplay string
}

Result is the update verdict read from the notify cache.

func Pending

func Pending(tool updater.Tool, opts ...Option) (Result, bool)

Pending returns the cached update verdict without printing. The bool is LatestIsUpdate && !Dismissed: it reports whether there is an update to act on after the comparator, track, cache, and dismissal state are applied. It is not gated by a failed refresh - a known newer ref keeps reporting true through a transient failure - so consult Result.Failed to learn the data may be stale and Result.Dismissed to tell a dismissal from no update. It uses the same stale-cache background refresh path as Check, but unlike the auto-printed hint it does not require a TTY.

Jump to

Keyboard shortcuts

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