Skip to main content

Channel

Enum Channel 

Source
pub enum Channel {
Show 14 variants Installer, Cargo, Npm, Bun, Pnpm, Yarn, UvTool, Pipx, Pip, WinGet, Scoop, Homebrew, Unknown, Foreign(&'static str),
}
Expand description

The package manager that owns the running binary.

One channel owns one binary. A copy installed through uv is upgraded through uv, never through npm, because two managers writing the same PATH entry would fight over it forever.

Variants§

§

Installer

install.sh / install.ps1 put it under the managed <config>/bin.

§

Cargo

cargo install / cargo binstall put it under ~/.cargo/bin.

§

Npm

npm install -g — the binary lives under a node_modules tree.

§

Bun

bun add -g — under ~/.bun/install/global.

§

Pnpm

pnpm add -g — under pnpm’s own global store.

§

Yarn

yarn global add (Yarn 1.x) — under ~/.config/yarn/global, shimmed from ~/.yarn/bin.

§

UvTool

uv tool install — under uv’s tool environments.

§

Pipx

pipx install — under a pipx venv.

§

Pip

pip install — a console script beside a Python interpreter, in the system scripts directory or a virtualenv’s.

§

WinGet

winget install — under %LOCALAPPDATA%\Microsoft\WinGet\Packages.

§

Scoop

scoop install — under ~/scoop/apps, shimmed from ~/scoop/shims.

§

Homebrew

brew install — under the Cellar, symlinked into the prefix’s bin.

§

Unknown

Anywhere else: a dev build, a hand-copied binary, a distro package.

§

Foreign(&'static str)

A copy inside a tree that is recognisably some manager’s, where dev-prune knows the manager’s name and not its commands.

The distinction that matters is against Channel::Unknown, not against the named channels: Unknown means nothing on this machine claims this file, and devp uninstall deletes those because the file is the whole install. This means something claims it and dev-prune cannot speak to it, which is the one case where the only safe move is to name the manager and stop.

Implementations§

Source§

impl Channel

Source

pub fn detect() -> Self

Classify the running executable.

Source

pub fn detect_at(exe: &Path, managed: Option<&Path>) -> Self

Classify exe by the directories in its path.

Purely lexical: this must not touch the network or spawn anything, and each channel’s layout is stable enough that its marker directory is a reliable fingerprint. managed is passed in rather than resolved here so tests can probe the classification without a config directory on disk.

The managed path is checked first and the three directory-owning managers next. Order is load-bearing twice over. A Scoop install of a Rust toolchain can put .cargo inside ~/scoop, and misreading that as Cargo would send devp update to run cargo install against a directory Scoop replaces wholesale. And bun, pnpm and yarn all install npm packages into a node_modules tree of their own, so each of them matches npm’s marker as well as its own and has to be tried before it.

Source

pub fn label(self) -> &'static str

How to name this channel in a sentence addressed to the user.

Source

pub fn upgrade_command(self) -> Option<String>

The command that upgrades through this channel, as the user would type it.

None for Channel::Unknown only: there is no command to name for a binary somebody copied into place by hand.

Source

pub fn uninstall_command(self) -> Option<String>

The command that uninstalls through this channel.

None where there is no manager to tell: the installer’s own copy is deleted by devp uninstall itself, and an unrecognised copy is just a file.

Source

pub fn install_command(self) -> Option<String>

The command that installs dev-prune fresh through this channel, as the user would type it. Does not include Self::install_sources.

Source

pub fn install_sources(self) -> Vec<Vec<String>>

Sources that must exist before Self::install_argv can resolve dev-prune.

Homebrew and Scoop are the only reason this exists. The formula and the manifest live in this project’s own tap and bucket rather than the default index, and brew install dev-prune without the tap resolves against homebrew-core, where dev-prune is not published. Adding a source that is already added reports failure, so these steps are best-effort; the install itself is not.

Source

pub fn install_argv(self) -> Option<Vec<String>>

The command that installs dev-prune fresh through this channel, once Self::install_sources has run.

None for Pip and Unknown: a bare pip install of a CLI puts the console script wherever the active interpreter happens to be, which is the ambiguity uv tool and pipx exist to remove, and nothing installs into an unrecognised location on purpose.

Source

pub fn upgrade_argv(self) -> Option<Vec<String>>

The command that upgrades the copy this channel installed.

Source

pub fn uninstall_argv(self) -> Option<Vec<String>>

The command that removes the copy this channel installed and clears the record the manager keeps of it.

Running this is the only correct way to remove a manager-owned copy, and the reason is not tidiness. Deleting the file behind cargo’s back leaves .crates.toml naming a binary that is gone, and cargo uninstall dev-prune then exits 101 with corrupt metadata, ... does not exist when it should — without clearing the entry. The manager has to be told first, or it can never be told at all.

None where no manager holds a record: the installer’s own copy is deleted by devp uninstall itself, and an unrecognised copy is just a file.

Source

pub fn owns_its_files(self) -> bool

Whether a package manager keeps a record of this install that deleting the file would falsify.

When true, devp uninstall names the manager’s own command instead of quietly removing the file: pip list still showing a package whose binary is gone, or cargo install refusing to reinstall over its own bookkeeping, is worse than a leftover binary the user was told about.

Source

pub fn may_delete_directly(self) -> bool

Whether devp uninstall may delete this copy with fs::remove_file.

True in exactly two cases, and the two look identical from a path, which is why this is asked as its own question. The installer keeps no record beyond the file it wrote, and a copy in a location nothing claims is a file somebody moved there. Everything else – a manager with a command, and a manager without one – is removed by its manager or not at all: Self::uninstall_argv explains what deleting the file first costs, and a Channel::Foreign copy costs the same with no way to repair it afterwards.

Source

pub fn replaces_its_directory(self) -> bool

Whether this channel replaces its install directory wholesale on upgrade.

This is the distinction the old per-command classifiers did not have, and the one that caused a real bug. WinGet, Scoop and Homebrew each version their package directory and swap the whole thing — …\WinGet\Packages\<id>\, ~/scoop/apps/ <pkg>/<version>/, <prefix>/Cellar/<pkg>/<version>/. Anything dev-prune writes beside its own executable there is gone at the next upgrade, and anything pointing at it — a scheduled task, a git hook — is left aimed at a path that no longer exists.

So nothing durable is ever written into one of these directories. The devp twin goes to the managed <config>/bin instead, which this program owns and which no package manager will replace underneath it.

Trait Implementations§

Source§

impl Clone for Channel

Source§

fn clone(&self) -> Channel

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Channel

Source§

impl Debug for Channel

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Channel

Source§

impl PartialEq for Channel

Source§

fn eq(&self, other: &Channel) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Channel

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.