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