Skip to main content

zoi_cli/cmd/
pin.rs

1//! Pinning packages to specific versions.
2
3use std::collections::HashMap;
4
5use anyhow::Result;
6use colored::Colorize;
7
8use crate::pkg::{config, pin, resolve};
9
10/// Pins a package to a specific version.
11///
12/// This prevents the package from being updated to a newer version during
13/// general upgrades, and records the version as the preferred global version
14/// for the binaries the package provides.
15///
16/// Pinning touches two different stores, because they answer different
17/// questions:
18/// - `pinned.json` answers "may this package be upgraded?", which is what keeps
19///   a general `zoi update` from moving it off the pinned version.
20/// - The `versions:` map in `config.yaml` answers "which version should the
21///   shim launch?", and is the last fallback in shim resolution after the env
22///   var, plugin hook, `zoi.lua` and `.tool-versions` are consulted.
23///
24/// Pinning both keeps the two consistent. Setting only the first would let the
25/// package stay installed at the old version while the shim still preferred a
26/// newer one.
27///
28/// # Errors
29///
30/// Returns an error if the package cannot be resolved, or if the pinned
31/// packages file or the user config cannot be read or written.
32pub fn run(source: &str, version: &str, yes: bool) -> Result<()> {
33    let (pkg, _, _, _, _registry_handle, _, _) =
34        resolve::resolve_package_and_version(source, None, false, yes)?;
35    let mut pinned_packages = pin::get_pinned_packages()?;
36
37    if pinned_packages.iter().any(|p| p.source == pkg.name) {
38        println!(
39            "Package '{}' is already pinned. Unpin it first to change the \
40             version.",
41            pkg.name
42        );
43        return Ok(());
44    }
45
46    let new_pin = pin::PinnedPackage {
47        source: pkg.name.clone(),
48        version: version.to_string()
49    };
50    pinned_packages.push(new_pin);
51    pin::write_pinned_packages(&pinned_packages)?;
52
53    // Record the preferred version for every binary the package provides. The
54    // map is keyed by binary name because that is what the shim looks up. A
55    // package with no declared `bins` still gets an entry under its own name,
56    // which covers the common case where the binary and the package share a
57    // name.
58    let mut versions: HashMap<String, String> = pkg
59        .bins
60        .clone()
61        .unwrap_or_default()
62        .into_iter()
63        .map(|bin| (bin, version.to_string()))
64        .collect();
65    if versions.is_empty() {
66        versions.insert(pkg.name.clone(), version.to_string());
67    }
68    config::update_global_versions(versions)?;
69
70    println!("Pinned {}@{}", pkg.name.green(), version.yellow());
71    Ok(())
72}