pub trait PackageManager: Send + Sync {
// Required methods
fn name(&self) -> &'static str;
fn detect(&self, project_path: &Path) -> bool;
fn bloat_dirs(&self, project_path: &Path) -> Vec<BloatDir>;
fn enforce_lockfile(
&self,
project_path: &Path,
policy: EnforcePolicy,
) -> Result<()>;
fn restore(&self, project_path: &Path, timeout: Duration) -> Result<()>;
// Provided methods
fn restore_named(
&self,
project_path: &Path,
dir_name: &str,
runtime: Option<&str>,
timeout: Duration,
) -> Result<()> { ... }
fn runtime_tag(&self, project_path: &Path, dir_name: &str) -> Option<String> { ... }
fn lockfiles(&self) -> &'static [&'static str] { ... }
fn drift(&self, project_path: &Path) -> Vec<DriftReport> { ... }
fn opt_in(&self) -> bool { ... }
}Expand description
The core trait that every package manager adapter must implement.
Each adapter is responsible for:
- Detecting whether it applies to a given project directory
- Listing the bloat directories it manages
- Enforcing lockfile consistency before deletion
- Restoring dependencies from lockfiles
Required Methods§
Sourcefn name(&self) -> &'static str
fn name(&self) -> &'static str
Human-readable name for this adapter (e.g., “npm”, “pnpm”, “uv”).
Sourcefn detect(&self, project_path: &Path) -> bool
fn detect(&self, project_path: &Path) -> bool
Check if this adapter applies to the given project directory.
Typically checks for the presence of a specific lockfile or config file.
Sourcefn bloat_dirs(&self, project_path: &Path) -> Vec<BloatDir>
fn bloat_dirs(&self, project_path: &Path) -> Vec<BloatDir>
List all bloat directories this adapter manages in the given project.
Only returns directories that actually exist on disk.
Sourcefn enforce_lockfile(
&self,
project_path: &Path,
policy: EnforcePolicy,
) -> Result<()>
fn enforce_lockfile( &self, project_path: &Path, policy: EnforcePolicy, ) -> Result<()>
Prove the lockfile can rebuild what is about to be deleted.
This is a safety-critical method. It MUST succeed before any bloat directory is deleted. If this fails, deletion for this adapter is aborted.
See EnforcePolicy for the one rule every adapter follows.
Sourcefn restore(&self, project_path: &Path, timeout: Duration) -> Result<()>
fn restore(&self, project_path: &Path, timeout: Duration) -> Result<()>
Restore dependencies from the lockfile (for dev-prune restore).
timeout is threaded explicitly for the same reason EnforcePolicy is: the
restore path used to burn the compiled-in default regardless of
command_timeout_secs, and a full npm ci on a large tree needs the raised
timeout far more often than a verify does.
Provided Methods§
Sourcefn restore_named(
&self,
project_path: &Path,
dir_name: &str,
runtime: Option<&str>,
timeout: Duration,
) -> Result<()>
fn restore_named( &self, project_path: &Path, dir_name: &str, runtime: Option<&str>, timeout: Duration, ) -> Result<()>
PackageManager::restore, told the name the pruned directory had.
Most managers have exactly one possible directory name and ignore this. venv does
not: it prunes any folder carrying a pyvenv.cfg — venv, env, my_env — and
without the recorded name it would rebuild the environment as .venv, leaving
every activate script, IDE interpreter path and Makefile pointing at nothing.
runtime is the interpreter tag recorded when the directory was deleted (see
crate::config::PrunedDir::runtime). None means nothing was recorded, or the
caller has decided this machine cannot honour it; either way the manager should
fall back to whatever it would have used before.
Sourcefn runtime_tag(&self, project_path: &Path, dir_name: &str) -> Option<String>
fn runtime_tag(&self, project_path: &Path, dir_name: &str) -> Option<String>
The language runtime a bloat directory is built against, recorded at prune time.
Only the Python managers answer this. A node_modules is rebuilt by the same
npm ci whichever Node is installed, and cargo and go pin their toolchains in
files that are already in the repository — but a virtual environment is a
copy of one specific interpreter, and rebuilding it on a different one silently
changes which wheels resolve.
dir_name is the directory about to be deleted, relative to project_path.
Sourcefn lockfiles(&self) -> &'static [&'static str]
fn lockfiles(&self) -> &'static [&'static str]
The file this manager rebuilds its bloat directory from.
Two callers. Conflict resolution breaks ties between managers that share a bloat
directory — npm, pnpm, yarn and bun all own the same node_modules — by comparing
these files’ timestamps. devp doctor names them, because a missing one is the
most common reason a project is not pruneable.
More than one entry means the manager accepts any of them (bun’s binary and text lockfiles). An empty slice means the manager has no single file to point at.
Sourcefn drift(&self, project_path: &Path) -> Vec<DriftReport>
fn drift(&self, project_path: &Path) -> Vec<DriftReport>
Installed-but-unrecorded packages, as data instead of a refusal.
The same comparison PackageManager::enforce_lockfile refuses a prune on,
surfaced early so devp status --drift can point at the problem before a prune
is ever attempted. Runs nothing and writes nothing. An empty answer means
“nothing detected”, not “proven clean” — most managers have no cheap way to
compare and say nothing here.
Sourcefn opt_in(&self) -> bool
fn opt_in(&self) -> bool
Whether this adapter is inert until the user enables it in settings.
Adapters whose directory is compiler output answer true — cargo, gradle,
maven and swift. Theirs come back by recompiling the project, which costs far
more than a dependency reinstall, so nobody should find them deleted without
having asked. The engine also holds them to the longer build_idle_days idle
window.
The test is what it costs to get the directory back, not whether a lockfile
exists: cargo has as good a lockfile as npm does, and target/ still has to be
rebuilt from source.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".