Skip to main content

aube_resolver/
direct_dep_info.rs

1//! Per-direct-dep packument facts the install summary printer surfaces
2//! inline with the `+ name@version` listing — currently deprecation
3//! status and the registry `latest` dist-tag when it differs from the
4//! resolved version. The data has to be snapshotted before the resolver
5//! (which owns the packument cache) is dropped at the end of resolution.
6
7use crate::Resolver;
8use aube_lockfile::LockfileGraph;
9use std::collections::HashMap;
10
11/// Subset of packument facts the install summary printer wants to
12/// render next to a direct-dependency line. Returned only for direct
13/// deps where at least one signal is set — the printer skips the badge
14/// column when [`Resolver::direct_dep_info`]'s map has no entry.
15///
16/// Deprecation is a bare flag (not the message string) by design: the
17/// full per-version `deprecated` text already surfaces via the WARN
18/// pipeline in [`crate::deprecations`][crate-deprecations] above the
19/// summary, so the badge column just signals "this direct dep is one
20/// of the WARN lines you saw" without duplicating the message.
21///
22/// [crate-deprecations]: https://github.com/endevco/aube/blob/main/crates/aube/src/deprecations.rs
23#[derive(Debug, Clone, Default)]
24pub struct DirectDepInfo {
25    /// True when the packument marks the *resolved* version as
26    /// deprecated. The actual message is intentionally not carried
27    /// here — see the struct docs.
28    pub deprecated: bool,
29    /// The registry's `dist-tags.latest` for this package, but only
30    /// when it differs from the resolved version. `None` when latest
31    /// matches the resolved version, when the registry omits `latest`
32    /// (common on private registries), or when the dep wasn't resolved
33    /// from a packument (git / file / link / remote tarball).
34    pub latest: Option<String>,
35}
36
37impl Resolver {
38    /// Snapshot per-direct-dep packument facts so the install summary
39    /// printer can render them inline after the resolver — and its
40    /// packument cache — is dropped. Keys are `DirectDep::dep_path`;
41    /// importer direct deps don't carry peer-context suffixes, so the
42    /// key matches the `LockfileGraph.packages` entry 1:1.
43    ///
44    /// Skips deps whose packument wasn't fetched (frozen-lockfile reuse,
45    /// non-registry sources) and deps whose registry didn't publish a
46    /// `latest` dist-tag. Returns only entries where at least one signal
47    /// is set so the caller's printer can use `get(dep_path)` as the
48    /// "should I render badges?" check.
49    pub fn direct_dep_info(&self, graph: &LockfileGraph) -> HashMap<String, DirectDepInfo> {
50        let mut out: HashMap<String, DirectDepInfo> = HashMap::new();
51        for deps in graph.importers.values() {
52            for dep in deps {
53                let Some(pkg) = graph.packages.get(&dep.dep_path) else {
54                    continue;
55                };
56                if pkg.local_source.is_some() {
57                    continue;
58                }
59                let Some(packument) = self.cache.get(pkg.registry_name()) else {
60                    continue;
61                };
62                let deprecated = packument
63                    .versions
64                    .get(&pkg.version)
65                    .is_some_and(|v| v.deprecated.is_some());
66                let latest = packument
67                    .dist_tags
68                    .get("latest")
69                    .filter(|l| l.as_str() != pkg.version.as_str())
70                    .cloned();
71                if deprecated || latest.is_some() {
72                    out.insert(dep.dep_path.clone(), DirectDepInfo { deprecated, latest });
73                }
74            }
75        }
76        out
77    }
78}