#[ allow( clippy ::std_instead_of_alloc, clippy ::std_instead_of_core ) ]
mod private
{
use crate :: *;
use collection_tools :: collection;
use std ::fmt;
use process_tools ::process;
use
{
iter ::Itertools,
error ::
{
untyped :: { format_err, Error },
}
};
use error ::ErrWith;
use std ::result ::Result :: { Ok, Err };
#[ derive( Debug, Clone ) ]
pub struct PackagePublishInstruction
{
pub package_name: package ::PackageName,
pub pack: cargo ::PackOptions,
pub bump: version ::BumpOptions,
pub git_options: entity ::git ::GitOptions,
pub publish: cargo ::PublishOptions,
pub dry: bool,
}
fn find_workspace_dependents
(
workspace: &Workspace,
crate_name: &str,
) -> Vec< CrateDir >
{
workspace
.packages()
.filter_map( | pkg |
{
let has_dependency = pkg
.dependencies()
.any( | dep | dep.name() == crate_name );
if has_dependency
{
pkg.crate_dir().ok()
}
else
{
None
}
})
.collect()
}
#[ derive( Debug, former ::Former ) ]
#[ perform( fn build() -> PackagePublishInstruction ) ]
pub struct PublishSinglePackagePlanner< 'a >
{
workspace_dir: CrateDir,
package: package ::Package< 'a >,
channel: channel ::Channel,
base_temp_dir: Option< path ::PathBuf >,
#[ former( default = true ) ]
dry: bool,
}
impl PublishSinglePackagePlanner< '_ > {
fn build( self ) -> PackagePublishInstruction
{
let crate_dir = self.package.crate_dir();
let workspace_root: AbsolutePath = self.workspace_dir.clone().absolute_path();
let pack = cargo ::PackOptions
{
path: crate_dir.clone().absolute_path().inner(),
channel: self.channel,
allow_dirty: self.dry,
checking_consistency: !self.dry,
temp_path: self.base_temp_dir.clone(),
dry: self.dry,
};
let old_version: Version = self.package.version().as_ref().unwrap().try_into().unwrap();
let new_version = old_version.clone().bump();
let workspace = Workspace ::try_from( self.workspace_dir.clone() ).unwrap();
let package_name = self.package.name().unwrap();
let mut dependencies = find_workspace_dependents( &workspace, package_name );
let workspace_root_dir = CrateDir ::try_from( workspace_root.clone() ).unwrap();
if !dependencies.contains( &workspace_root_dir )
{
dependencies.push( workspace_root_dir );
}
let bump = version ::BumpOptions
{
crate_dir: crate_dir.clone(),
old_version: old_version.clone(),
new_version: new_version.clone(),
dependencies: dependencies.clone(),
dry: self.dry,
};
let git_options = entity ::git ::GitOptions
{
git_root: workspace_root,
items: dependencies.iter().chain( [ &crate_dir ] ).map( | d | d.clone().absolute_path().join( "Cargo.toml" ).expect( "Failed to join Cargo.toml path" ) ).collect(),
message: format!( "{}-v{}", self.package.name().unwrap(), new_version ),
dry: self.dry,
};
let publish = cargo ::PublishOptions
{
path: crate_dir.clone().absolute_path().inner(),
temp_path: self.base_temp_dir.clone(),
retry_count: 2,
dry: self.dry,
};
PackagePublishInstruction
{
package_name: self.package.name().unwrap().to_string().into(),
pack,
bump,
git_options,
publish,
dry: self.dry,
}
}
}
#[ derive( Debug, former ::Former, Clone ) ]
pub struct PublishPlan
{
pub workspace_dir: CrateDir,
pub base_temp_dir: Option< path ::PathBuf >,
pub channel: channel ::Channel,
#[ former( default = true ) ]
pub dry: bool,
pub roots: Vec< CrateDir >,
#[ scalar( setter = false ) ]
pub plans: Vec< PackagePublishInstruction >,
}
impl PublishPlan
{
pub fn write_as_tree< W >( &self, f: &mut W ) -> fmt ::Result
where
W: fmt ::Write
{
let name_bump_report: collection ::HashMap< _, _ > = self
.plans
.iter()
.map( | x | ( x.package_name.as_ref(), ( x.bump.old_version.to_string(), x.bump.new_version.to_string() ) ) )
.collect();
for wanted in &self.roots
{
let list = action ::list_all
(
action ::list ::ListOptions ::former()
.path_to_manifest( wanted.clone() )
.format( action ::list ::ListFormat ::Tree )
.dependency_sources( [ action ::list ::DependencySource ::Local ] )
.dependency_categories( [ action ::list ::DependencyCategory ::Primary ] )
.form()
)
.map_err( | ( _, _e ) | fmt ::Error )?;
let action ::list ::ListReport ::Tree( list ) = list else { unreachable!() };
#[ allow( clippy ::items_after_statements ) ]
fn callback( name_bump_report: &collection ::HashMap< &String, ( String, String ) >, mut r: tool ::ListNodeReport ) -> tool ::ListNodeReport
{
if let Some( ( old, new ) ) = name_bump_report.get( &r.name )
{
r.version = Some( format!( "({old} -> {new})" ) );
}
r.normal_dependencies = r.normal_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect();
r.dev_dependencies = r.dev_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect();
r.build_dependencies = r.build_dependencies.into_iter().map( | r | callback( name_bump_report, r ) ).collect();
r
}
let printer = list;
let rep: Vec< tool ::ListNodeReport > = printer.iter().map( | printer | printer.info.clone() ).collect();
let list: Vec< tool ::ListNodeReport > = rep.into_iter().map( | r | callback( &name_bump_report, r ) ).collect();
let printer: Vec< tool ::TreePrinter > = list.iter().map( tool ::TreePrinter ::new ).collect();
let list = action ::list ::ListReport ::Tree( printer );
writeln!( f, "{list}" )?;
}
Ok( () )
}
pub fn write_as_list< W >( &self, f: &mut W ) -> fmt ::Result
where
W: fmt ::Write
{
for ( idx, package ) in self.plans.iter().enumerate()
{
let bump = &package.bump;
writeln!( f, "[{idx}] {} ({} -> {})", package.package_name, bump.old_version, bump.new_version )?;
}
Ok( () )
}
}
impl< 'a > PublishPlanFormer
{
pub fn option_base_temp_dir( mut self, path: Option< path ::PathBuf > ) -> Self
{
self.storage.base_temp_dir = path;
self
}
pub fn package< IntoPackage >( mut self, package: IntoPackage ) -> Self
where
IntoPackage: Into< package ::Package< 'a > >,
{
let channel = self.storage.channel.unwrap_or_default();
let mut plan = PublishSinglePackagePlanner ::former();
if let Some( workspace ) = &self.storage.workspace_dir
{
plan = plan.workspace_dir( workspace.clone() );
}
if let Some( base_temp_dir ) = &self.storage.base_temp_dir
{
plan = plan.base_temp_dir( base_temp_dir.clone() );
}
if let Some( dry ) = self.storage.dry
{
plan = plan.dry( dry );
}
let plan = plan
.channel( channel )
.package( package )
.perform();
let mut plans = self.storage.plans.unwrap_or_default();
plans.push( plan );
self.storage.plans = Some( plans );
self
}
pub fn packages< IntoPackageIter, IntoPackage >( mut self, packages: IntoPackageIter ) -> Self
where
IntoPackageIter: IntoIterator< Item = IntoPackage >,
IntoPackage: Into< package ::Package< 'a > >,
{
for package in packages
{
self = self.package( package );
}
self
}
}
#[ derive( Debug, Default, Clone ) ]
pub struct PublishReport
{
pub get_info: Option< process ::Report >,
pub bump: Option< version ::ExtendedBumpReport >,
pub add: Option< process ::Report >,
pub commit: Option< process ::Report >,
pub push: Option< process ::Report >,
pub publish: Option< process ::Report >,
}
impl fmt ::Display for PublishReport
{
fn fmt( &self, f: &mut fmt ::Formatter< '_ > ) -> fmt ::Result
{
let PublishReport
{
get_info,
bump,
add,
commit,
push,
publish,
} = self;
if get_info.is_none()
{
f.write_str( "Empty report" )?;
return Ok( () )
}
let info = get_info.as_ref().unwrap();
write!( f, "{info}" )?;
if let Some( bump ) = bump
{
writeln!( f, "{bump}" )?;
}
if let Some( add ) = add
{
write!( f, "{add}" )?;
}
if let Some( commit ) = commit
{
write!( f, "{commit}" )?;
}
if let Some( push ) = push
{
write!( f, "{push}" )?;
}
if let Some( publish ) = publish
{
write!( f, "{publish}" )?;
}
Ok( () )
}
}
#[ allow( clippy ::option_map_unit_fn, clippy ::result_large_err ) ]
pub fn perform_package_publish( instruction: PackagePublishInstruction ) -> ResultWithReport< PublishReport, Error >
{
let mut report = PublishReport ::default();
let PackagePublishInstruction
{
package_name: _,
mut pack,
mut bump,
mut git_options,
mut publish,
dry,
} = instruction;
pack.dry = dry;
bump.dry = dry;
git_options.dry = dry;
publish.dry = dry;
report.get_info = Some( cargo ::pack( pack ).err_with_report( &report )? );
let bump_report = version ::bump( bump ).err_with_report( &report )?;
report.bump = Some( bump_report.clone() );
let git_root = git_options.git_root.clone();
let git = match entity ::git ::perform_git_commit( git_options )
{
Ok( git ) => git,
Err( e ) =>
{
version ::revert( &bump_report )
.map_err( | le | format_err!( "Base error: \n{}\nRevert error: \n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) ) )
.err_with_report( &report )?;
return Err( ( report, e ) );
}
};
report.add = git.add;
report.commit = git.commit;
report.publish = match cargo ::publish( &publish )
{
Ok( publish ) => Some( publish ),
Err( e ) =>
{
tool ::git ::reset( git_root.as_ref(), true, 1, false )
.map_err
(
| le |
format_err!( "Base error: \n{}\nRevert error: \n{}", e.to_string().replace( '\n', "\n\t" ), le.to_string().replace( '\n', "\n\t" ) )
)
.err_with_report( &report )?;
return Err( ( report, e ) );
}
};
let res = tool ::git ::push( &git_root, dry ).err_with_report( &report )?;
report.push = Some( res );
Ok( report )
}
pub fn perform_packages_publish( plan: PublishPlan ) -> error ::untyped ::Result< Vec< PublishReport > >
{
let mut report = vec![];
for package in plan.plans
{
let res = perform_package_publish( package ).map_err
(
| ( current_rep, e ) |
format_err!( "{}\n{current_rep}\n{e}", report.iter().map( | r | format!( "{r}" ) ).join( "\n" ) )
)?;
report.push( res );
}
Ok( report )
}
}
crate ::mod_interface!
{
own use PublishPlan;
own use PackagePublishInstruction;
own use PublishSinglePackagePlanner;
own use PublishReport;
own use perform_package_publish;
own use perform_packages_publish;
}