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
- func Apply(ctx context.Context, client *http.Client, ...) error
- func CheckURL(getenv func(string) string) string
- func Disabled(getenv func(string) string, flagDisabled bool) bool
- func Due(c Cache, ok bool, now time.Time) bool
- func Eligible(current string) bool
- func FetchLatest(ctx context.Context, client *http.Client, url string) (string, error)
- func InCI(getenv func(string) string) bool
- func ManualHint(ch Channel) string
- func NewClient(cfg model.EgressConfig, timeout time.Duration) *http.Client
- func Newer(current, latest string) bool
- func ResolveExecutable() (string, error)
- func SaveCache(stateDir string, c Cache) error
- func UpgradeArgv(ch Channel) []string
- type Cache
- type Channel
Constants ¶
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).
const CheckTimeout = 5 * time.Second
CheckTimeout caps one version check end-to-end — same budget as the egress probe.
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.
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 ¶
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 ¶
Disabled reports whether the PASSIVE side (background check + all notices) is off. Explicit actions (`ccwrap upgrade`, `ccwrap version --check`) intentionally ignore this switch.
func Eligible ¶
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 ¶
FetchLatest performs one plain GET — no query params, no machine/version identifiers — and returns the endpoint's version.
func InCI ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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.