mod auth;
mod cache;
mod commands;
mod render;
#[allow(dead_code)]
mod wrappers;
use std::path::PathBuf;
use clap::{Parser, Subcommand, ValueEnum};
#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "lowercase")]
pub enum OutputFormat {
Text,
Json,
Sarif,
}
impl OutputFormat {
pub fn as_wire_str(self) -> &'static str {
match self {
OutputFormat::Text => "text",
OutputFormat::Json => "json",
OutputFormat::Sarif => "sarif",
}
}
}
#[derive(Parser)]
#[command(name = "cleanlib", version, about, long_about = None, arg_required_else_help = true)]
struct Cli {
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Status,
Config {
#[command(subcommand)]
action: ConfigAction,
},
Login {
#[arg(long)]
api_key: String,
#[arg(long)]
keyring: bool,
},
Logout {
#[arg(long)]
keyring: bool,
},
#[command(name = "risk-accept")]
RiskAccept {
#[arg(long)]
ecosystem: String,
#[arg(long)]
package: String,
#[arg(long)]
version: String,
#[arg(long)]
justification: String,
#[arg(long)]
proposed_by: Option<String>,
#[arg(long)]
write_to: Option<PathBuf>,
#[arg(long)]
force: bool,
},
Verdict {
#[arg(long)]
ecosystem: String,
#[arg(long)]
package: String,
#[arg(long)]
version: String,
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
output: OutputFormat,
},
Scan {
#[arg(long)]
ecosystem: String,
#[arg(long)]
packages: PathBuf,
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
output: OutputFormat,
},
Audit {
#[arg(long)]
since: Option<String>,
#[arg(long)]
decision: Option<String>,
#[arg(long)]
ecosystem: Option<String>,
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
output: OutputFormat,
},
Policy {
#[command(subcommand)]
action: PolicyAction,
},
Fetch {
ecosystem: String,
package: String,
version: String,
#[arg(long)]
output: Option<PathBuf>,
},
Npm {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Pip {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Cargo {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Go {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
Fix {
#[arg(long)]
lockfile: PathBuf,
#[arg(long)]
apply: bool,
#[arg(long)]
risk_accept: Option<PathBuf>,
#[arg(long)]
allow_list: Option<PathBuf>,
#[arg(long)]
auto_pr: bool,
#[arg(long)]
pr_remote: Option<String>,
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
output: OutputFormat,
},
}
#[derive(Subcommand)]
enum PolicyAction {
Preview {
#[arg(long)]
policy: PathBuf,
#[arg(long)]
packages: PathBuf,
#[arg(long)]
ecosystem: String,
#[arg(long, value_enum, default_value_t = OutputFormat::Text)]
output: OutputFormat,
},
}
#[derive(Subcommand)]
enum ConfigAction {
Init {
#[arg(long, value_delimiter = ',', default_values_t = vec!["npm".to_string(), "pypi".to_string(), "go".to_string()])]
ecosystem: Vec<String>,
#[arg(long)]
scope: Option<String>,
#[arg(long)]
write: bool,
#[arg(long)]
write_to: Option<PathBuf>,
#[arg(long)]
inline_token: bool,
#[arg(long)]
force: bool,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Some(Command::Status) => commands::status::run(),
Some(Command::Config { action }) => match action {
ConfigAction::Init {
ecosystem,
scope,
write,
write_to,
inline_token,
force,
} => commands::config_init::run(ecosystem, scope, write, write_to, inline_token, force),
},
Some(Command::Login { api_key, keyring }) => commands::login::run(api_key, keyring).await,
Some(Command::Logout { keyring }) => commands::logout::run(keyring),
Some(Command::RiskAccept {
ecosystem,
package,
version,
justification,
proposed_by,
write_to,
force,
}) => commands::risk_accept::run(
ecosystem,
package,
version,
justification,
proposed_by,
write_to,
force,
),
Some(Command::Verdict {
ecosystem,
package,
version,
output,
}) => commands::verdict::run(ecosystem, package, version, output.as_wire_str().to_string()).await,
Some(Command::Scan {
ecosystem,
packages,
output,
}) => commands::scan::run(ecosystem, packages, output.as_wire_str().to_string()).await,
Some(Command::Audit {
since,
decision,
ecosystem,
output,
}) => commands::audit::run(since, decision, ecosystem, output.as_wire_str().to_string()).await,
Some(Command::Policy { action }) => match action {
PolicyAction::Preview {
policy,
packages,
ecosystem,
output,
} => commands::policy::preview(policy, packages, ecosystem, output.as_wire_str().to_string()).await,
},
Some(Command::Fetch {
ecosystem,
package,
version,
output,
}) => commands::fetch::run(ecosystem, package, version, output).await,
Some(Command::Npm { args }) => commands::wrap::run("npm", args).await,
Some(Command::Pip { args }) => commands::wrap::run("pip", args).await,
Some(Command::Cargo { args }) => commands::wrap::run("cargo", args).await,
Some(Command::Go { args }) => commands::wrap::run("go", args).await,
Some(Command::Fix {
lockfile,
apply,
risk_accept,
allow_list,
auto_pr,
pr_remote,
output,
}) => {
commands::fix::run(commands::fix::FixArgs {
lockfile,
apply,
risk_accept,
allow_list,
auto_pr,
pr_remote,
output: output.as_wire_str().to_string(),
})
.await
}
None => Ok(()),
}
}