-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(torii): configutation file for all torii cli options #2646
Changes from all commits
d6c77c0
3108039
aa985f7
7c19061
cf20035
f671543
8b41cf2
a2c064e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -11,15 +11,15 @@ | |||||||||||||||||||||||||||||||||||||||||||||
//! for more info. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
use std::cmp; | ||||||||||||||||||||||||||||||||||||||||||||||
use std::collections::VecDeque; | ||||||||||||||||||||||||||||||||||||||||||||||
use std::net::SocketAddr; | ||||||||||||||||||||||||||||||||||||||||||||||
use std::path::PathBuf; | ||||||||||||||||||||||||||||||||||||||||||||||
use std::str::FromStr; | ||||||||||||||||||||||||||||||||||||||||||||||
use std::sync::Arc; | ||||||||||||||||||||||||||||||||||||||||||||||
use std::time::Duration; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
use anyhow::Context; | ||||||||||||||||||||||||||||||||||||||||||||||
use clap::{ArgAction, Parser}; | ||||||||||||||||||||||||||||||||||||||||||||||
use clap::{ArgAction, CommandFactory, FromArgMatches, Parser}; | ||||||||||||||||||||||||||||||||||||||||||||||
use clap_config::ClapConfig; | ||||||||||||||||||||||||||||||||||||||||||||||
use dojo_metrics::exporters::prometheus::PrometheusRecorder; | ||||||||||||||||||||||||||||||||||||||||||||||
use dojo_utils::parse::{parse_socket_address, parse_url}; | ||||||||||||||||||||||||||||||||||||||||||||||
use dojo_world::contracts::world::WorldContractReader; | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -37,9 +37,10 @@ | |||||||||||||||||||||||||||||||||||||||||||||
use torii_core::engine::{Engine, EngineConfig, IndexingFlags, Processors}; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::executor::Executor; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::processors::store_transaction::StoreTransactionProcessor; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::processors::EventProcessorConfig; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::simple_broker::SimpleBroker; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::sql::Sql; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::types::{Contract, ContractType, Model, ToriiConfig}; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_core::types::{Contract, ContractType, Model}; | ||||||||||||||||||||||||||||||||||||||||||||||
use torii_server::proxy::Proxy; | ||||||||||||||||||||||||||||||||||||||||||||||
use tracing::{error, info}; | ||||||||||||||||||||||||||||||||||||||||||||||
use tracing_subscriber::{fmt, EnvFilter}; | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -48,7 +49,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||
pub(crate) const LOG_TARGET: &str = "torii::cli"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
/// Dojo World Indexer | ||||||||||||||||||||||||||||||||||||||||||||||
#[derive(Parser, Debug)] | ||||||||||||||||||||||||||||||||||||||||||||||
#[derive(ClapConfig, Parser, Debug)] | ||||||||||||||||||||||||||||||||||||||||||||||
#[command(name = "torii", author, version, about, long_about = None)] | ||||||||||||||||||||||||||||||||||||||||||||||
struct Args { | ||||||||||||||||||||||||||||||||||||||||||||||
/// The world to index | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -91,9 +92,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
/// Specify allowed origins for api endpoints (comma-separated list of allowed origins, or "*" | ||||||||||||||||||||||||||||||||||||||||||||||
/// for all) | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(long)] | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(value_delimiter = ',')] | ||||||||||||||||||||||||||||||||||||||||||||||
allowed_origins: Option<Vec<String>>, | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(long, value_delimiter = ',')] | ||||||||||||||||||||||||||||||||||||||||||||||
allowed_origins: Vec<String>, | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
/// The external url of the server, used for configuring the GraphQL Playground in a hosted | ||||||||||||||||||||||||||||||||||||||||||||||
/// environment | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -139,32 +139,38 @@ | |||||||||||||||||||||||||||||||||||||||||||||
index_raw_events: bool, | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
/// ERC contract addresses to index | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(long, value_parser = parse_erc_contracts)] | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(conflicts_with = "config")] | ||||||||||||||||||||||||||||||||||||||||||||||
contracts: Option<std::vec::Vec<Contract>>, | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(long, value_delimiter = ',', value_parser = parse_erc_contract)] | ||||||||||||||||||||||||||||||||||||||||||||||
contracts: Vec<Contract>, | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
/// Event messages that are going to be treated as historical | ||||||||||||||||||||||||||||||||||||||||||||||
/// A list of the model tags (namespace-name) | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(long, value_delimiter = ',')] | ||||||||||||||||||||||||||||||||||||||||||||||
historical_events: Vec<String>, | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
/// Configuration file | ||||||||||||||||||||||||||||||||||||||||||||||
#[arg(long)] | ||||||||||||||||||||||||||||||||||||||||||||||
#[clap_config(skip)] | ||||||||||||||||||||||||||||||||||||||||||||||
config: Option<PathBuf>, | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
#[tokio::main] | ||||||||||||||||||||||||||||||||||||||||||||||
async fn main() -> anyhow::Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||
let args = Args::parse(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let mut config = if let Some(path) = args.config { | ||||||||||||||||||||||||||||||||||||||||||||||
ToriiConfig::load_from_path(&path)? | ||||||||||||||||||||||||||||||||||||||||||||||
let matches = <Args as CommandFactory>::command().get_matches(); | ||||||||||||||||||||||||||||||||||||||||||||||
let mut args = if let Some(path) = matches.get_one::<PathBuf>("config") { | ||||||||||||||||||||||||||||||||||||||||||||||
let config: ArgsConfig = toml::from_str(&std::fs::read_to_string(path)?)?; | ||||||||||||||||||||||||||||||||||||||||||||||
Args::from_merged(matches, Some(config)) | ||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||
let mut config = ToriiConfig::default(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
if let Some(contracts) = args.contracts { | ||||||||||||||||||||||||||||||||||||||||||||||
config.contracts = VecDeque::from(contracts); | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
Args::from_arg_matches(&matches)? | ||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+158
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider enhancing error handling for config file loading. The current implementation could benefit from more robust error handling and validation. Consider this improvement: let matches = <Args as CommandFactory>::command().get_matches();
let mut args = if let Some(path) = matches.get_one::<PathBuf>("config") {
- let config: ArgsConfig = toml::from_str(&std::fs::read_to_string(path)?)?;
+ let content = std::fs::read_to_string(path)
+ .with_context(|| format!("Failed to read config file: {}", path.display()))?;
+ let config: ArgsConfig = toml::from_str(&content)
+ .with_context(|| format!("Failed to parse TOML config at: {}", path.display()))?;
Args::from_merged(matches, Some(config))
} else {
Args::from_arg_matches(&matches)?
}; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
config | ||||||||||||||||||||||||||||||||||||||||||||||
let world_address = if let Some(world_address) = args.world_address { | ||||||||||||||||||||||||||||||||||||||||||||||
world_address | ||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||
return Err(anyhow::anyhow!("Please specify a world address.")); | ||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let world_address = verify_single_world_address(args.world_address, &mut config)?; | ||||||||||||||||||||||||||||||||||||||||||||||
// let mut contracts = parse_erc_contracts(&args.contracts)?; | ||||||||||||||||||||||||||||||||||||||||||||||
args.contracts.push(Contract { address: world_address, r#type: ContractType::WORLD }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let filter_layer = EnvFilter::try_from_default_env() | ||||||||||||||||||||||||||||||||||||||||||||||
.unwrap_or_else(|_| EnvFilter::new("info,hyper_reverse_proxy=off")); | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -210,15 +216,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||
// Get world address | ||||||||||||||||||||||||||||||||||||||||||||||
let world = WorldContractReader::new(world_address, provider.clone()); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let contracts = | ||||||||||||||||||||||||||||||||||||||||||||||
config.contracts.iter().map(|contract| (contract.address, contract.r#type)).collect(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let (mut executor, sender) = Executor::new(pool.clone(), shutdown_tx.clone()).await?; | ||||||||||||||||||||||||||||||||||||||||||||||
tokio::spawn(async move { | ||||||||||||||||||||||||||||||||||||||||||||||
executor.run().await.unwrap(); | ||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let db = Sql::new(pool.clone(), sender.clone(), &contracts).await?; | ||||||||||||||||||||||||||||||||||||||||||||||
let db = Sql::new(pool.clone(), sender.clone(), &args.contracts).await?; | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let processors = Processors { | ||||||||||||||||||||||||||||||||||||||||||||||
transaction: vec![Box::new(StoreTransactionProcessor)], | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -248,10 +251,13 @@ | |||||||||||||||||||||||||||||||||||||||||||||
index_pending: args.index_pending, | ||||||||||||||||||||||||||||||||||||||||||||||
polling_interval: Duration::from_millis(args.polling_interval), | ||||||||||||||||||||||||||||||||||||||||||||||
flags, | ||||||||||||||||||||||||||||||||||||||||||||||
event_processor_config: EventProcessorConfig { | ||||||||||||||||||||||||||||||||||||||||||||||
historical_events: args.historical_events.into_iter().collect(), | ||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||
shutdown_tx.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||
Some(block_tx), | ||||||||||||||||||||||||||||||||||||||||||||||
Arc::new(contracts), | ||||||||||||||||||||||||||||||||||||||||||||||
&args.contracts, | ||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let shutdown_rx = shutdown_tx.subscribe(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -270,7 +276,12 @@ | |||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||
.expect("Failed to start libp2p relay server"); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let proxy_server = Arc::new(Proxy::new(args.addr, args.allowed_origins, Some(grpc_addr), None)); | ||||||||||||||||||||||||||||||||||||||||||||||
let proxy_server = Arc::new(Proxy::new( | ||||||||||||||||||||||||||||||||||||||||||||||
args.addr, | ||||||||||||||||||||||||||||||||||||||||||||||
if args.allowed_origins.is_empty() { None } else { Some(args.allowed_origins) }, | ||||||||||||||||||||||||||||||||||||||||||||||
Some(grpc_addr), | ||||||||||||||||||||||||||||||||||||||||||||||
None, | ||||||||||||||||||||||||||||||||||||||||||||||
)); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let graphql_server = spawn_rebuilding_graphql_server( | ||||||||||||||||||||||||||||||||||||||||||||||
shutdown_tx.clone(), | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -321,26 +332,6 @@ | |||||||||||||||||||||||||||||||||||||||||||||
Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// Verifies that the world address is defined at most once | ||||||||||||||||||||||||||||||||||||||||||||||
// and returns the world address | ||||||||||||||||||||||||||||||||||||||||||||||
fn verify_single_world_address( | ||||||||||||||||||||||||||||||||||||||||||||||
world_address: Option<Felt>, | ||||||||||||||||||||||||||||||||||||||||||||||
config: &mut ToriiConfig, | ||||||||||||||||||||||||||||||||||||||||||||||
) -> anyhow::Result<Felt> { | ||||||||||||||||||||||||||||||||||||||||||||||
let world_from_config = | ||||||||||||||||||||||||||||||||||||||||||||||
config.contracts.iter().find(|c| c.r#type == ContractType::WORLD).map(|c| c.address); | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
match (world_address, world_from_config) { | ||||||||||||||||||||||||||||||||||||||||||||||
(Some(_), Some(_)) => Err(anyhow::anyhow!("World address specified multiple times")), | ||||||||||||||||||||||||||||||||||||||||||||||
(Some(addr), _) => { | ||||||||||||||||||||||||||||||||||||||||||||||
config.contracts.push_front(Contract { address: addr, r#type: ContractType::WORLD }); | ||||||||||||||||||||||||||||||||||||||||||||||
Ok(addr) | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
(_, Some(addr)) => Ok(addr), | ||||||||||||||||||||||||||||||||||||||||||||||
(None, None) => Err(anyhow::anyhow!("World address not specified")), | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
async fn spawn_rebuilding_graphql_server( | ||||||||||||||||||||||||||||||||||||||||||||||
shutdown_tx: Sender<()>, | ||||||||||||||||||||||||||||||||||||||||||||||
pool: Arc<SqlitePool>, | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -368,25 +359,20 @@ | |||||||||||||||||||||||||||||||||||||||||||||
// Parses clap cli argument which is expected to be in the format: | ||||||||||||||||||||||||||||||||||||||||||||||
// - erc_type:address:start_block | ||||||||||||||||||||||||||||||||||||||||||||||
// - address:start_block (erc_type defaults to ERC20) | ||||||||||||||||||||||||||||||||||||||||||||||
fn parse_erc_contracts(s: &str) -> anyhow::Result<Vec<Contract>> { | ||||||||||||||||||||||||||||||||||||||||||||||
let parts: Vec<&str> = s.split(',').collect(); | ||||||||||||||||||||||||||||||||||||||||||||||
let mut contracts = Vec::new(); | ||||||||||||||||||||||||||||||||||||||||||||||
for part in parts { | ||||||||||||||||||||||||||||||||||||||||||||||
match part.split(':').collect::<Vec<&str>>().as_slice() { | ||||||||||||||||||||||||||||||||||||||||||||||
[r#type, address] => { | ||||||||||||||||||||||||||||||||||||||||||||||
let r#type = r#type.parse::<ContractType>()?; | ||||||||||||||||||||||||||||||||||||||||||||||
let address = Felt::from_str(address) | ||||||||||||||||||||||||||||||||||||||||||||||
.with_context(|| format!("Expected address, found {}", address))?; | ||||||||||||||||||||||||||||||||||||||||||||||
contracts.push(Contract { address, r#type }); | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
[address] => { | ||||||||||||||||||||||||||||||||||||||||||||||
let r#type = ContractType::WORLD; | ||||||||||||||||||||||||||||||||||||||||||||||
let address = Felt::from_str(address) | ||||||||||||||||||||||||||||||||||||||||||||||
.with_context(|| format!("Expected address, found {}", address))?; | ||||||||||||||||||||||||||||||||||||||||||||||
contracts.push(Contract { address, r#type }); | ||||||||||||||||||||||||||||||||||||||||||||||
fn parse_erc_contract(part: &str) -> anyhow::Result<Contract> { | ||||||||||||||||||||||||||||||||||||||||||||||
match part.split(':').collect::<Vec<&str>>().as_slice() { | ||||||||||||||||||||||||||||||||||||||||||||||
[r#type, address] => { | ||||||||||||||||||||||||||||||||||||||||||||||
let r#type = r#type.parse::<ContractType>()?; | ||||||||||||||||||||||||||||||||||||||||||||||
if r#type == ContractType::WORLD { | ||||||||||||||||||||||||||||||||||||||||||||||
return Err(anyhow::anyhow!( | ||||||||||||||||||||||||||||||||||||||||||||||
"World address cannot be specified as an ERC contract" | ||||||||||||||||||||||||||||||||||||||||||||||
)); | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
_ => return Err(anyhow::anyhow!("Invalid contract format")), | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
let address = Felt::from_str(address) | ||||||||||||||||||||||||||||||||||||||||||||||
.with_context(|| format!("Expected address, found {}", address))?; | ||||||||||||||||||||||||||||||||||||||||||||||
Ok(Contract { address, r#type }) | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
_ => Err(anyhow::anyhow!("Invalid contract format")), | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
Ok(contracts) | ||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,14 @@ | ||
# Example configuration file for Torii | ||
# contracts = [ | ||
# { type = "WORLD", address = "<WORLD_CONTRACT_ADDRESS>" }, | ||
# { type = "ERC20", address = "<ERC20_CONTRACT_ADDRESS>" }, | ||
# { type = "ERC721", address = "<ERC721_CONTRACT_ADDRESS>" }, | ||
# ] | ||
world_address="0x054d0f13bf3fb5f15a8674c5204aad35e3022af96bcc23bdbd16b7e297ffd399" | ||
addr="0.0.0.0:8080" | ||
rpc="http://0.0.0.0:5050" | ||
|
||
historical_events=["ns-Moved", "ns-Spawned"] | ||
|
||
[[contracts]] | ||
type="ERC20" | ||
address="0x0" | ||
|
||
[[contracts]] | ||
type="ERC721" | ||
Comment on lines
+8
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance contract configuration validation and documentation. The contract definitions use placeholder addresses and lack documentation about supported types. Consider adding:
Here's a suggested format with documentation: # Contract Definitions
# Supported types: ERC20, ERC721
# Address format: 0x-prefixed hex string of appropriate length
[[contracts]]
# ERC20 token contract
type = "ERC20"
address = "0x1234567890123456789012345678901234567890" # Example format
[[contracts]]
# ERC721 NFT contract
type = "ERC721"
address = "0x1234567890123456789012345678901234567890" # Example format |
||
address="0x123" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
🛠️ Refactor suggestion
Consider using clap directly instead of clap_config, sensei!
The verification reveals:
clap_config
is only used in one place (bin/torii/src/main.rs
) for a simpleskip
attributeclap
directly, as seen in multiple filesThe functionality can be replaced with clap's built-in attributes, making the dependency unnecessary:
#[clap_config(skip)]
with#[arg(skip)]
🔗 Analysis chain
Consider workspace version management and package stability, sensei!
The
clap_config
dependency has a few concerns:.workspace = true
) like other dependenciesLet's check the package's health:
Consider:
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 257
Script:
Length of output: 15413