changeset_operations/operations/release/
config_builder.rs1use std::collections::HashMap;
2
3use changeset_core::{PackageInfo, PrereleaseSpec};
4use changeset_project::GraduationState;
5use changeset_version::is_zero_version;
6
7use super::validator::ReleaseCliInput;
8use crate::types::PackageReleaseConfig;
9
10#[derive(Debug, Clone)]
11#[must_use]
12pub struct ValidatedReleaseConfig {
13 per_package: HashMap<String, PackageReleaseConfig>,
14}
15
16impl ValidatedReleaseConfig {
17 #[must_use]
18 pub fn per_package(&self) -> &HashMap<String, PackageReleaseConfig> {
19 &self.per_package
20 }
21
22 #[must_use]
23 pub fn into_per_package(self) -> HashMap<String, PackageReleaseConfig> {
24 self.per_package
25 }
26}
27
28#[derive(Default)]
29pub(super) struct ParsedPrereleaseCache {
30 pub(super) specs: HashMap<String, PrereleaseSpec>,
31}
32
33pub(super) fn build_release_config(
34 cli_input: &ReleaseCliInput,
35 parsed_cache: &ParsedPrereleaseCache,
36 graduation_state: Option<&GraduationState>,
37 packages: &[PackageInfo],
38) -> ValidatedReleaseConfig {
39 let mut per_package = HashMap::new();
40
41 for (pkg, spec) in &parsed_cache.specs {
42 per_package
43 .entry(pkg.clone())
44 .or_insert_with(PackageReleaseConfig::default)
45 .set_prerelease(spec.clone());
46 }
47
48 for (pkg, spec) in &cli_input.cli_prerelease {
49 per_package
50 .entry(pkg.clone())
51 .or_insert_with(PackageReleaseConfig::default)
52 .set_prerelease(spec.clone());
53 }
54
55 if let Some(global) = cli_input.global_prerelease.as_ref() {
56 for pkg in packages {
57 per_package
58 .entry(pkg.name().clone())
59 .or_insert_with(PackageReleaseConfig::default)
60 .set_prerelease(global.clone());
61 }
62 }
63
64 if let Some(state) = graduation_state {
65 for pkg in state.iter() {
66 per_package
67 .entry(pkg.to_string())
68 .or_insert_with(PackageReleaseConfig::default)
69 .set_graduate_zero();
70 }
71 }
72
73 for pkg in &cli_input.cli_graduate {
74 per_package
75 .entry(pkg.clone())
76 .or_insert_with(PackageReleaseConfig::default)
77 .set_graduate_zero();
78 }
79
80 if cli_input.graduate_all {
81 for pkg in packages {
82 if is_zero_version(pkg.version()) {
83 per_package
84 .entry(pkg.name().clone())
85 .or_insert_with(PackageReleaseConfig::default)
86 .set_graduate_zero();
87 }
88 }
89 }
90
91 ValidatedReleaseConfig { per_package }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::mocks::make_package;
98 use changeset_project::GraduationState;
99
100 fn empty_cli_input() -> ReleaseCliInput {
101 ReleaseCliInput::default()
102 }
103
104 #[test]
105 fn empty_inputs_produce_empty_config() {
106 let cli_input = empty_cli_input();
107 let cache = ParsedPrereleaseCache::default();
108 let packages = vec![make_package("crate-a", "1.0.0")];
109
110 let config = build_release_config(&cli_input, &cache, None, &packages);
111
112 assert!(config.per_package().is_empty());
113 }
114
115 #[test]
116 fn toml_prerelease_specs_are_applied() {
117 let cli_input = empty_cli_input();
118 let mut cache = ParsedPrereleaseCache::default();
119 cache
120 .specs
121 .insert("crate-a".to_string(), PrereleaseSpec::Alpha);
122 let packages = vec![make_package("crate-a", "1.0.0")];
123
124 let config = build_release_config(&cli_input, &cache, None, &packages);
125
126 assert_eq!(
127 config.per_package()["crate-a"].prerelease(),
128 Some(&PrereleaseSpec::Alpha)
129 );
130 }
131
132 #[test]
133 fn cli_prerelease_overrides_toml() {
134 let mut cli_input = empty_cli_input();
135 cli_input
136 .cli_prerelease
137 .insert("crate-a".to_string(), PrereleaseSpec::Beta);
138 let mut cache = ParsedPrereleaseCache::default();
139 cache
140 .specs
141 .insert("crate-a".to_string(), PrereleaseSpec::Alpha);
142 let packages = vec![make_package("crate-a", "1.0.0")];
143
144 let config = build_release_config(&cli_input, &cache, None, &packages);
145
146 assert_eq!(
147 config.per_package()["crate-a"].prerelease(),
148 Some(&PrereleaseSpec::Beta)
149 );
150 }
151
152 #[test]
153 fn global_prerelease_applies_to_all_packages() {
154 let mut cli_input = empty_cli_input();
155 cli_input.global_prerelease = Some(PrereleaseSpec::Rc);
156 let cache = ParsedPrereleaseCache::default();
157 let packages = vec![
158 make_package("crate-a", "1.0.0"),
159 make_package("crate-b", "2.0.0"),
160 ];
161
162 let config = build_release_config(&cli_input, &cache, None, &packages);
163
164 assert_eq!(
165 config.per_package()["crate-a"].prerelease(),
166 Some(&PrereleaseSpec::Rc)
167 );
168 assert_eq!(
169 config.per_package()["crate-b"].prerelease(),
170 Some(&PrereleaseSpec::Rc)
171 );
172 }
173
174 #[test]
175 fn graduation_state_marks_packages() {
176 let cli_input = empty_cli_input();
177 let cache = ParsedPrereleaseCache::default();
178 let mut state = GraduationState::default();
179 state.add("crate-a".to_string());
180 let packages = vec![make_package("crate-a", "0.5.0")];
181
182 let config = build_release_config(&cli_input, &cache, Some(&state), &packages);
183
184 assert!(config.per_package()["crate-a"].graduate_zero());
185 }
186
187 #[test]
188 fn graduate_all_marks_zero_version_packages() {
189 let mut cli_input = empty_cli_input();
190 cli_input.graduate_all = true;
191 let cache = ParsedPrereleaseCache::default();
192 let packages = vec![
193 make_package("crate-a", "0.5.0"),
194 make_package("crate-b", "1.0.0"),
195 ];
196
197 let config = build_release_config(&cli_input, &cache, None, &packages);
198
199 assert!(config.per_package()["crate-a"].graduate_zero());
200 assert!(!config.per_package().contains_key("crate-b"));
201 }
202
203 #[test]
204 fn cli_graduate_marks_specific_packages() {
205 let mut cli_input = empty_cli_input();
206 cli_input.cli_graduate.insert("crate-a".to_string());
207 let cache = ParsedPrereleaseCache::default();
208 let packages = vec![make_package("crate-a", "0.5.0")];
209
210 let config = build_release_config(&cli_input, &cache, None, &packages);
211
212 assert!(config.per_package()["crate-a"].graduate_zero());
213 }
214}