changeset_operations/operations/release/
types.rs1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use changeset_core::{Changeset, PackageInfo, PrereleaseSpec};
5use changeset_project::GraduationState;
6use indexmap::IndexMap;
7use semver::Version;
8
9use crate::types::{PackageReleaseConfig, PackageVersion};
10
11pub struct ReleaseInput {
12 dry_run: bool,
13 convert_inherited: bool,
14 no_commit: bool,
15 no_tags: bool,
16 keep_changesets: bool,
17 force: bool,
18 per_package_config: HashMap<String, PackageReleaseConfig>,
19 global_prerelease: Option<PrereleaseSpec>,
20 graduate_all: bool,
21}
22
23impl ReleaseInput {
24 #[must_use]
25 pub fn builder() -> ReleaseInputBuilder {
26 ReleaseInputBuilder::new()
27 }
28
29 #[must_use]
30 pub fn dry_run(&self) -> bool {
31 self.dry_run
32 }
33
34 #[must_use]
35 pub fn convert_inherited(&self) -> bool {
36 self.convert_inherited
37 }
38
39 #[must_use]
40 pub fn no_commit(&self) -> bool {
41 self.no_commit
42 }
43
44 #[must_use]
45 pub fn no_tags(&self) -> bool {
46 self.no_tags
47 }
48
49 #[must_use]
50 pub fn keep_changesets(&self) -> bool {
51 self.keep_changesets
52 }
53
54 #[must_use]
55 pub fn force(&self) -> bool {
56 self.force
57 }
58
59 #[must_use]
60 pub fn per_package_config(&self) -> &HashMap<String, PackageReleaseConfig> {
61 &self.per_package_config
62 }
63
64 #[must_use]
65 pub fn global_prerelease(&self) -> Option<&PrereleaseSpec> {
66 self.global_prerelease.as_ref()
67 }
68
69 #[must_use]
70 pub fn graduate_all(&self) -> bool {
71 self.graduate_all
72 }
73}
74
75#[derive(Default)]
76pub struct ReleaseInputBuilder {
77 dry_run: bool,
78 convert_inherited: bool,
79 no_commit: bool,
80 no_tags: bool,
81 keep_changesets: bool,
82 force: bool,
83 per_package_config: HashMap<String, PackageReleaseConfig>,
84 global_prerelease: Option<PrereleaseSpec>,
85 graduate_all: bool,
86}
87
88impl ReleaseInputBuilder {
89 #[must_use]
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 #[must_use]
95 pub fn dry_run(mut self, v: bool) -> Self {
96 self.dry_run = v;
97 self
98 }
99
100 #[must_use]
101 pub fn convert_inherited(mut self, v: bool) -> Self {
102 self.convert_inherited = v;
103 self
104 }
105
106 #[must_use]
107 pub fn no_commit(mut self, v: bool) -> Self {
108 self.no_commit = v;
109 self
110 }
111
112 #[must_use]
113 pub fn no_tags(mut self, v: bool) -> Self {
114 self.no_tags = v;
115 self
116 }
117
118 #[must_use]
119 pub fn keep_changesets(mut self, v: bool) -> Self {
120 self.keep_changesets = v;
121 self
122 }
123
124 #[must_use]
125 pub fn force(mut self, v: bool) -> Self {
126 self.force = v;
127 self
128 }
129
130 #[must_use]
131 pub fn per_package_config(mut self, v: HashMap<String, PackageReleaseConfig>) -> Self {
132 self.per_package_config = v;
133 self
134 }
135
136 #[must_use]
137 pub fn global_prerelease(mut self, v: Option<PrereleaseSpec>) -> Self {
138 self.global_prerelease = v;
139 self
140 }
141
142 #[must_use]
143 pub fn graduate_all(mut self, v: bool) -> Self {
144 self.graduate_all = v;
145 self
146 }
147
148 #[must_use]
149 pub fn build(self) -> ReleaseInput {
150 ReleaseInput {
151 dry_run: self.dry_run,
152 convert_inherited: self.convert_inherited,
153 no_commit: self.no_commit,
154 no_tags: self.no_tags,
155 keep_changesets: self.keep_changesets,
156 force: self.force,
157 per_package_config: self.per_package_config,
158 global_prerelease: self.global_prerelease,
159 graduate_all: self.graduate_all,
160 }
161 }
162}
163
164#[derive(Debug, Clone)]
165pub struct ChangelogUpdate {
166 pub path: PathBuf,
167 pub package: Option<String>,
168 pub version: Version,
169 pub created: bool,
170}
171
172#[derive(Debug, Clone)]
173pub struct CommitResult {
174 pub sha: String,
175 pub message: String,
176}
177
178#[derive(Debug, Clone)]
179pub struct TagResult {
180 pub name: String,
181 pub target_sha: String,
182}
183
184#[derive(Debug, Clone, Default)]
185pub struct GitOperationResult {
186 pub commit: Option<CommitResult>,
187 pub tags_created: Vec<TagResult>,
188 pub changesets_deleted: Vec<PathBuf>,
189}
190
191#[derive(Debug, Clone)]
192pub struct ReleaseOutput {
193 pub planned_releases: Vec<PackageVersion>,
194 pub unchanged_packages: Vec<String>,
195 pub changesets_consumed: Vec<PathBuf>,
196 pub changelog_updates: Vec<ChangelogUpdate>,
197 pub git_result: Option<GitOperationResult>,
198}
199
200#[derive(Debug)]
201pub enum ReleaseOutcome {
202 DryRun(ReleaseOutput),
203 Executed(ReleaseOutput),
204 NoChangesets,
205}
206
207pub(super) struct GitOptions {
208 pub(super) should_commit: bool,
209 pub(super) should_create_tags: bool,
210 pub(super) should_delete_changesets: bool,
211}
212
213pub(super) enum PrepareResult {
214 Ready(ReleaseContext),
215 EarlyReturn(ReleaseOutcome),
216}
217
218#[derive(Debug, Clone, Copy)]
219pub(super) struct ReleaseClassification {
220 pub(super) is_prerelease_graduation: bool,
221 pub(super) is_graduating: bool,
222 pub(super) is_prerelease_release: bool,
223}
224
225pub(super) struct ReleaseContext {
226 pub(super) project: changeset_project::CargoProject,
227 pub(super) root_config: changeset_project::RootChangesetConfig,
228 pub(super) changeset_dir: PathBuf,
229 pub(super) changeset_files: Vec<PathBuf>,
230 pub(super) prerelease_state: Option<changeset_project::PrereleaseState>,
231 pub(super) graduation_state: Option<GraduationState>,
232 pub(super) per_package_config: HashMap<String, PackageReleaseConfig>,
233 pub(super) classification: ReleaseClassification,
234 pub(super) git_options: GitOptions,
235 pub(super) inherited_packages: Vec<String>,
236}
237
238#[derive(Debug, Clone)]
239pub(crate) struct ChangelogFileState {
240 pub(crate) path: PathBuf,
241 pub(crate) original_content: Option<String>,
242 pub(crate) file_existed: bool,
243}
244
245#[derive(Debug, Clone)]
246pub(crate) struct ChangesetFileState {
247 pub(crate) path: PathBuf,
248 pub(crate) original_consumed_status: Option<String>,
249 pub(crate) backup: Option<Changeset>,
250}
251
252pub(super) struct ReleasePlan {
253 pub(super) output: ReleaseOutput,
254 pub(super) package_lookup: IndexMap<String, PackageInfo>,
255 pub(super) changelog_backups: Vec<ChangelogFileState>,
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::types::PackageReleaseConfig;
262 use changeset_core::PrereleaseSpec;
263 use std::collections::HashMap;
264
265 #[test]
266 fn builder_defaults_all_false() {
267 let input = ReleaseInput::builder().build();
268
269 assert!(!input.dry_run());
270 assert!(!input.convert_inherited());
271 assert!(!input.no_commit());
272 assert!(!input.no_tags());
273 assert!(!input.keep_changesets());
274 assert!(!input.force());
275 assert!(!input.graduate_all());
276 assert!(input.per_package_config().is_empty());
277 assert!(input.global_prerelease().is_none());
278 }
279
280 #[test]
281 fn builder_sets_dry_run() {
282 let input = ReleaseInput::builder().dry_run(true).build();
283
284 assert!(input.dry_run());
285 }
286
287 #[test]
288 fn builder_sets_global_prerelease() {
289 let input = ReleaseInput::builder()
290 .global_prerelease(Some(PrereleaseSpec::Alpha))
291 .build();
292
293 let prerelease = input.global_prerelease();
294 assert!(prerelease.is_some());
295 assert_eq!(
296 prerelease.expect("should have prerelease").identifier(),
297 "alpha"
298 );
299 }
300
301 #[test]
302 fn builder_sets_per_package_config() {
303 let mut map = HashMap::new();
304 map.insert(
305 "crate-a".to_string(),
306 PackageReleaseConfig {
307 prerelease: None,
308 graduate_zero: false,
309 },
310 );
311
312 let input = ReleaseInput::builder().per_package_config(map).build();
313
314 assert!(input.per_package_config().contains_key("crate-a"));
315 }
316}