a plugin can depend on a release binary, and prove it is the right one

osDependencies could only say "this package, from this manager". Gitea's `tea`
is not in any distribution — it is a Go binary from dl.gitea.com — and Debian
has an UNRELATED package of that name: a graphical text editor. So the one
dependency gitea actually wants could not be expressed, and its marketplace
page showed no dependencies at all while music's showed two.

`release: { url, identify }` covers it. {os} and {arch} are filled from the
host in the spelling Go releases use, and the binary is fetched to
~/.local/bin — no root, and nothing a plugin downloaded from the internet
sitting beside what the distribution installed. Written to a temporary name and
moved into place, so a half-downloaded file is never on PATH under the real
one.

`identify` is the part that matters. Bun.which('tea') cannot tell Gitea's CLI
from the text editor, so presence was never the same question as identity —
and reporting the wrong program as satisfied is worse than reporting it
missing, because the install succeeds and every call afterwards fails
strangely. A binary that fails to identify is now `present: false, imposter:
true`.

Found while testing: Bun.which() with no options reads the PATH the PROCESS
started with, which under PM2 frequently excludes ~/.local/bin entirely. A
release binary fetched during an install would have been reported missing
seconds later by stillMissing, failing an install that had just succeeded. The
search now passes PATH explicitly, with ~/.local/bin first.

Verified against the real binaries on this machine: the genuine tea identifies,
a fake that prints text-editor output is caught as an imposter, and both
dl.gitea.com URLs (linux-amd64, darwin-arm64) answer 200.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 08:48:18 +00:00
co-authored by Claude Opus 5
parent 51afa40a19
commit 09f2cfa267
5 changed files with 277 additions and 9 deletions
+28
View File
@@ -86,6 +86,34 @@ export type OsDependency = {
* to the owner rather than a guess.
*/
packages: Partial<Record<PackageManager, string>>;
/**
* A release binary, for a dependency no package manager carries.
*
* `packages` covers the ordinary case: the thing is in the distribution and `apt install` fetches it.
* Some tools are not, and the motivating one is Gitea's `tea` — distributed as a Go binary from
* dl.gitea.com, and **Debian has an unrelated package of the same name**, a graphical text editor.
* Declaring `apt: 'tea'` would install the wrong program, report success, and leave every call failing
* strangely.
*
* `{os}` and `{arch}` are substituted from the host: `linux`/`darwin`, `amd64`/`arm64` — the spelling
* Go releases use, since that is what publishes binaries this way.
*
* Downloaded to `~/.local/bin`, not a system directory: this needs no root, and a binary a plugin
* fetched from the internet has no business in `/usr/local/bin` beside things the distribution put
* there.
*/
release?: {
/** e.g. `https://dl.gitea.com/tea/0.9.2/tea-0.9.2-{os}-{arch}` */
url: string;
/**
* How to prove the binary on PATH is THIS one and not a namesake.
*
* Presence is not identity. `Bun.which('tea')` cannot tell Gitea's CLI from the text editor, so
* without this the platform would find a `tea`, report the dependency satisfied, and be wrong. The
* command is run with these arguments and its output must contain `contains`.
*/
identify?: { args: string[]; contains: string };
};
};
export type PluginManifest = {