Skip to content

Commit dc1f0bb

Browse files
authored
feat(cli): let the version check come from GitHub releases, npm, GitHub Packages, or a custom URL (#7785)
The version check and the download used to be one setting, so pointing the check at npm meant pointing the download there too. They are now separate: `source` picks where the version number is read from, while the binary and its signed manifest always come from Perry's release infrastructure, so a check source cannot redirect an install. An npm-installed Perry defaults to asking npm, which is both the cheapest request and the one that matches what the user will run to upgrade.
1 parent 9e5a58c commit dc1f0bb

5 files changed

Lines changed: 803 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)