1#![warn(dead_code)]
2#![allow(unused_imports)]
3#![allow(clippy::all)]
4
5use semver::{BuildMetadata, Prerelease, Version as SemVersion};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use std::collections::HashMap;
13use std::fs::OpenOptions;
14use std::io::{BufWriter, Write};
15use std::path::PathBuf;
16
17use crate::conventional::ConventionalPackage;
18
19use super::changes::{get_package_change, init_changes, Change};
20use super::conventional::{get_conventional_for_package, ConventionalPackageOptions};
21use super::git::{
22 git_add_all, git_all_files_changed_since_sha, git_commit, git_config, git_current_branch,
23 git_current_sha, git_fetch_all, git_push, git_tag,
24};
25use super::packages::PackageInfo;
26use super::packages::{get_package_info, get_packages};
27use super::paths::get_project_root_path;
28
29#[cfg(feature = "napi")]
30#[napi(string_enum)]
31#[derive(Debug, Deserialize, Serialize, PartialEq)]
32pub enum Bump {
33 Major,
34 Minor,
35 Patch,
36 Snapshot,
37}
38
39#[cfg(not(feature = "napi"))]
40#[derive(Debug, Clone, Deserialize, Serialize, Copy, PartialEq)]
41pub enum Bump {
43 Major,
44 Minor,
45 Patch,
46 Snapshot,
47}
48
49#[cfg(feature = "napi")]
50#[napi(object)]
51#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
52pub struct BumpOptions {
53 pub changes: Vec<Change>,
54 pub since: Option<String>,
55 pub release_as: Option<Bump>,
56 pub fetch_all: Option<bool>,
57 pub fetch_tags: Option<bool>,
58 pub sync_deps: Option<bool>,
59 pub push: Option<bool>,
60 pub cwd: Option<String>,
61}
62
63#[cfg(not(feature = "napi"))]
64#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
65pub struct BumpOptions {
67 pub changes: Vec<Change>,
68 pub since: Option<String>,
69 pub release_as: Option<Bump>,
70 pub fetch_all: Option<bool>,
71 pub fetch_tags: Option<bool>,
72 pub sync_deps: Option<bool>,
73 pub push: Option<bool>,
74 pub cwd: Option<String>,
75}
76
77#[cfg(not(feature = "napi"))]
78#[derive(Debug, Clone, Deserialize, Serialize)]
79pub struct BumpPackage {
81 pub from: String,
82 pub to: String,
83 pub package_info: PackageInfo,
84 pub conventional_commits: Value,
85}
86
87#[cfg(feature = "napi")]
88#[napi(object)]
89#[derive(Debug, Clone, Deserialize, Serialize)]
90pub struct BumpPackage {
91 pub from: String,
92 pub to: String,
93 pub package_info: PackageInfo,
94 pub conventional_commits: Value,
95}
96
97#[cfg(not(feature = "napi"))]
98#[derive(Debug, Clone, Deserialize, Serialize)]
99pub struct RecommendBumpPackage {
101 pub from: String,
102 pub to: String,
103 pub package_info: PackageInfo,
104 pub conventional: ConventionalPackage,
105 pub changed_files: Vec<String>,
106 pub deploy_to: Vec<String>,
107}
108
109#[cfg(feature = "napi")]
110#[napi(object)]
111#[derive(Debug, Clone, Deserialize, Serialize)]
112pub struct RecommendBumpPackage {
114 pub from: String,
115 pub to: String,
116 pub package_info: PackageInfo,
117 pub conventional: ConventionalPackage,
118 pub changed_files: Vec<String>,
119 pub deploy_to: Vec<String>,
120}
121
122impl Bump {
123 fn bump_major(version: String) -> SemVersion {
125 let mut sem_version = SemVersion::parse(&version).unwrap();
126 sem_version.major += 1;
127 sem_version.minor = 0;
128 sem_version.patch = 0;
129 sem_version.pre = Prerelease::EMPTY;
130 sem_version.build = BuildMetadata::EMPTY;
131 sem_version
132 }
133
134 fn bump_minor(version: String) -> SemVersion {
136 let mut sem_version = SemVersion::parse(&version).unwrap();
137 sem_version.minor += 1;
138 sem_version.patch = 0;
139 sem_version.pre = Prerelease::EMPTY;
140 sem_version.build = BuildMetadata::EMPTY;
141 sem_version
142 }
143
144 fn bump_patch(version: String) -> SemVersion {
146 let mut sem_version = SemVersion::parse(&version).unwrap();
147 sem_version.patch += 1;
148 sem_version.pre = Prerelease::EMPTY;
149 sem_version.build = BuildMetadata::EMPTY;
150 sem_version
151 }
152
153 fn bump_snapshot(version: String) -> SemVersion {
155 let sha = git_current_sha(None);
156 let alpha = format!("alpha.{}.{}", 0, sha);
157
158 let mut sem_version = SemVersion::parse(&version).unwrap();
159 sem_version.pre = Prerelease::new(alpha.as_str()).unwrap_or(Prerelease::EMPTY);
160 sem_version.build = BuildMetadata::EMPTY;
161 sem_version
162 }
163}
164
165pub fn get_package_recommend_bump(
166 package_info: &PackageInfo,
167 root: &String,
168 options: Option<BumpOptions>,
169) -> RecommendBumpPackage {
170 let ref current_branch =
171 git_current_branch(Some(root.to_string())).unwrap_or(String::from("origin/main"));
172
173 let package_version = &package_info.version.to_string();
174 let package_name = &package_info.name.to_string();
175 let package_change = get_package_change(
176 package_name.to_string(),
177 current_branch.to_string(),
178 Some(root.to_string()),
179 );
180
181 let settings = options.unwrap_or_else(|| BumpOptions {
182 changes: vec![],
183 since: None,
184 release_as: None,
185 fetch_all: None,
186 fetch_tags: None,
187 sync_deps: None,
188 push: None,
189 cwd: None,
190 });
191
192 let ref since = settings.since.unwrap_or(String::from("origin/main"));
193
194 let release_as = settings
195 .release_as
196 .unwrap_or_else(|| match package_change.to_owned() {
197 Some(change) => change.release_as,
198 None => Bump::Patch,
199 });
200
201 let deploy_to = match package_change.to_owned() {
202 Some(change) => change.deploy,
203 None => vec![String::from("production")],
204 };
205
206 let fetch_all = settings.fetch_all.unwrap_or(false);
207
208 let semversion = match release_as {
209 Bump::Major => Bump::bump_major(package_version.to_string()),
210 Bump::Minor => Bump::bump_minor(package_version.to_string()),
211 Bump::Patch => Bump::bump_patch(package_version.to_string()),
212 Bump::Snapshot => Bump::bump_snapshot(package_version.to_string()),
213 };
214
215 let changed_files = git_all_files_changed_since_sha(since.to_string(), Some(root.to_string()));
216 let ref version = semversion.to_string();
217
218 let conventional = get_conventional_for_package(
219 &package_info,
220 Some(fetch_all),
221 Some(root.to_string()),
222 &Some(ConventionalPackageOptions {
223 version: Some(version.to_string()),
224 title: Some("# What changed?".to_string()),
225 }),
226 );
227
228 RecommendBumpPackage {
229 from: package_version.to_string(),
230 to: version.to_string(),
231 package_info: package_info.to_owned(),
232 conventional: conventional.to_owned(),
233 changed_files: changed_files.to_owned(),
234 deploy_to: deploy_to.to_owned(),
235 }
236}
237
238pub fn get_bumps(options: &BumpOptions) -> Vec<BumpPackage> {
240 let ref root = match options.cwd {
241 Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
242 None => get_project_root_path(None).unwrap(),
243 };
244
245 if options.fetch_tags.is_some() {
246 git_fetch_all(Some(root.to_string()), options.fetch_tags)
247 .expect("No possible to fetch tags");
248 }
249
250 let since = match options.since {
251 Some(ref since) => since.to_string(),
252 None => String::from("origin/main"),
253 };
254
255 let current_branch = git_current_branch(Some(root.to_string())).unwrap_or(String::from("main"));
256
257 let ref packages = get_packages(Some(root.to_string()));
258 let changed_packages = packages
259 .iter()
260 .filter(|package| {
261 options
262 .changes
263 .iter()
264 .any(|change| change.package == package.name)
265 })
266 .map(|package| package.to_owned())
267 .collect::<Vec<PackageInfo>>();
268
269 if changed_packages.len() == 0 {
270 return vec![];
271 }
272
273 let mut bump_changes = HashMap::new();
274 let mut bump_dependencies = HashMap::new();
275
276 for changed_package in changed_packages.iter() {
277 let change = options
278 .changes
279 .iter()
280 .find(|change| change.package == changed_package.name);
281
282 if change.is_some() {
283 let release_as = match Some(current_branch.contains("main")) {
284 Some(true) => change.unwrap().release_as,
285 Some(false) | None => Bump::Snapshot,
286 };
287
288 let change = Change {
289 release_as,
290 ..change.unwrap().to_owned()
291 };
292
293 bump_changes.insert(changed_package.name.to_string(), change.to_owned());
294 }
295
296 if options.sync_deps.unwrap_or(false) {
297 packages.iter().for_each(|package| {
298 package.dependencies.iter().for_each(|dependency| {
299 let release_as = match Some(current_branch.contains("main")) {
300 Some(true) => Bump::Patch,
301 Some(false) | None => Bump::Snapshot,
302 };
303
304 if dependency.name == changed_package.name {
305 if change.is_some() && !bump_changes.contains_key(&package.name) {
306 bump_changes.insert(
307 package.name.to_string(),
308 Change {
309 package: package.name.to_string(),
310 release_as,
311 deploy: change.unwrap().deploy.to_owned(),
312 },
313 );
314 }
315 }
316 });
317 });
318 }
319 }
320
321 let mut bumps = bump_changes
322 .iter()
323 .map(|(package_name, change)| {
324 let package = get_package_info(package_name.to_string(), Some(root.to_string()));
325
326 let release_as = match Some(current_branch.contains("main")) {
327 Some(true) => change.release_as.to_owned(),
328 Some(false) | None => Bump::Snapshot,
329 };
330
331 let recommended_bump = get_package_recommend_bump(
332 &package.unwrap(),
333 root,
334 Some(BumpOptions {
335 changes: vec![change.to_owned()],
336 since: Some(since.to_string()),
337 release_as: Some(release_as.to_owned()),
338 fetch_all: options.fetch_all.to_owned(),
339 fetch_tags: options.fetch_tags.to_owned(),
340 sync_deps: options.sync_deps.to_owned(),
341 push: options.push.to_owned(),
342 cwd: Some(root.to_string()),
343 }),
344 );
345
346 let bump = BumpPackage {
347 from: recommended_bump.from.to_string(),
348 to: recommended_bump.to.to_string(),
349 conventional_commits: recommended_bump
350 .conventional
351 .conventional_commits
352 .to_owned(),
353 package_info: recommended_bump.package_info.to_owned(),
354 };
355
356 if bump.package_info.dependencies.len() > 0 {
357 bump_dependencies.insert(
358 package_name.to_string(),
359 bump.package_info.dependencies.to_owned(),
360 );
361 }
362
363 return bump;
364 })
365 .collect::<Vec<BumpPackage>>();
366
367 bumps.iter_mut().for_each(|bump| {
368 let version = bump.to.to_string();
369 bump.package_info.update_version(version.to_string());
370 bump.package_info
371 .extend_changed_files(vec![String::from("package.json")]);
372 bump.package_info.write_package_json();
373 });
374
375 if options.sync_deps.unwrap_or(false) {
376 bump_dependencies.iter().for_each(|(package_name, deps)| {
377 let temp_bumps = bumps.clone();
378 let bump = bumps
379 .iter_mut()
380 .find(|b| b.package_info.name == package_name.to_string())
381 .unwrap();
382
383 for dep in deps {
384 let bump_dep = temp_bumps.iter().find(|b| b.package_info.name == dep.name);
385
386 if bump_dep.is_some() {
387 bump.package_info.update_dependency_version(
388 dep.name.to_string(),
389 bump_dep.unwrap().to.to_string(),
390 );
391 bump.package_info.update_dev_dependency_version(
392 dep.name.to_string(),
393 bump_dep.unwrap().to.to_string(),
394 );
395 bump.package_info.write_package_json();
396 }
397 }
398 });
399 }
400
401 bumps
402}
403
404pub fn apply_bumps(options: &BumpOptions) -> Vec<BumpPackage> {
407 let ref root = match options.cwd {
408 Some(ref dir) => get_project_root_path(Some(PathBuf::from(dir))).unwrap(),
409 None => get_project_root_path(None).unwrap(),
410 };
411
412 let ref changes_data = init_changes(Some(root.to_string()), &None);
413 let git_user_name = changes_data.git_user_name.to_owned();
414 let git_user_email = changes_data.git_user_email.to_owned();
415
416 git_config(
417 &git_user_name.unwrap_or(String::from("")),
418 &git_user_email.unwrap_or(String::from("")),
419 &root.to_string(),
420 )
421 .expect("Failed to set git user name and email");
422
423 let bumps = get_bumps(options);
424
425 if bumps.len() != 0 {
426 for bump in &bumps {
427 let git_message = changes_data.message.to_owned();
428
429 let ref bump_pkg_json_file_path =
430 PathBuf::from(bump.package_info.package_json_path.to_string());
431 let ref bump_changelog_file_path =
432 PathBuf::from(bump.package_info.package_path.to_string())
433 .join(String::from("CHANGELOG.md"));
434
435 let bump_pkg_json_file = OpenOptions::new()
437 .write(true)
438 .append(false)
439 .open(bump_pkg_json_file_path)
440 .unwrap();
441 let pkg_json_writer = BufWriter::new(bump_pkg_json_file);
442 serde_json::to_writer_pretty(pkg_json_writer, &bump.package_info.pkg_json).unwrap();
443
444 let conventional = get_conventional_for_package(
445 &bump.package_info,
446 options.fetch_all.to_owned(),
447 Some(root.to_string()),
448 &Some(ConventionalPackageOptions {
449 version: Some(bump.to.to_string()),
450 title: Some("# What changed?".to_string()),
451 }),
452 );
453
454 let mut bump_changelog_file = OpenOptions::new()
456 .write(true)
457 .create(true)
458 .append(false)
459 .open(bump_changelog_file_path)
460 .unwrap();
461
462 bump_changelog_file
463 .write_all(conventional.changelog_output.as_bytes())
464 .unwrap();
465
466 let ref package_tag = format!("{}@{}", bump.package_info.name, bump.to);
467
468 git_add_all(&root.to_string()).expect("Failed to add all files to git");
469 git_commit(
470 git_message.unwrap_or(String::from("chore: release version")),
471 None,
472 None,
473 Some(root.to_string()),
474 )
475 .unwrap();
476 git_tag(
477 package_tag.to_string(),
478 Some(format!(
479 "chore: release {} to version {}",
480 bump.package_info.name, bump.to
481 )),
482 Some(root.to_string()),
483 )
484 .unwrap();
485
486 if options.push.unwrap_or(false) {
487 git_push(Some(root.to_string()), Some(true)).unwrap();
488 }
489 }
490 }
491
492 bumps
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use crate::changes::{add_change, get_change, init_changes};
499 use crate::manager::PackageManager;
500 use crate::packages::get_changed_packages;
501 use crate::paths::get_project_root_path;
502 use crate::utils::create_test_monorepo;
503 use std::fs::remove_dir_all;
504 use std::fs::File;
505 use std::io::Write;
506 use std::process::Command;
507 use std::process::Stdio;
508
509 fn create_single_changes(root: &String) -> Result<(), Box<dyn std::error::Error>> {
510 let change_package_a = Change {
511 package: String::from("@scope/package-a"),
512 release_as: Bump::Major,
513 deploy: vec![String::from("production")],
514 };
515
516 init_changes(Some(root.to_string()), &None);
517
518 add_change(&change_package_a, Some(root.to_string()));
519
520 Ok(())
521 }
522
523 fn create_single_package(monorepo_dir: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
524 let js_path = monorepo_dir.join("packages/package-a/index.js");
525
526 let branch = Command::new("git")
527 .current_dir(&monorepo_dir)
528 .arg("checkout")
529 .arg("-b")
530 .arg("feat/message")
531 .stdout(Stdio::piped())
532 .spawn()
533 .expect("Git branch problem");
534
535 branch.wait_with_output()?;
536
537 let mut js_file = File::create(&js_path)?;
538 js_file
539 .write_all(r#"export const message = "hello package-a";"#.as_bytes())
540 .unwrap();
541
542 let add = Command::new("git")
543 .current_dir(&monorepo_dir)
544 .arg("add")
545 .arg(".")
546 .stdout(Stdio::piped())
547 .spawn()
548 .expect("Git add problem");
549
550 add.wait_with_output()?;
551
552 let commit = Command::new("git")
553 .current_dir(&monorepo_dir)
554 .arg("commit")
555 .arg("-m")
556 .arg("feat: message to the world")
557 .stdout(Stdio::piped())
558 .spawn()
559 .expect("Git commit problem");
560
561 commit.wait_with_output()?;
562
563 Ok(())
564 }
565
566 fn create_multiple_changes(root: &String) -> Result<(), Box<dyn std::error::Error>> {
567 let change_package_a = Change {
568 package: String::from("@scope/package-a"),
569 release_as: Bump::Major,
570 deploy: vec![String::from("production")],
571 };
572
573 let change_package_c = Change {
574 package: String::from("@scope/package-c"),
575 release_as: Bump::Minor,
576 deploy: vec![String::from("production")],
577 };
578
579 init_changes(Some(root.to_string()), &None);
580
581 add_change(&change_package_a, Some(root.to_string()));
582 add_change(&change_package_c, Some(root.to_string()));
583
584 Ok(())
585 }
586
587 fn create_multiple_packages(monorepo_dir: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
588 let js_path_package_a = monorepo_dir.join("packages/package-a/index.js");
589 let js_path_package_c = monorepo_dir.join("packages/package-c/index.js");
590
591 let branch = Command::new("git")
592 .current_dir(&monorepo_dir)
593 .arg("checkout")
594 .arg("-b")
595 .arg("feat/message")
596 .stdout(Stdio::piped())
597 .spawn()
598 .expect("Git branch problem");
599
600 branch.wait_with_output()?;
601
602 let mut js_file_package_a = File::create(&js_path_package_a)?;
603 js_file_package_a
604 .write_all(r#"export const message = "hello package-a";"#.as_bytes())
605 .unwrap();
606
607 let mut js_file_package_c = File::create(&js_path_package_c)?;
608 js_file_package_c
609 .write_all(r#"export const message = "hello package-c";"#.as_bytes())
610 .unwrap();
611
612 let add = Command::new("git")
613 .current_dir(&monorepo_dir)
614 .arg("add")
615 .arg(".")
616 .stdout(Stdio::piped())
617 .spawn()
618 .expect("Git add problem");
619
620 add.wait_with_output()?;
621
622 let commit = Command::new("git")
623 .current_dir(&monorepo_dir)
624 .arg("commit")
625 .arg("-m")
626 .arg("feat: message to the world")
627 .stdout(Stdio::piped())
628 .spawn()
629 .expect("Git commit problem");
630
631 commit.wait_with_output()?;
632
633 Ok(())
634 }
635
636 fn create_single_dependency_changes(root: &String) -> Result<(), Box<dyn std::error::Error>> {
637 let change_package_a = Change {
638 package: String::from("@scope/package-b"),
639 release_as: Bump::Snapshot,
640 deploy: vec![String::from("production")],
641 };
642
643 init_changes(Some(root.to_string()), &None);
644
645 add_change(&change_package_a, Some(root.to_string()));
646
647 Ok(())
648 }
649
650 fn create_single_dependency_package(
651 monorepo_dir: &PathBuf,
652 ) -> Result<(), Box<dyn std::error::Error>> {
653 let js_path = monorepo_dir.join("packages/package-b/index.js");
654
655 let branch = Command::new("git")
656 .current_dir(&monorepo_dir)
657 .arg("checkout")
658 .arg("-b")
659 .arg("feat/message")
660 .stdout(Stdio::piped())
661 .spawn()
662 .expect("Git branch problem");
663
664 branch.wait_with_output()?;
665
666 let mut js_file = File::create(&js_path)?;
667 js_file
668 .write_all(r#"export const message = "hello package-b";"#.as_bytes())
669 .unwrap();
670
671 let add = Command::new("git")
672 .current_dir(&monorepo_dir)
673 .arg("add")
674 .arg(".")
675 .stdout(Stdio::piped())
676 .spawn()
677 .expect("Git add problem");
678
679 add.wait_with_output()?;
680
681 let commit = Command::new("git")
682 .current_dir(&monorepo_dir)
683 .arg("commit")
684 .arg("-m")
685 .arg("feat: message to the world")
686 .stdout(Stdio::piped())
687 .spawn()
688 .expect("Git commit problem");
689
690 commit.wait_with_output()?;
691
692 Ok(())
693 }
694
695 fn create_multiple_dependency_changes(root: &String) -> Result<(), Box<dyn std::error::Error>> {
696 let change_package_a = Change {
697 package: String::from("@scope/package-a"),
698 release_as: Bump::Major,
699 deploy: vec![String::from("production")],
700 };
701
702 let change_package_b = Change {
703 package: String::from("@scope/package-b"),
704 release_as: Bump::Major,
705 deploy: vec![String::from("production")],
706 };
707
708 init_changes(Some(root.to_string()), &None);
709
710 add_change(&change_package_a, Some(root.to_string()));
711 add_change(&change_package_b, Some(root.to_string()));
712
713 Ok(())
714 }
715
716 fn create_multiple_dependency_packages(
717 monorepo_dir: &PathBuf,
718 ) -> Result<(), Box<dyn std::error::Error>> {
719 let js_path = monorepo_dir.join("packages/package-b/index.js");
720 let js_path_no_depend = monorepo_dir.join("packages/package-a/index.js");
721
722 let branch = Command::new("git")
723 .current_dir(&monorepo_dir)
724 .arg("checkout")
725 .arg("-b")
726 .arg("feat/message")
727 .stdout(Stdio::piped())
728 .spawn()
729 .expect("Git branch problem");
730
731 branch.wait_with_output()?;
732
733 let mut js_file_no_depend = File::create(&js_path_no_depend)?;
734 js_file_no_depend
735 .write_all(r#"export const message = "hello package-a";"#.as_bytes())
736 .unwrap();
737
738 let mut js_file = File::create(&js_path)?;
739 js_file
740 .write_all(r#"export const message = "hello package-b";"#.as_bytes())
741 .unwrap();
742
743 let add = Command::new("git")
744 .current_dir(&monorepo_dir)
745 .arg("add")
746 .arg(".")
747 .stdout(Stdio::piped())
748 .spawn()
749 .expect("Git add problem");
750
751 add.wait_with_output()?;
752
753 let commit = Command::new("git")
754 .current_dir(&monorepo_dir)
755 .arg("commit")
756 .arg("-m")
757 .arg("feat: message to the world")
758 .stdout(Stdio::piped())
759 .spawn()
760 .expect("Git commit problem");
761
762 commit.wait_with_output()?;
763
764 Ok(())
765 }
766
767 #[test]
768 fn test_single_get_bumps() -> Result<(), Box<dyn std::error::Error>> {
769 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm).unwrap();
770 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())).unwrap();
771
772 let ref root = project_root.to_string();
773
774 create_single_package(monorepo_dir)?;
775 create_single_changes(&root)?;
776
777 let changes = get_change(String::from("feat/message"), Some(root.to_string()));
778
779 let bumps = get_bumps(&BumpOptions {
780 changes,
781 since: Some(String::from("main")),
782 release_as: Some(Bump::Major),
783 fetch_all: None,
784 fetch_tags: None,
785 sync_deps: Some(false),
786 push: Some(false),
787 cwd: Some(root.to_string()),
788 });
789
790 dbg!(&bumps);
791
792 assert_eq!(bumps.len(), 1);
793
794 let first_bump = bumps.get(0);
795
796 assert_eq!(first_bump.is_some(), true);
797
798 remove_dir_all(&monorepo_dir)?;
799 Ok(())
800 }
801
802 #[test]
803 fn test_multiple_get_bumps() -> Result<(), Box<dyn std::error::Error>> {
804 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm).unwrap();
805 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())).unwrap();
806
807 let ref root = project_root.to_string();
808
809 create_multiple_packages(monorepo_dir)?;
810 create_multiple_changes(&root)?;
811
812 let changes = get_change(String::from("feat/message"), Some(root.to_string()));
813
814 let bumps = get_bumps(&BumpOptions {
815 changes,
816 since: Some(String::from("main")),
817 release_as: None,
818 fetch_all: None,
819 fetch_tags: None,
820 sync_deps: Some(false),
821 push: Some(false),
822 cwd: Some(root.to_string()),
823 });
824
825 assert_eq!(bumps.len(), 2);
826
827 let first_bump = bumps.get(0);
828 let second_bump = bumps.get(1);
829
830 assert_eq!(first_bump.is_some(), true);
831 assert_eq!(second_bump.is_some(), true);
832
833 remove_dir_all(&monorepo_dir)?;
834 Ok(())
835 }
836
837 #[test]
838 fn test_single_dependency_get_bumps() -> Result<(), Box<dyn std::error::Error>> {
839 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm).unwrap();
840 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())).unwrap();
841
842 let ref root = project_root.to_string();
843
844 create_single_dependency_package(monorepo_dir)?;
845 create_single_dependency_changes(&root)?;
846
847 let changes = get_change(String::from("feat/message"), Some(root.to_string()));
848
849 let bumps = get_bumps(&BumpOptions {
850 changes,
851 since: Some(String::from("main")),
852 release_as: None,
853 fetch_all: None,
854 fetch_tags: None,
855 sync_deps: Some(true),
856 push: Some(false),
857 cwd: Some(root.to_string()),
858 });
859
860 assert_eq!(bumps.len(), 2);
861
862 let first_bump = bumps.get(0);
863 let second_bump = bumps.get(1);
864
865 assert_eq!(first_bump.is_some(), true);
866 assert_eq!(second_bump.is_some(), true);
867
868 remove_dir_all(&monorepo_dir)?;
869 Ok(())
870 }
871
872 #[test]
873 fn test_multiple_dependency_get_bumps() -> Result<(), Box<dyn std::error::Error>> {
874 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm).unwrap();
875 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf())).unwrap();
876
877 let ref root = project_root.to_string();
878
879 create_multiple_dependency_packages(monorepo_dir)?;
880 create_multiple_dependency_changes(&root)?;
881
882 let changes = get_change(String::from("feat/message"), Some(root.to_string()));
883
884 let bumps = get_bumps(&BumpOptions {
885 changes,
886 since: Some(String::from("main")),
887 release_as: None,
888 fetch_all: None,
889 fetch_tags: None,
890 sync_deps: Some(true),
891 push: Some(false),
892 cwd: Some(root.to_string()),
893 });
894
895 assert_eq!(bumps.len(), 3);
896
897 let first_bump = bumps.get(0);
898 let second_bump = bumps.get(1);
899 let third_bump = bumps.get(2);
900
901 assert_eq!(first_bump.is_some(), true);
902 assert_eq!(second_bump.is_some(), true);
903 assert_eq!(third_bump.is_some(), true);
904
905 remove_dir_all(&monorepo_dir)?;
906 Ok(())
907 }
908
909 #[test]
910 fn test_apply_bumps() -> Result<(), Box<dyn std::error::Error>> {
911 let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
912 let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
913
914 create_multiple_dependency_packages(monorepo_dir)?;
915
916 let ref root = project_root.unwrap().to_string();
917
918 let packages = get_changed_packages(Some(String::from("main")), Some(root.to_string()))
919 .iter()
920 .map(|package| package.name.to_string())
921 .collect::<Vec<String>>();
922
923 init_changes(Some(root.to_string()), &None);
924
925 for package in packages {
926 let change_package = Change {
927 package: package.to_string(),
928 release_as: Bump::Major,
929 deploy: vec![String::from("production")],
930 };
931
932 add_change(&change_package, Some(root.to_string()));
933 }
934
935 let changes = get_change(String::from("feat/message"), Some(root.to_string()));
936
937 let main_branch = Command::new("git")
938 .current_dir(&monorepo_dir)
939 .arg("checkout")
940 .arg("main")
941 .stdout(Stdio::piped())
942 .spawn()
943 .expect("Git checkout main problem");
944
945 main_branch.wait_with_output()?;
946
947 let merge_branch = Command::new("git")
948 .current_dir(&monorepo_dir)
949 .arg("merge")
950 .arg("feat/message")
951 .stdout(Stdio::piped())
952 .spawn()
953 .expect("Git merge problem");
954
955 merge_branch.wait_with_output()?;
956
957 let bump_options = BumpOptions {
958 changes,
959 since: Some(String::from("main")),
960 release_as: Some(Bump::Minor),
961 fetch_all: None,
962 fetch_tags: None,
963 sync_deps: Some(true),
964 push: Some(false),
965 cwd: Some(root.to_string()),
966 };
967
968 let bumps = apply_bumps(&bump_options);
969
970 assert_eq!(bumps.len(), 3);
971 remove_dir_all(&monorepo_dir)?;
972 Ok(())
973 }
974}