Skip to content

Commit bbc8c0d

Browse files
committed
feat(cli): make the update check source a choice of four
The check walked one fixed ladder and read a GitHub-releases-shaped document from whichever URL answered. That is wrong as soon as people install differently: an npm user's latest is the registry's latest dist-tag, and announcing a GitHub release their package manager cannot install yet is worse than saying nothing. [update] source now selects gh-releases, npm, gh-registry or custom; unset keeps today's ladder, and an npm-managed install defaults to asking npm. Checking is kept separate from downloading. A source returns a version, a link, a publish time and a headline, and never says where the binary comes from -- artifacts and their signed manifest always resolve from the release infrastructure. The manifest is what makes a self-update trustworthy and a source is a URL a user can point anywhere, so letting it redirect the download would turn a config setting into arbitrary code execution. A test fails if a source ever reaches the artifact ladder. get_update_servers and its private config reader are DELETED rather than left beside the new code, so the compiler enforces that both call sites moved. Four sources that pass their own tests while the old ladder still runs underneath is the failure this avoids. The npm shapes request the abbreviated packument, which is cheaper and dodges GitHub's unauthenticated rate limit. The public registry is asked with no credentials and a test asserts it; GitHub Packages requires a token and fails naming the fix rather than retrying anonymously and reading a 404 as up to date. A configured source does not fall back to the ladder on error.
1 parent b7e6988 commit bbc8c0d

5 files changed

Lines changed: 666 additions & 40 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
### Added
2+
3+
**Where Perry asks "what is the latest version?" is now a choice.** It used to
4+
walk one fixed list — an override, the config, Perry Hub, then the GitHub
5+
releases API — and read a GitHub-releases-shaped document from whichever
6+
answered first. That is fine while everyone installs the same way, and wrong as
7+
soon as they do not: an npm user's "latest" is whatever the registry's `latest`
8+
dist-tag says, and asking GitHub instead can announce a version their package
9+
manager cannot install yet.
10+
11+
```toml
12+
[update]
13+
source = "npm" # gh-releases | npm | gh-registry | custom
14+
package = "@perryts/perry" # npm-shaped sources; defaults to Perry's own
15+
registry = "..." # npm-shaped sources; defaults to the public registry
16+
server = "..." # the URL for `custom`, and the mirror override
17+
```
18+
19+
Unset keeps the historical ladder, so nothing changes for anyone who does not
20+
set it — except on an **npm-managed install**, which now defaults to asking npm,
21+
because that is the version its own package manager can actually install.
22+
23+
<details>
24+
<summary><b>The split that matters: checking is not downloading</b></summary>
25+
26+
A check source answers one question and returns a version, a link, a publish
27+
time and a headline. It does **not** decide where the binary comes from.
28+
Artifacts and their signed manifest always resolve from the release
29+
infrastructure, whatever the check source is.
30+
31+
That separation is load-bearing rather than tidy. The manifest — Ed25519 over
32+
the artifact's digest and version — is what makes a self-update trustworthy,
33+
and a check source is a URL a user can point anywhere. Letting it redirect the
34+
download would turn a configuration setting into a way to install an arbitrary
35+
binary. Whoever answers "what is new?" never gets to answer "what should I
36+
run?", and there is a test that fails if a source ever leaks into the artifact
37+
ladder.
38+
39+
The old `get_update_servers` and its private config reader are **deleted**
40+
rather than left beside the new code, so the compiler enforces that both call
41+
sites moved. A new abstraction with the old ladder still wired up underneath is
42+
the shape where four sources exist, pass their own tests, and are never
43+
reached.
44+
</details>
45+
46+
<details>
47+
<summary><b>Credentials go to exactly one of the four</b></summary>
48+
49+
The npm shapes ask for the *abbreviated* packument
50+
(`Accept: application/vnd.npm.install-v1+json`) — smaller, cacheable, and the
51+
document npm itself requests for this question. It also avoids GitHub's
52+
unauthenticated API rate limit, which the old ladder shared with everything
53+
else on the machine.
54+
55+
The public registry is asked **without credentials**, and a test asserts no
56+
`Authorization` header is sent: a token there would be a leak, not a
57+
convenience. GitHub Packages does need one, so that shape reads `GH_TOKEN` /
58+
`GITHUB_TOKEN` and fails with a sentence naming the fix when neither is set,
59+
rather than retrying anonymously and reporting the resulting 404 as "up to
60+
date".
61+
62+
A configured source does not fall back to the ladder when it errors. Somebody
63+
who said "ask npm" and got a failure wants to hear that, not a version from
64+
somewhere they never named.
65+
</details>
66+
67+
<details>
68+
<summary><b>Tests</b></summary>
69+
70+
11 new, all parsing real response shapes from string fixtures so no network is
71+
involved:
72+
73+
- a GitHub release document, including that the `v` prefix is stripped;
74+
- an abbreviated packument, which has no `time` map — so the publish date reads
75+
"unknown" rather than being invented, which matters because the release
76+
cooldown in the next slice depends on it;
77+
- a full packument, which does supply it;
78+
- a custom manifest with only a `version`, and one with every optional field;
79+
- that each shape **rejects the others' documents** rather than reading a field
80+
that happens to be present — a registry answering a gh-releases request must
81+
be an error, not a version of `""`;
82+
- that a scoped package's `/` is percent-encoded, or the registry reads the
83+
scope as a path segment and answers 404;
84+
- that an unknown `source` name falls back instead of failing, so a config
85+
written by a newer Perry does not break an older one;
86+
- that `custom` with no URL is treated as a missing key rather than a default;
87+
- that an npm install defaults to npm and every other channel keeps the ladder;
88+
- that no check source can reach the artifact ladder;
89+
- and both credential rules.
90+
91+
`cargo test -p perry`: 925 passed, 0 failed.
92+
</details>

crates/perry/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod compat_reports;
77
mod install_channel;
88
#[cfg(test)]
99
mod panic_profile_contract;
10+
mod release_source;
1011
mod shadow_layout_contract;
1112
mod telemetry;
1213
#[cfg(test)]

0 commit comments

Comments
 (0)