update

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

internal/update/channel.go

Package update implements ccwrap's update awareness: a rate-limited background version check, a fact-only state cache, install-channel detection, and the binary self-replace used by `ccwrap upgrade`.

The package is deliberately UI-free — it returns data, never renders copy. All user-facing wording lives in cmd/ccwrap.

Index

Constants

View Source
const CacheFile = "update-check.json"

CacheFile is the update-check state file, beside profiles.json and the persisted timezone choice in app.Paths.StateDir (the repo's established convention for durable single-value state; see internal/profiles/profiles.go DefaultPath's doc comment).

View Source
const CheckTimeout = 5 * time.Second

CheckTimeout caps one version check end-to-end — same budget as the egress probe.

View Source
const DefaultCheckURL = "https://registry.npmjs.org/ccwrap-cli/latest"

DefaultCheckURL is npm's per-dist-tag endpoint. npm over the GitHub API for two reasons: registry.npmjs.org is far more reachable for the project's primary (CN-network) audience, and the `latest` dist-tag only moves on stable releases — prereleases publish to `next` (see release.yml), so users are never nagged about a prerelease. Versions are lockstep across npm and GitHub releases (both cut from the same tag), so either source is authoritative.

View Source
const DefaultReleaseBase = "https://github.com/Hoper-J/ccwrap/releases/download"

DefaultReleaseBase is where release artifacts live. Version DISCOVERY goes to npm (reachability), but artifacts only exist on GitHub releases — the same split install.sh lives with. Lockstep between the two sources is guaranteed by the tag-driven release pipeline.

Variables

This section is empty.

Functions

func Apply

func Apply(ctx context.Context, client *http.Client, releaseBase, version, goos, goarch, exePath string, out io.Writer) error

Apply downloads the release archive for version/goos/goarch, verifies its sha256 against the published checksums.txt (same rigor as install.sh — no checksum, no install), extracts the `ccwrap` entry, and atomically renames it over exePath via a same-directory temp file. On ANY failure the existing binary is left untouched and the temp file is removed. A permission failure surfaces as os.ErrPermission so the caller can print an actionable sudo / CCWRAP_BINDIR hint instead of a raw error.

func CheckURL

func CheckURL(getenv func(string) string) string

CheckURL resolves the version-discovery endpoint. The override exists for self-hosted mirrors in privacy-strict or GitHub/npm-blocked environments — same posture as CCWRAP_EGRESS_TEST_URL. Contract: the endpoint returns JSON with a top-level "version" field (npm's /latest response is natively compatible).

func Disabled

func Disabled(getenv func(string) string, flagDisabled bool) bool

Disabled reports whether the PASSIVE side (background check + all notices) is off. Explicit actions (`ccwrap upgrade`, `ccwrap version --check`) intentionally ignore this switch.

func Due

func Due(c Cache, ok bool, now time.Time) bool

Due reports whether a new network check is allowed at `now`.

func Eligible

func Eligible(current string) bool

Eligible reports whether the running build participates in update notifications. Only clean release versions qualify: a developer's in-tree build (pseudo-version, dirty tree, or the 0.0.0 sentinel from cmd/ccwrap/version.go) must never be nagged about its own older tag. Build metadata (+sha) is allowed — release binaries carry it — but a ".dirty" marker inside it disqualifies the build.

func FetchLatest

func FetchLatest(ctx context.Context, client *http.Client, url string) (string, error)

FetchLatest performs one plain GET — no query params, no machine/version identifiers — and returns the endpoint's version.

func InCI

func InCI(getenv func(string) string) bool

InCI reports whether we are running under CI. Notifications (and the background check itself) are suppressed there — nobody reads them and CI egress is often restricted.

func ManualHint

func ManualHint(ch Channel) string

ManualHint is the copy-pasteable fallback command shown whenever the automated path fails or is unavailable — upgrade failures must always leave the user with a working next step.

func NewClient

func NewClient(cfg model.EgressConfig, timeout time.Duration) *http.Client

NewClient builds an HTTP client that dials through the resolved egress config — update traffic must exit exactly where the session's traffic exits; a check that bypasses the user's egress proxy would betray the tool's whole premise. Construction mirrors buildEgressAwareHTTPClient (probe.go): SOCKS goes through DialContext because http.Transport.Proxy only speaks HTTP/HTTPS proxies; otherwise only Proxy is set, so an HTTP egress proxy is never dialed through itself. timeout 0 means no client-level cap (the caller's context governs; used by the larger `upgrade` download).

func Newer

func Newer(current, latest string) bool

Newer reports whether latest is strictly newer than current under semver precedence (build metadata ignored, per semver 2.0 §10 — the same reading versionString()'s doc comment relies on). Unparseable input on either side means "no": a notification must never fire on garbage data from the network or an exotic local build.

func ResolveExecutable

func ResolveExecutable() (string, error)

ResolveExecutable returns the symlink-resolved path of the running binary. Self-replace targets the REAL file so user-made symlinks keep pointing at the refreshed install.

func SaveCache

func SaveCache(stateDir string, c Cache) error

SaveCache writes atomically (same-dir temp file + rename) so a concurrent reader never sees a torn file. Two sessions checking at once is a benign last-write-wins race — both write equivalent facts seconds apart — which is why this is a plain rename and not an flock like profiles.json (that one is read-modify-write user data).

func UpgradeArgv

func UpgradeArgv(ch Channel) []string

UpgradeArgv returns the package-manager command that performs the upgrade for exec-style channels, nil for binary (self-replace) and source (refused).

Types

type Cache

type Cache struct {
	Schema    int       `json:"schema"`
	CheckedAt time.Time `json:"checked_at"`
	Latest    string    `json:"latest"`
}

Cache stores FACTS only (when we checked, what the registry said) — never a "newer" verdict. Whether to notify is recomputed at render time against the running version, so a cache written before an upgrade goes naturally quiet after it without cleanup.

func LoadCache

func LoadCache(stateDir string) (Cache, bool)

LoadCache returns the cache and whether it is usable. Any defect — missing file, corrupt JSON, unknown schema, zero time, blank latest — reads as "no cache": the file is disposable by contract, so a bad one must degrade to a silent re-check, never to an error.

type Channel

type Channel int

Channel identifies how this binary was installed, which decides how `ccwrap upgrade` acts.

const (
	// ChannelSource is a from-source build (pseudo-version, dirty, or no
	// stamp): upgrade refuses and points at git pull && go build.
	ChannelSource Channel = iota
	ChannelNPM
	ChannelPnpm
	ChannelBun
	ChannelYarn
	// ChannelBinary is a goreleaser release binary outside node_modules
	// (install.sh or manual tarball): upgrade self-replaces in place.
	ChannelBinary
	ChannelGoInstall
)

func DetectChannel

func DetectChannel(exePath, versionBase string, bi *debug.BuildInfo) Channel

DetectChannel classifies the running binary. Order matters: the npm platform package ships the SAME goreleaser binary as the release tarball (release.yml publishes dist/ verbatim to npm), so ldflags cannot tell those two apart — the node_modules path test must win before the versionBase test.

Jump to

Keyboard shortcuts

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