diff --git a/module/move/willbe/src/action/publish.rs b/module/move/willbe/src/action/publish.rs index 0c775fb420..e5d59906f5 100644 --- a/module/move/willbe/src/action/publish.rs +++ b/module/move/willbe/src/action/publish.rs @@ -18,8 +18,6 @@ mod private { /// Represents the absolute path to the root directory of the workspace. pub workspace_root_dir : Option< AbsolutePath >, - /// Represents a collection of packages that are roots of the trees. - pub wanted_to_publish : Vec< CrateDir >, pub plan : Option< package::PublishPlan >, /// Represents a collection of packages and their associated publishing reports. pub packages : Vec<( AbsolutePath, package::PublishReport )> @@ -34,16 +32,8 @@ mod private write!( f, "Nothing to publish" )?; return Ok( () ); } - if let Some( plan ) = &self.plan - { - write!( f, "Tree{} :\n", if self.wanted_to_publish.len() > 1 { "s" } else { "" } )?; - plan.display_as_tree( f, &self.wanted_to_publish )?; - - writeln!( f, "The following packages are pending for publication :" )?; - plan.display_as_list( f )?; - } - writeln!( f, "\nActions :" )?; + writeln!( f, "Actions :" )?; for ( path, report ) in &self.packages { let report = report.to_string().replace("\n", "\n "); @@ -58,66 +48,105 @@ mod private }; write!( f, "Publishing crate by `{}` path\n {report}", path.display() )?; } + if let Some( plan ) = &self.plan + { + if !plan.dry + { + let expected_to_publish = plan + .plans + .iter() + .map( | p | ( p.version_bump.crate_dir.absolute_path(), p.package_name.clone(), p.version_bump.clone() ) ) + .collect::< Vec< _ > >(); + let mut actually_published = self.packages.iter() + .filter_map + ( + |( path, repo )| + if repo.publish.as_ref().is_some_and( | r | r.error.is_ok() ) + { + Some( path.clone() ) + } + else + { + None + } + ) + .collect::< Vec< _ > >(); + + writeln!( f, "Status :" )?; + for ( path, name, version ) in expected_to_publish + { + if let Some( pos ) = actually_published.iter().position( | p | p == &path ) + { + write!( f, "✅ {name} {}", version.new_version )?; + // want to check that only expected packages actually published + _ = actually_published.remove( pos ); + } + else + { + write!( f, "❌ {name} {}", version.old_version )?; + } + } + if !actually_published.is_empty() + { + writeln!( f, "Logical error. Published unexpected packages" )?; + return Err( std::fmt::Error ); + } + } + } Ok( () ) } } + /// Publishes packages based on the specified patterns. /// - /// Publish packages. + /// # Arguments + /// * `patterns` - A vector of patterns specifying the folders to search for packages. + /// * `dry` - A boolean value indicating whether to perform a dry run. + /// * `temp` - A boolean value indicating whether to use a temporary directory. /// - + /// # Returns + /// A Result containing a `PublishPlan` if successful, or an `Error` otherwise. #[ cfg_attr( feature = "tracing", tracing::instrument ) ] - pub fn publish( patterns : Vec< String >, dry : bool, temp : bool ) -> Result< PublishReport, ( PublishReport, Error ) > + pub fn publish_plan( patterns : Vec< String >, dry : bool, temp : bool ) -> Result< package::PublishPlan, Error > { - let mut report = PublishReport::default(); - let mut paths = HashSet::new(); // find all packages by specified folders for pattern in &patterns { - let current_path = AbsolutePath::try_from( std::path::PathBuf::from( pattern ) ).err_with( || report.clone() )?; + let current_path = AbsolutePath::try_from( std::path::PathBuf::from( pattern ) )?; // let current_paths = files::find( current_path, &[ "Cargo.toml" ] ); paths.extend( Some( current_path ) ); } let mut metadata = if paths.is_empty() { - Workspace::from_current_path().err_with( || report.clone() )? + Workspace::from_current_path()? } else { // FIX : patterns can point to different workspaces. Current solution take first random path from list let current_path = paths.iter().next().unwrap().clone(); - let dir = CrateDir::try_from( current_path ).err_with( || report.clone() )?; + let dir = CrateDir::try_from( current_path )?; - Workspace::with_crate_dir( dir ).err_with( || report.clone() )? + Workspace::with_crate_dir( dir )? }; let workspace_root_dir : AbsolutePath = metadata - .workspace_root() - .err_with( || report.clone() )? - .try_into() - .err_with( || report.clone() )?; - report.workspace_root_dir = Some( workspace_root_dir.clone() ); - let packages = metadata.load().err_with( || report.clone() )?.packages().err_with( || report.clone() )?; + .workspace_root()? + .try_into()?; + let packages = metadata.load()?.packages()?; let packages_to_publish : Vec< _ > = packages .iter() .filter( | &package | paths.contains( &AbsolutePath::try_from( package.manifest_path().as_std_path().parent().unwrap() ).unwrap() ) ) .map( | p | p.name().clone() ) .collect(); let package_map = packages.into_iter().map( | p | ( p.name().clone(), Package::from( p.clone() ) ) ).collect::< HashMap< _, _ > >(); - { - for node in &packages_to_publish - { - report.wanted_to_publish.push( package_map.get( node ).unwrap().crate_dir() ); - } - } let graph = metadata.graph(); let subgraph_wanted = graph::subgraph( &graph, &packages_to_publish ); let tmp = subgraph_wanted.map( | _, n | graph[ *n ].clone(), | _, e | graph[ *e ].clone() ); - let mut unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name().err_with( || report.clone() )? ); + let mut unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name()? ); let dir = if temp { @@ -125,11 +154,11 @@ mod private while temp_dir.exists() { - unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name().err_with( || report.clone() )? ); + unique_name = format!( "temp_dir_for_publish_command_{}", path_tools::path::unique_folder_name()? ); temp_dir = env::temp_dir().join( unique_name ); } - fs::create_dir( &temp_dir ).err_with( || report.clone() )?; + fs::create_dir( &temp_dir )?; Some( temp_dir ) } else @@ -142,12 +171,29 @@ mod private let queue = graph::toposort( subgraph ).unwrap().into_iter().map( | n | package_map.get( &n ).unwrap() ).cloned().collect::< Vec< _ > >(); + let roots = packages_to_publish.iter().map( | p | package_map.get( p ).unwrap().crate_dir() ).collect::< Vec< _ > >(); + let plan = package::PublishPlan::former() .workspace_dir( CrateDir::try_from( workspace_root_dir ).unwrap() ) .option_base_temp_dir( dir.clone() ) .dry( dry ) + .roots( roots ) .packages( queue ) .form(); + + Ok( plan ) + } + + /// + /// Publish packages. + /// + + #[ cfg_attr( feature = "tracing", tracing::instrument ) ] + pub fn publish( plan : package::PublishPlan ) -> Result< PublishReport, ( PublishReport, Error ) > + { + let mut report = PublishReport::default(); + let temp = plan.base_temp_dir.clone(); + report.plan = Some( plan.clone() ); for package_report in package::perform_packages_publish( plan ).err_with( || report.clone() )? { @@ -155,9 +201,9 @@ mod private report.packages.push(( AbsolutePath::try_from( path ).unwrap(), package_report )); } - if temp + if let Some( dir ) = temp { - fs::remove_dir_all( dir.unwrap() ).err_with( || report.clone() )?; + fs::remove_dir_all( dir ).err_with( || report.clone() )?; } Ok( report ) @@ -188,6 +234,8 @@ mod private crate::mod_interface! { - /// Publish package. + /// Create a plan for publishing packages + orphan use publish_plan; + /// Execute the publication plan orphan use publish; } diff --git a/module/move/willbe/src/command/publish.rs b/module/move/willbe/src/command/publish.rs index e8d7d5aabf..d591f56fe3 100644 --- a/module/move/willbe/src/command/publish.rs +++ b/module/move/willbe/src/command/publish.rs @@ -5,8 +5,9 @@ mod private use colored::Colorize; use wca::VerifiedCommand; - use wtools::error::Result; + use wtools::error::{ Result, for_app::Context }; use former::Former; + use std::fmt::Write; #[ derive( Former ) ] struct PublishProperties @@ -29,8 +30,20 @@ mod private let patterns : Vec< _ > = o.args.get_owned( 0 ).unwrap_or_else( || vec![ "./".into() ] ); let PublishProperties { dry, temp } = o.props.try_into()?; + let plan = action::publish_plan( patterns, dry, temp ).context( "Failed to plan the publication process" )?; - match action::publish( patterns, dry, temp ) + let mut formatted_plan = String::new(); + writeln!( &mut formatted_plan, "Tree :" )?; + plan.write_as_tree( &mut formatted_plan )?; + + if !plan.plans.is_empty() + { + writeln!( &mut formatted_plan, "The following packages are pending for publication :" )?; + plan.write_as_list( &mut formatted_plan )?; + } + println!( "{formatted_plan}" ); + + match action::publish( plan ) { Ok( report ) => { diff --git a/module/move/willbe/src/entity/package.rs b/module/move/willbe/src/entity/package.rs index 0d987e5579..94d7c53321 100644 --- a/module/move/willbe/src/entity/package.rs +++ b/module/move/willbe/src/entity/package.rs @@ -484,6 +484,9 @@ mod private #[ default( true ) ] pub dry : bool, + /// Required for tree view only + pub roots : Vec< CrateDir >, + /// `plans` - This is a vector containing the instructions for publishing each package. Each item /// in the `plans` vector indicates a `PackagePublishInstruction` set for a single package. It outlines /// how to build and where to publish the package amongst other instructions. The `#[setter( false )]` @@ -500,19 +503,20 @@ mod private /// # Arguments /// /// * `f` - A mutable reference to a `Formatter` used for writing the output. - /// * `roots` - A slice of `CrateDir` representing the root crates to display. /// /// # Errors /// /// Returns a `std::fmt::Error` if there is an error writing to the formatter. - pub fn display_as_tree( &self, f : &mut Formatter< '_ >, roots : &[ CrateDir ] ) -> std::fmt::Result + pub fn write_as_tree< W >( &self, f : &mut W ) -> std::fmt::Result + where + W : std::fmt::Write { let name_bump_report = self .plans .iter() .map( | x | ( &x.package_name, ( x.version_bump.old_version.to_string(), x.version_bump.new_version.to_string() ) ) ) .collect::< HashMap< _, _ > >(); - for wanted in roots + for wanted in &self.roots { let list = action::list ( @@ -556,7 +560,9 @@ mod private /// # Errors /// /// Returns a `std::fmt::Error` if there is an error writing to the formatter. - pub fn display_as_list( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result + pub fn write_as_list< W >( &self, f : &mut W ) -> std::fmt::Result + where + W : std::fmt::Write { for ( idx, package ) in self.plans.iter().enumerate() {