11use crate :: { get_keypair, is_hidden, keys_sync, DEFAULT_RPC_PORT } ;
22use anchor_client:: Cluster ;
33use anchor_lang_idl:: types:: Idl ;
4- use anyhow:: { anyhow, Context , Error , Result } ;
4+ use anyhow:: { anyhow, bail , Context , Error , Result } ;
55use clap:: { Parser , ValueEnum } ;
66use dirs:: home_dir;
77use heck:: ToSnakeCase ;
@@ -21,6 +21,7 @@ use std::marker::PhantomData;
2121use std:: ops:: Deref ;
2222use std:: path:: Path ;
2323use std:: path:: PathBuf ;
24+ use std:: process:: Command ;
2425use std:: str:: FromStr ;
2526use std:: { fmt, io} ;
2627use walkdir:: WalkDir ;
@@ -291,6 +292,7 @@ pub struct Config {
291292 pub provider : ProviderConfig ,
292293 pub programs : ProgramsConfig ,
293294 pub scripts : ScriptsConfig ,
295+ pub hooks : HooksConfig ,
294296 pub workspace : WorkspaceConfig ,
295297 // Separate entry next to test_config because
296298 // "anchor localnet" only has access to the Anchor.toml,
@@ -384,6 +386,49 @@ pub type ScriptsConfig = BTreeMap<String, String>;
384386
385387pub type ProgramsConfig = BTreeMap < Cluster , BTreeMap < String , ProgramDeployment > > ;
386388
389+ #[ derive( Default , Clone , Debug , Serialize , Deserialize ) ]
390+ #[ serde( deny_unknown_fields) ]
391+ pub struct HooksConfig {
392+ #[ serde( alias = "pre-build" ) ]
393+ pre_build : Option < Hook > ,
394+ #[ serde( alias = "post-build" ) ]
395+ post_build : Option < Hook > ,
396+ #[ serde( alias = "pre-test" ) ]
397+ pre_test : Option < Hook > ,
398+ #[ serde( alias = "post-test" ) ]
399+ post_test : Option < Hook > ,
400+ #[ serde( alias = "pre-deploy" ) ]
401+ pre_deploy : Option < Hook > ,
402+ #[ serde( alias = "post-deploy" ) ]
403+ post_deploy : Option < Hook > ,
404+ }
405+
406+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
407+ #[ serde( untagged) ]
408+ enum Hook {
409+ Single ( String ) ,
410+ List ( Vec < String > ) ,
411+ }
412+
413+ impl Hook {
414+ pub fn hooks ( & self ) -> & [ String ] {
415+ match self {
416+ Self :: Single ( h) => std:: slice:: from_ref ( h) ,
417+ Self :: List ( l) => l. as_slice ( ) ,
418+ }
419+ }
420+ }
421+
422+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
423+ pub enum HookType {
424+ PreBuild ,
425+ PostBuild ,
426+ PreTest ,
427+ PostTest ,
428+ PreDeploy ,
429+ PostDeploy ,
430+ }
431+
387432#[ derive( Debug , Default , Clone , Serialize , Deserialize ) ]
388433pub struct WorkspaceConfig {
389434 #[ serde( default , skip_serializing_if = "Vec::is_empty" ) ]
@@ -501,6 +546,32 @@ impl Config {
501546 pub fn wallet_kp ( & self ) -> Result < Keypair > {
502547 get_keypair ( & self . provider . wallet . to_string ( ) )
503548 }
549+
550+ pub fn run_hooks ( & self , hook_type : HookType ) -> Result < ( ) > {
551+ let hooks = match hook_type {
552+ HookType :: PreBuild => & self . hooks . pre_build ,
553+ HookType :: PostBuild => & self . hooks . post_build ,
554+ HookType :: PreTest => & self . hooks . pre_test ,
555+ HookType :: PostTest => & self . hooks . post_test ,
556+ HookType :: PreDeploy => & self . hooks . pre_deploy ,
557+ HookType :: PostDeploy => & self . hooks . post_deploy ,
558+ } ;
559+ let cmds = hooks. as_ref ( ) . map ( Hook :: hooks) . unwrap_or_default ( ) ;
560+ for cmd in cmds {
561+ let status = Command :: new ( "bash" )
562+ . arg ( "-c" )
563+ . arg ( cmd)
564+ . status ( )
565+ . with_context ( || format ! ( "failed to execute `{cmd}`" ) ) ?;
566+ if !status. success ( ) {
567+ match status. code ( ) {
568+ Some ( code) => bail ! ( "`{cmd}` failed with exit code {code}" ) ,
569+ None => bail ! ( "`{cmd}` killed by signal" ) ,
570+ }
571+ }
572+ }
573+ Ok ( ( ) )
574+ }
504575}
505576
506577#[ derive( Debug , Serialize , Deserialize ) ]
@@ -512,6 +583,7 @@ struct _Config {
512583 provider : Provider ,
513584 workspace : Option < WorkspaceConfig > ,
514585 scripts : Option < ScriptsConfig > ,
586+ hooks : Option < HooksConfig > ,
515587 test : Option < _TestValidator > ,
516588}
517589
@@ -610,6 +682,7 @@ impl fmt::Display for Config {
610682 true => None ,
611683 false => Some ( self . scripts . clone ( ) ) ,
612684 } ,
685+ hooks : Some ( self . hooks . clone ( ) ) ,
613686 programs,
614687 workspace : ( !self . workspace . members . is_empty ( ) || !self . workspace . exclude . is_empty ( ) )
615688 . then ( || self . workspace . clone ( ) ) ,
@@ -635,6 +708,7 @@ impl FromStr for Config {
635708 wallet : shellexpand:: tilde ( & cfg. provider . wallet ) . parse ( ) ?,
636709 } ,
637710 scripts : cfg. scripts . unwrap_or_default ( ) ,
711+ hooks : cfg. hooks . unwrap_or_default ( ) ,
638712 test_validator : cfg. test . map ( Into :: into) ,
639713 test_config : None ,
640714 programs : cfg. programs . map_or ( Ok ( BTreeMap :: new ( ) ) , deser_programs) ?,
0 commit comments