Skip to main content

changeset_operations/operations/release/
config_builder.rs

1use 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}