Skip to main content

forge/
args.rs

1use crate::{
2    cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch},
3    opts::{Forge, ForgeSubcommand},
4};
5use clap::{CommandFactory, Parser};
6use clap_complete::generate;
7use eyre::Result;
8use foundry_cli::utils;
9use foundry_common::{sh_warn, shell};
10use foundry_evm::inspectors::cheatcodes::{ForgeContext, set_execution_context};
11
12/// Run the `forge` command line interface.
13pub fn run() -> Result<()> {
14    // Pre-parse discovery flags run before `setup()` so they cannot be blocked
15    // by panic-handler / tracing init failures and avoid that init's cost.
16    foundry_cli::opts::GlobalArgs::check_introspect::<Forge>();
17    foundry_cli::opts::GlobalArgs::check_markdown_help::<Forge>();
18
19    setup()?;
20
21    let args = Forge::parse();
22    args.global.init()?;
23
24    run_command(args)
25}
26
27/// Setup the global logger and other utilities.
28pub fn setup() -> Result<()> {
29    utils::common_setup();
30    utils::subscriber();
31
32    Ok(())
33}
34
35/// Run the subcommand.
36pub fn run_command(args: Forge) -> Result<()> {
37    // Set the execution context based on the subcommand.
38    let context = match &args.cmd {
39        ForgeSubcommand::Test(_) => ForgeContext::Test,
40        ForgeSubcommand::Coverage(_) => ForgeContext::Coverage,
41        ForgeSubcommand::Snapshot(_) => ForgeContext::Snapshot,
42        ForgeSubcommand::Script(cmd) => {
43            if cmd.broadcast {
44                ForgeContext::ScriptBroadcast
45            } else if cmd.resume {
46                ForgeContext::ScriptResume
47            } else {
48                ForgeContext::ScriptDryRun
49            }
50        }
51        _ => ForgeContext::Unknown,
52    };
53    set_execution_context(context);
54
55    let global = &args.global;
56
57    // Run the subcommand.
58    match args.cmd {
59        ForgeSubcommand::Test(cmd) => {
60            if cmd.is_watch() {
61                global.block_on(watch::watch_test(cmd))
62            } else {
63                let silent = cmd.junit || shell::is_json();
64                let outcome = global.block_on(cmd.run())?;
65                outcome.ensure_ok(silent)
66            }
67        }
68        ForgeSubcommand::Script(cmd) => global.block_on(cmd.run_script()),
69        ForgeSubcommand::Coverage(cmd) => {
70            if cmd.is_watch() {
71                global.block_on(watch::watch_coverage(cmd))
72            } else {
73                global.block_on(cmd.run())
74            }
75        }
76        ForgeSubcommand::Bind(cmd) => cmd.run(),
77        ForgeSubcommand::Build(cmd) => {
78            if cmd.is_watch() {
79                global.block_on(watch::watch_build(cmd))
80            } else {
81                global.block_on(cmd.run()).map(drop)
82            }
83        }
84        ForgeSubcommand::VerifyContract(args) => global.block_on(args.run()),
85        ForgeSubcommand::VerifyCheck(args) => global.block_on(args.run()),
86        ForgeSubcommand::VerifyBytecode(cmd) => global.block_on(cmd.run()),
87        ForgeSubcommand::Clone(cmd) => global.block_on(cmd.run()),
88        ForgeSubcommand::Cache(cmd) => match cmd.sub {
89            CacheSubcommands::Clean(cmd) => cmd.run(),
90            CacheSubcommands::Ls(cmd) => cmd.run(),
91        },
92        ForgeSubcommand::Create(cmd) => global.block_on(cmd.run()),
93        ForgeSubcommand::Update(cmd) => cmd.run(),
94        ForgeSubcommand::Install(cmd) => global.block_on(cmd.run()),
95        ForgeSubcommand::Remove(cmd) => cmd.run(),
96        ForgeSubcommand::Remappings(cmd) => cmd.run(),
97        ForgeSubcommand::Init(cmd) => global.block_on(cmd.run()),
98        ForgeSubcommand::Completions { shell } => {
99            generate(shell, &mut Forge::command(), "forge", &mut std::io::stdout());
100            Ok(())
101        }
102        ForgeSubcommand::Clean { root } => {
103            let config = utils::load_config_with_root(root.as_deref())?;
104            let project = config.project()?;
105            for warning in config.cleanup(&project)? {
106                let _ = sh_warn!("{warning}");
107            }
108            Ok(())
109        }
110        ForgeSubcommand::Snapshot(cmd) => {
111            if cmd.is_watch() {
112                global.block_on(watch::watch_gas_snapshot(cmd))
113            } else {
114                global.block_on(cmd.run())
115            }
116        }
117        ForgeSubcommand::Fmt(cmd) => {
118            if cmd.is_watch() {
119                global.block_on(watch::watch_fmt(cmd))
120            } else {
121                cmd.run()
122            }
123        }
124        ForgeSubcommand::Config(cmd) => cmd.run(),
125        ForgeSubcommand::Flatten(cmd) => cmd.run(),
126        ForgeSubcommand::Inspect(cmd) => cmd.run(),
127        ForgeSubcommand::Tree(cmd) => cmd.run(),
128        ForgeSubcommand::Geiger(cmd) => cmd.run(),
129        ForgeSubcommand::Doc(cmd) => {
130            if cmd.is_watch() {
131                global.block_on(watch::watch_doc(cmd))
132            } else {
133                global.block_on(cmd.run())?;
134                Ok(())
135            }
136        }
137        ForgeSubcommand::Selectors { command } => global.block_on(command.run()),
138        ForgeSubcommand::Generate(cmd) => match cmd.sub {
139            GenerateSubcommands::Test(cmd) => cmd.run(),
140        },
141        ForgeSubcommand::Compiler(cmd) => cmd.run(),
142        ForgeSubcommand::Soldeer(cmd) => global.block_on(cmd.run()),
143        ForgeSubcommand::Eip712(cmd) => cmd.run(),
144        ForgeSubcommand::BindJson(cmd) => cmd.run(),
145        ForgeSubcommand::Lint(cmd) => cmd.run(),
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use foundry_cli::introspect::{
153        CommandRegistry, INTROSPECT_SCHEMA_ID, IntrospectDocument, build_document,
154        duplicate_command_ids, render_introspect_document,
155    };
156
157    /// Every `command_id` exposed by `forge --introspect` MUST be unique.
158    /// This is the foundation of the agent contract — agents key on
159    /// `command_id` to identify commands, and duplicates would silently break
160    /// downstream tooling.
161    #[test]
162    fn introspect_command_ids_are_unique() {
163        let cmd = <Forge as clap::CommandFactory>::command();
164        let doc = build_document(&cmd, &CommandRegistry::EMPTY);
165        let dups = duplicate_command_ids(&doc);
166        assert!(dups.is_empty(), "duplicate forge command_ids: {dups:?}");
167    }
168
169    /// `forge --introspect` must produce a JSON document that parses back into
170    /// the canonical `IntrospectDocument` shape.
171    #[test]
172    fn introspect_document_is_valid_json() {
173        let cmd = <Forge as clap::CommandFactory>::command();
174        let json = render_introspect_document(&cmd, &CommandRegistry::EMPTY);
175        let doc: IntrospectDocument = serde_json::from_str(&json).expect("valid JSON");
176        assert_eq!(doc.schema_id, INTROSPECT_SCHEMA_ID);
177        assert_eq!(doc.binary.name, "forge");
178    }
179}