1use crate::adapters::{
9 CargoAdapter, FormatAdapter, JsonFormat, MsbuildAdapter, NpmAdapter, PubAdapter, PythonAdapter,
10 TomlFormat, YamlFormat,
11};
12use crate::config::Config;
13use crate::error::Result;
14use crate::intent::Intent;
15use crate::model::{Adapter, Bump, ProjectionMode};
16use crate::ownership::check_physical_ownership;
17use crate::version::{aggregate_bumps, aggregate_graduations, resolve_versions, VersionRepository};
18use crate::workspace_files::read_contained;
19use semver::Version;
20use std::path::Path;
21
22pub const MISSING_BASELINE_CODE: &str = "missing-baseline";
24
25pub const MISSING_BASELINE_NEXT_ACTION: &str = "intentional tag --baseline";
27
28#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ReleaseUnitStatus {
31 pub id: String,
33 pub current: Version,
35 pub next: Version,
37 pub bump: Bump,
39 pub graduates: bool,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct WorkspaceStatus {
46 pub intents: Vec<String>,
48 pub release_units: Vec<ReleaseUnitStatus>,
50 pub drift: Vec<Drift>,
52 pub missing_baselines: Vec<String>,
54 pub tag_record_issues: Vec<String>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct Drift {
61 pub release_unit: String,
63 pub file: std::path::PathBuf,
65 pub expected: String,
67 pub actual: String,
69}
70
71impl WorkspaceStatus {
72 pub fn load(root: &Path) -> Result<Self> {
74 let config = Config::load(root)?;
75 let intents = Intent::load_all(root, &config)?;
76 Self::compute(root, &config, &intents)
77 }
78
79 pub fn compute(root: &Path, config: &Config, intents: &[Intent]) -> Result<Self> {
81 check_physical_ownership(root, config)?;
82 let repository = VersionRepository::discover(root)?;
83 let declared = aggregate_bumps(intents.iter().map(|intent| &intent.release_units));
84 let graduating = aggregate_graduations(intents.iter().map(|intent| &intent.graduates));
85 let mut current_versions = std::collections::BTreeMap::new();
86 let mut missing_baselines = Vec::new();
87 for id in config.release_units.keys() {
88 let (_, primary) = config.primary_tag(id)?;
89 let current = repository.current_version(id, &primary.template)?;
90 if !repository.has_matching_tag(id, &primary.template)? {
91 missing_baselines.push(id.clone());
92 }
93 current_versions.insert(id.clone(), current);
94 }
95 let resolved = resolve_versions(config, &declared, &graduating, ¤t_versions)?;
96 let mut release_units = Vec::with_capacity(config.release_units.len());
97 let mut drift = Vec::new();
98 for (id, release_unit) in &config.release_units {
99 let versions = &resolved[id];
100 let expected = versions.current.to_string();
101 release_units.push(ReleaseUnitStatus {
102 id: id.clone(),
103 current: versions.current.clone(),
104 next: versions.next.clone(),
105 bump: versions.bump,
106 graduates: versions.graduates,
107 });
108 for projection in &release_unit.projections {
109 if projection.mode == ProjectionMode::None || projection.adapter == Adapter::Go {
110 continue;
111 }
112 let relative = projection.file.workspace_path(&release_unit.path);
113 let text = read_contained(root, &relative)?;
114 let actual = read_projection_version(root, &relative, projection, &text)?;
115 if actual != expected {
116 drift.push(Drift {
117 release_unit: id.clone(),
118 file: relative,
119 expected: expected.clone(),
120 actual,
121 });
122 }
123 }
124 }
125 Ok(Self {
126 intents: intents.iter().map(|intent| intent.id.clone()).collect(),
127 release_units,
128 drift,
129 missing_baselines,
130 tag_record_issues: crate::tag::tag_record_issues(root, config)?,
131 })
132 }
133}
134
135pub(crate) fn read_projection_version(
136 root: &Path,
137 relative: &Path,
138 projection: &crate::Projection,
139 text: &str,
140) -> Result<String> {
141 match projection.adapter {
142 Adapter::Npm => NpmAdapter.version(text),
143 Adapter::Cargo => match CargoAdapter.version(text)? {
144 Some(version) => Ok(version),
145 None => {
146 let workspace_path = crate::apply::workspace_manifest(root, relative)?;
147 let workspace = read_contained(root, &workspace_path)?;
148 TomlFormat.read_text(&workspace, "/workspace/package/version")
149 }
150 },
151 Adapter::Pub => PubAdapter.version(text),
152 Adapter::Python => PythonAdapter.version(text),
153 Adapter::Msbuild => MsbuildAdapter.version(text),
154 Adapter::Json => JsonFormat.read_text(
155 text,
156 projection.pointer.as_deref().expect("validated pointer"),
157 ),
158 Adapter::Toml => TomlFormat.read_text(
159 text,
160 projection.pointer.as_deref().expect("validated pointer"),
161 ),
162 Adapter::Yaml => YamlFormat.read_text(
163 text,
164 projection.pointer.as_deref().expect("validated pointer"),
165 ),
166 Adapter::Go => unreachable!("Go has no manifest version"),
167 }
168}