use std::io::{self};
use std::path::PathBuf;
use clap::builder::styling;
use clap::{
ColorChoice, CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint
};
use clap_complete::{Shell, generate};
use colored::Colorize;
use zoi_common::Runnable;
use crate::pkg::lock;
use crate::{cmd, utils};
const BRANCH: &str = "Prod";
const STATUS: &str = "Release";
const NUMBER: &str = "1.29.3";
const PKG_SOURCE_HELP: &str =
"Package identifier (e.g. @repo/name, #git@repo/name, path, or URL)";
#[derive(Parser)]
#[command(name = "zoi", author, about, long_about = None, disable_version_flag = true,
trailing_var_arg = true,
color = ColorChoice::Auto,
arg_required_else_help = true,
)]
pub struct Cli {
#[command(subcommand)]
command: Option<Commands>,
#[arg(
short = 'v',
long = "version",
help = "Print detailed version information"
)]
version_flag: bool,
#[arg(
short = 'y',
long,
help = "Automatically answer yes to all prompts",
global = true
)]
yes: bool,
#[arg(
long = "root",
help = "Operate on a different root directory",
global = true,
value_hint = ValueHint::DirPath
)]
pub root: Option<std::path::PathBuf>,
#[arg(
long = "offline",
help = "Do not attempt to connect to the network",
global = true
)]
pub offline: bool,
#[arg(
long = "pkg-dir",
help = "Additional directory to search for .zpa archives",
global = true,
value_hint = ValueHint::DirPath
)]
pub pkg_dirs: Vec<std::path::PathBuf>
}
#[derive(clap::ValueEnum, Clone, Debug, Copy, PartialEq, Eq)]
pub enum SetupScope {
User,
System
}
#[derive(clap::ValueEnum, Clone, Debug, Copy)]
pub enum InstallScope {
User,
System,
Project
}
#[derive(Subcommand)]
pub enum AltCommand {
#[command(alias = "ls")]
List {
#[arg(short, long)]
verbose: bool
},
Get {
name: String
},
Set {
name: String,
path: String
},
Auto {
name: String
},
Remove {
name: String,
path: String
}
}
#[derive(Subcommand)]
enum Commands {
#[command(hide = true)]
GenerateCompletions {
#[arg(value_enum)]
shell: Shell
},
#[command(hide = true)]
Complete {
#[arg(value_enum)]
shell: Shell,
index: usize,
words: Vec<String>
},
#[command(
alias = "v",
long_about = "Displays the version number, build status, branch, and \
commit hash. This is the same output provided by the -v \
and --version flags."
)]
Version,
#[command(long_about = "Displays the full application name, description, \
author, license, and homepage information.")]
About,
#[command(long_about = "Detects and displays key system details, \
including the OS, CPU architecture, Linux \
distribution (if applicable), and available \
package managers.")]
Info,
#[command(
alias = "dl",
long_about = "Downloads the binary archive (.zpa) or source bundle \
(.zsa) for a package to the local cache or a specified \
directory."
)]
Download {
#[arg(value_name = "PACKAGE", required = true, help = PKG_SOURCE_HELP)]
package: String,
#[arg(long, group = "type")]
archive: bool,
#[arg(long, group = "type")]
source: bool,
#[arg(short, long)]
output_dir: Option<PathBuf>
},
#[command(
alias = "sy",
long_about = "Clones the official package database from GitLab to \
Zoi's user data directory. If the database already \
exists, it verifies the remote URL and pulls the latest \
changes."
)]
Sync {
#[command(subcommand)]
command: Option<SyncCommands>,
#[arg(short, long)]
verbose: bool,
#[arg(long)]
fallback: bool,
#[arg(long = "no-pm")]
no_package_managers: bool,
#[arg(long)]
force: bool,
#[arg(long)]
local: bool,
#[arg(long)]
frozen: bool,
#[arg(long, value_enum, conflicts_with = "local")]
scope: Option<SetupScope>
},
Migrate(cmd::migrate::MigrateCommand),
#[command(alias = "ls")]
List {
#[arg(short, long)]
all: bool,
#[arg(short, long)]
outdated: bool,
#[arg(long)]
registry: Option<String>,
#[arg(long)]
repo: Option<String>,
#[arg(short = 't', long = "type")]
package_type: Option<String>,
#[arg(short = 'm', long)]
foreign: bool,
#[arg(long, hide = true)]
names: bool,
#[arg(long, hide = true)]
completion: bool
},
Show {
#[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
package_name: String,
#[arg(long)]
raw: bool,
#[arg(long)]
purl: bool
},
Pin {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package: String,
version: String
},
Provides {
term: String
},
Tree {
#[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
packages: Vec<String>
},
Unpin {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package: String
},
#[command(
alias = "m",
long_about = "Changes whether a package is considered explicitly installed or a dependency. Explicit packages are not removed by 'autoremove', while dependencies are if no other package requires them.",
group(clap::ArgGroup::new("mode").required(true).args(["as_dependency", "as_explicit"]))
)]
Mark {
#[arg(value_name = "INST_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
packages: Vec<String>,
#[arg(long, aliases = ["asdeps"])]
as_dependency: bool,
#[arg(long, aliases = ["asexpl"], conflicts_with = "as_dependency")]
as_explicit: bool
},
#[command(
alias = "up",
arg_required_else_help = true,
group(clap::ArgGroup::new("target").required(true).args(["package_names", "all"]))
)]
Update {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package_names: Vec<String>,
#[arg(long, conflicts_with = "package_names")]
all: bool,
#[arg(long)]
dry_run: bool,
#[arg(long)]
explain: bool,
#[arg(long, requires = "dry_run")]
plan_json: bool,
#[arg(long, short)]
verbose: bool,
#[arg(long, requires = "all")]
interactive: bool
},
#[command(aliases = ["i", "in", "add"])]
Install(cmd::install::args::InstallArgs),
#[command(
aliases = ["un", "rm", "remove"],
long_about = "Removes one or more packages' files from the Zoi store and deletes their symlinks from the bin directory. This command will fail if a package was not installed by Zoi."
)]
Uninstall(cmd::uninstall::args::UninstallArgs),
#[command(long_about = "Execute a command from zoi.lua. If no command is \
specified, it will launch an interactive prompt \
to choose one.")]
Run {
cmd_alias: Option<String>,
args: Vec<String>
},
#[command(long_about = "Checks for required packages and runs setup \
commands for a defined environment. If no \
environment is specified, it launches an \
interactive prompt.")]
Env {
env_alias: Option<String>,
#[arg(long, value_enum, hide = true)]
export_shell: Option<Shell>
},
#[command(
alias = "develop",
long_about = "Loads the project configuration from zoi.lua, ensures \
all required packages are installed locally, sets up \
environment variables (PATH, LD_LIBRARY_PATH, etc.), \
and drops you into a subshell."
)]
Dev {
#[arg(short, long)]
run: Option<String>,
#[arg(long)]
repo: Option<String>
},
#[command(
alias = "ug",
long_about = "Upgrades Zoi to the latest version. By default, it \
attempts a delta upgrade (bsdiff) to minimize download \
size. If the delta upgrade is unavailable or fails, it \
automatically falls back to a full download."
)]
Upgrade {
#[arg(long)]
force: bool,
#[arg(long)]
tag: Option<String>,
#[arg(long)]
branch: Option<String>
},
Autoremove {
#[arg(long)]
dry_run: bool
},
Why {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package_name: String
},
#[command(alias = "owns")]
Owner {
#[arg(value_hint = ValueHint::FilePath)]
path: std::path::PathBuf
},
Files {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package: String
},
#[command(subcommand, name = "alt")]
Alt(AltCommand),
History {
#[arg(long, conflicts_with = "export")]
verify: bool,
#[arg(long, value_hint = ValueHint::FilePath, conflicts_with = "verify")]
export: Option<std::path::PathBuf>,
#[arg(long, requires = "export")]
ndjson: bool
},
#[command(
alias = "s",
long_about = "Searches for a case-insensitive term in the name, \
description, and tags of all available packages in the \
database. Filter by repo, type, or tags."
)]
Search {
search_term: String,
#[arg(long)]
registry: Option<String>,
#[arg(long)]
repo: Option<String>,
#[arg(long = "type")]
package_type: Option<String>,
#[arg(short = 't', long = "tag", value_delimiter = ',', num_args = 1..)]
tags: Option<Vec<String>>,
#[arg(long, default_value = "name")]
sort: String,
#[arg(short, long)]
files: bool,
#[arg(short = 'i', long)]
interactive: bool
},
#[command(alias = "svc")]
Service(cmd::service::ServiceCommand),
#[command(
long_about = "If a shell is provided, it installs completion scripts. If 'hook' is provided, it outputs shell-specific hook scripts for auto-activation. If packages are provided via --package/-p, it enters a temporary subshell with those packages available in PATH.",
arg_required_else_help = true,
group(clap::ArgGroup::new("shell_action").required(true).args(["shell", "hook", "packages"]).multiple(true))
)]
Shell {
#[arg(value_enum)]
shell: Option<Shell>,
#[arg(long)]
hook: bool,
#[arg(long, value_enum, default_value = "user")]
scope: SetupScope,
#[arg(short, long = "package", value_name = "ALL_PACKAGES")]
packages: Vec<String>,
#[arg(short, long)]
run: Option<String>,
#[arg(long, short)]
verbose: bool
},
#[command(
alias = "x",
long_about = "Resolves a package and its dependencies, installs them \
if needed, then runs the requested binary directly. By \
default runs the first binary the package provides. \
Uses bwrap for sandboxed packages."
)]
Exec {
#[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
source: String,
#[arg(long)]
bin: Option<String>,
#[arg(long, short)]
verbose: bool,
#[arg(value_name = "ARGS")]
args: Vec<String>
},
Clone {
#[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
package: String,
#[arg(value_name = "LOCATION")]
location: Option<String>
},
Cache {
#[command(subcommand)]
command: CacheCommands
},
#[command(alias = "tx")]
Transaction {
#[command(subcommand)]
command: TransactionCommands
},
#[command(alias = "reg")]
Registry(cmd::registry::RegistryCommand),
Home(cmd::home::HomeCommand),
System(cmd::system::SystemCommand),
#[command(
aliases = ["repositories"],
long_about = "Manages the list of package repositories used by Zoi.\n\nCommands:\n- add (alias: a): Add an official repo by name or clone from a git URL.\n- remove|rm: Remove a repo from active list (repo rm <name>).\n- list|ls: Show active repositories by default; use 'list all' to show all available repositories.\n- git: Manage cloned git repositories (git ls, git rm <repo-name>)."
)]
Repo(cmd::repo::RepoCommand),
#[command(long_about = "Manage opt-in anonymous telemetry used to \
understand package popularity. Default is \
disabled.")]
Telemetry {
#[command(subcommand)]
command: TelemetrySubcommand
},
Init {
#[arg(short, long)]
force: bool
},
Create {
#[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
source: String,
app_name: Option<String>
},
#[command(
alias = "dg",
long_about = "Interactively choose and install an older version of a \
package from the local store or archive cache. This is \
useful if a recent update has introduced bugs or \
compatibility issues."
)]
Downgrade {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package: String
},
#[command(alias = "ext")]
Extension(ExtensionCommand),
Rollback {
#[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
package: String
},
Man {
#[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
package_name: String,
#[arg(long)]
upstream: bool,
#[arg(long)]
raw: bool,
#[arg(long)]
page: Option<String>
},
#[command(alias = "pkg")]
Package(cmd::package::PackageCommand),
Pgp(cmd::pgp::PgpCommand),
Helper(cmd::helper::HelperCommand),
Doctor,
Audit {
#[arg(short, long)]
all: bool,
#[arg(long)]
registry: Option<String>,
#[arg(long)]
repo: Option<String>
},
#[command(external_subcommand)]
External(Vec<String>)
}
#[derive(clap::Parser, Debug)]
pub struct ExtensionCommand {
#[command(subcommand)]
pub command: ExtensionCommands
}
#[derive(clap::Subcommand, Debug)]
pub enum ExtensionCommands {
Add {
#[arg(required = true)]
name: String
},
Remove {
#[arg(required = true)]
name: String
}
}
#[derive(clap::Subcommand, Clone)]
pub enum SyncCommands {
Add {
url: String
},
Remove {
handle: String
},
#[command(alias = "ls")]
List,
Set {
url: String
}
}
#[derive(clap::Subcommand)]
pub enum CacheCommands {
Add {
#[arg(required = true)]
files: Vec<std::path::PathBuf>
},
#[command(alias = "clean")]
Clear {
#[arg(long)]
dry_run: bool
},
#[command(alias = "ls")]
List,
Mirror {
#[command(subcommand)]
command: CacheMirrorCommands
}
}
#[derive(clap::Subcommand)]
pub enum CacheMirrorCommands {
Add {
url: String
},
Remove {
url: String
},
#[command(alias = "ls")]
List
}
#[derive(clap::Subcommand)]
pub enum TransactionCommands {
#[command(alias = "ls")]
List,
Show {
id: String
},
Files {
id: String
},
#[command(alias = "rollback")]
Undo {
id: Option<String>,
#[arg(long)]
yes: bool
}
}
#[derive(clap::Subcommand, Clone)]
pub enum TelemetrySubcommand {
Status,
Enable,
Disable,
Crash {
#[command(subcommand)]
command: CrashSubcommand
},
#[command(hide = true)]
Flush
}
#[derive(clap::Subcommand, Clone)]
pub enum CrashSubcommand {
#[command(alias = "ls")]
List,
Show {
file: PathBuf
},
Send {
file: PathBuf,
#[arg(long)]
yes: bool
},
Clear {
#[arg(long)]
yes: bool
}
}
fn command_name(command: &Commands) -> &'static str {
match command {
Commands::GenerateCompletions { .. } => "generate-completions",
Commands::Complete { .. } => "complete",
Commands::Version => "version",
Commands::About => "about",
Commands::Info => "info",
Commands::Download { .. } => "download",
Commands::Sync { .. } => "sync",
Commands::Migrate(_) => "migrate",
Commands::List { .. } => "list",
Commands::Show { .. } => "show",
Commands::Pin { .. } => "pin",
Commands::Provides { .. } => "provides",
Commands::Tree { .. } => "tree",
Commands::Unpin { .. } => "unpin",
Commands::Mark { .. } => "mark",
Commands::Update { .. } => "update",
Commands::Install(_) => "install",
Commands::Uninstall(_) => "uninstall",
Commands::Run { .. } => "run",
Commands::Env { .. } => "env",
Commands::Dev { .. } => "dev",
Commands::Upgrade { .. } => "upgrade",
Commands::Autoremove { .. } => "autoremove",
Commands::Why { .. } => "why",
Commands::Owner { .. } => "owner",
Commands::Files { .. } => "files",
Commands::Alt(_) => "alt",
Commands::History { .. } => "history",
Commands::Search { .. } => "search",
Commands::Service(_) => "service",
Commands::Shell { .. } => "shell",
Commands::Exec { .. } => "exec",
Commands::Clone { .. } => "clone",
Commands::Cache { .. } => "cache",
Commands::Transaction { .. } => "transaction",
Commands::Repo(_) => "repo",
Commands::Registry(_) => "registry",
Commands::Home(_) => "home",
Commands::System { .. } => "system",
Commands::Telemetry { .. } => "telemetry",
Commands::Create { .. } => "create",
Commands::Init { .. } => "init",
Commands::Downgrade { .. } => "downgrade",
Commands::Extension(_) => "extension",
Commands::Rollback { .. } => "rollback",
Commands::Man { .. } => "man",
Commands::Package(_) => "package",
Commands::Pgp(_) => "pgp",
Commands::Helper(_) => "helper",
Commands::Doctor => "doctor",
Commands::Audit { .. } => "audit",
Commands::External(_) => "external"
}
}
pub fn run() -> anyhow::Result<()> {
let styles = styling::Styles::styled()
.header(
styling::AnsiColor::Yellow.on_default() | styling::Effects::BOLD
)
.usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
.literal(styling::AnsiColor::Green.on_default())
.placeholder(styling::AnsiColor::Cyan.on_default());
let commit: &str = option_env!("ZOI_COMMIT_HASH").unwrap_or("dev");
let cmd = Cli::command().styles(styles.clone());
let matches = cmd.clone().get_matches();
let cli = match Cli::from_arg_matches(&matches) {
Ok(cli) => cli,
Err(err) => {
err.print()?;
return Err(anyhow::anyhow!("Failed to parse arguments"));
}
};
if let Some(root) = cli.root {
crate::pkg::sysroot::set_sysroot(root);
}
let config = crate::pkg::config::read_config().unwrap_or_default();
let is_offline = cli.offline || config.offline_mode;
crate::pkg::offline::set_offline(is_offline);
let prompt_pending_crashes =
!matches!(cli.command, Some(Commands::Telemetry { .. }));
let _telemetry_guards =
zoi_telemetry::init_telemetry(prompt_pending_crashes);
let mut all_pkg_dirs = cli.pkg_dirs;
for dir in config.pkg_dirs {
let path = std::path::PathBuf::from(dir);
if !all_pkg_dirs.contains(&path) {
all_pkg_dirs.push(path);
}
}
crate::pkg::pkgdir::set_pkg_dirs(all_pkg_dirs);
utils::check_path();
if let Err(e) = crate::pkg::pgp::ensure_builtin_keys() {
eprintln!(
"{}: Failed to ensure builtin PGP keys: {}",
"Warning".yellow(),
e
);
}
let plugin_manager = crate::pkg::plugin::PluginManager::new()?;
if let Err(e) = plugin_manager.load_all(cli.yes) {
eprintln!("{}: Failed to load plugins: {}", "Warning".yellow(), e);
}
if cli.version_flag {
cmd::version::run(BRANCH, STATUS, NUMBER, commit);
return Ok(());
}
if let Some(command) = cli.command {
let command_name = command_name(&command);
let is_flush_command = matches!(
command,
Commands::Telemetry {
command: TelemetrySubcommand::Flush
}
);
unsafe {
std::env::set_var("ZOI_CRASH_COMMAND", command_name);
}
let needs_lock = matches!(
command,
Commands::Install { .. }
| Commands::Uninstall { .. }
| Commands::Update { .. }
| Commands::Autoremove { .. }
| Commands::Rollback { .. }
| Commands::Package(_)
);
let _lock_guard = if needs_lock {
Some(lock::acquire_lock()?)
} else {
None
};
let result = match command {
Commands::GenerateCompletions { shell } => {
let mut cmd = Cli::command();
let bin_name = cmd.get_name().to_string();
generate(shell, &mut cmd, bin_name, &mut io::stdout());
Ok(())
}
Commands::Complete {
shell,
index,
words
} => cmd::complete::run(shell, index, &words),
Commands::Version => {
cmd::version::run(BRANCH, STATUS, NUMBER, commit);
Ok(())
}
Commands::About => {
cmd::about::run(BRANCH, STATUS, NUMBER, commit);
Ok(())
}
Commands::Info => cmd::info::run(BRANCH, STATUS, NUMBER, commit),
Commands::Sync {
command,
verbose,
fallback,
no_package_managers,
force,
local,
frozen,
scope
} => {
if let Some(cmd) = command {
match cmd {
SyncCommands::Add { url } => {
cmd::sync::add_registry(&url)
}
SyncCommands::Remove { handle } => {
cmd::sync::remove_registry(&handle)
}
SyncCommands::List => cmd::sync::list_registries(),
SyncCommands::Set { url } => {
cmd::sync::set_registry(&url)
}
}
} else if local {
plugin_manager.trigger_hook("on_pre_sync", None)?;
let res =
cmd::sync::run_local(verbose, fallback, force, frozen);
plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
res
} else {
plugin_manager.trigger_hook("on_pre_sync", None)?;
let res = cmd::sync::run(
verbose,
fallback,
no_package_managers,
force,
scope
);
plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
res
}
}
Commands::Migrate(args) => cmd::migrate::run(args),
Commands::List {
all,
outdated,
registry,
repo,
package_type,
foreign,
names,
completion
} => cmd::list::run(
all,
outdated,
registry.as_deref(),
repo.as_deref(),
package_type.as_deref(),
foreign,
names,
completion
),
Commands::Show {
package_name,
raw,
purl
} => cmd::show::run(&package_name, raw, purl),
Commands::Pin { package, version } => {
cmd::pin::run(&package, &version, cli.yes)
}
Commands::Provides { term } => cmd::provides::run(&term),
Commands::Tree { packages } => cmd::tree::run(&packages),
Commands::Unpin { package } => cmd::unpin::run(&package, cli.yes),
Commands::Mark {
packages,
as_dependency,
as_explicit
} => cmd::mark::run(&packages, as_dependency, as_explicit),
Commands::Update {
package_names,
all,
dry_run,
explain,
plan_json,
verbose,
interactive
} => cmd::update::run(
all,
&package_names,
cli.yes,
dry_run,
explain,
plan_json,
verbose,
interactive
)
.map_err(|e| cmd::ux::with_failure_hint("update", e)),
Commands::Install(args) => args
.run(cli.yes)
.map_err(|e| cmd::ux::with_failure_hint("install", e)),
Commands::Uninstall(args) => args
.run(cli.yes)
.map_err(|e| cmd::ux::with_failure_hint("uninstall", e)),
Commands::Run { cmd_alias, args } => {
cmd::run::run(cmd_alias.as_deref(), &args)
}
Commands::Env {
env_alias,
export_shell
} => cmd::env::run(env_alias.as_deref(), export_shell),
Commands::Dev { run, repo } => cmd::dev::run(run, repo),
Commands::Upgrade { force, tag, branch } => {
match cmd::upgrade::run(
BRANCH, STATUS, NUMBER, force, tag, branch
) {
Ok(()) => {
println!(
"\n{}",
"Zoi upgraded successfully! Please restart your \
shell for changes to take effect."
.green()
);
println!(
"\n{}: https://github.com/zillowe/zoi/blob/main/CHANGELOG.md",
"Changelog".cyan().bold()
);
println!(
"\n{}: To update shell completions, run 'zoi \
shell <your-shell>'.",
"Hint".cyan().bold()
);
}
Err(e) if e.to_string() == "already_on_latest" => {}
Err(e) if e.to_string() == "managed_by_package_manager" => {
}
Err(e) => return Err(e)
}
Ok(())
}
Commands::Autoremove { dry_run } => {
cmd::autoremove::run(cli.yes, dry_run)
}
Commands::Why { package_name } => cmd::why::run(&package_name),
Commands::Owner { path } => cmd::owner::run(&path),
Commands::Alt(ref args) => cmd::alt::run(args),
Commands::Files { package } => cmd::files::run(&package),
Commands::History {
verify,
export,
ndjson
} => cmd::history::run(verify, export, ndjson),
Commands::Search {
search_term,
registry,
repo,
package_type,
tags,
sort,
files,
interactive
} => cmd::search::run(
&search_term,
registry.as_deref(),
repo.as_deref(),
package_type.as_deref(),
tags,
&sort,
files,
interactive
),
Commands::Service(args) => cmd::service::run(args),
Commands::Shell {
shell,
hook,
scope,
packages,
run,
verbose
} => {
let target_shell = shell
.or_else(crate::pkg::utils::get_current_shell)
.unwrap_or(Shell::Bash);
if hook {
cmd::shell::print_hook(target_shell)
} else if !packages.is_empty() {
cmd::shell::enter_ephemeral_shell(
&packages,
run,
verbose,
Some(&plugin_manager)
)
} else {
cmd::shell::run(target_shell, scope)
}
}
Commands::Exec {
source,
bin,
verbose,
args
} => cmd::exec::run(&source, bin, &args, verbose),
Commands::Download {
package,
archive: _,
source,
output_dir
} => {
let download_type = if source {
cmd::download::DownloadType::Source
} else {
cmd::download::DownloadType::Archive
};
cmd::download::run(&package, download_type, output_dir)
}
Commands::Clone { package, location } => {
cmd::clone::run(&package, location, cli.yes)
}
Commands::Cache { command } => match command {
CacheCommands::Add { files } => cmd::cache::add(&files),
CacheCommands::Clear { dry_run } => cmd::cache::clear(dry_run),
CacheCommands::List => cmd::cache::list(),
CacheCommands::Mirror { command } => match command {
CacheMirrorCommands::Add { url } => {
cmd::cache::add_mirror(&url)
}
CacheMirrorCommands::Remove { url } => {
cmd::cache::remove_mirror(&url)
}
CacheMirrorCommands::List => cmd::cache::list_mirrors()
}
},
Commands::Transaction { command } => match command {
TransactionCommands::List => cmd::transaction::list(),
TransactionCommands::Show { id } => cmd::transaction::show(&id),
TransactionCommands::Files { id } => {
cmd::transaction::files(&id)
}
TransactionCommands::Undo { id, yes } => {
cmd::transaction::undo(id.as_deref(), yes, cli.yes)
}
},
Commands::Repo(args) => cmd::repo::run(args),
Commands::Registry(args) => cmd::registry::run(args),
Commands::Home(args) => cmd::home::run(args),
Commands::System(args) => cmd::system::run(args, cli.yes),
Commands::Telemetry { command } => {
use cmd::telemetry::{CrashCommand, TelemetryCommand, run};
let cmd = match command {
TelemetrySubcommand::Status => TelemetryCommand::Status,
TelemetrySubcommand::Enable => TelemetryCommand::Enable,
TelemetrySubcommand::Disable => TelemetryCommand::Disable,
TelemetrySubcommand::Flush => TelemetryCommand::Flush,
TelemetrySubcommand::Crash { command } => {
TelemetryCommand::Crash(match command {
CrashSubcommand::List => CrashCommand::List,
CrashSubcommand::Show { file } => {
CrashCommand::Show { file }
}
CrashSubcommand::Send { file, yes } => {
CrashCommand::Send {
file,
yes: yes || cli.yes
}
}
CrashSubcommand::Clear { yes } => {
CrashCommand::Clear {
yes: yes || cli.yes
}
}
})
}
};
run(cmd)
}
Commands::Create { source, app_name } => cmd::create::run(
cmd::create::CreateCommand { source, app_name },
cli.yes,
Some(&plugin_manager)
),
Commands::Init { force } => cmd::init::run(cli.yes, force),
Commands::Downgrade { package } => {
cmd::downgrade::run(&package, cli.yes, Some(&plugin_manager))
}
Commands::Extension(args) => {
cmd::extension::run(args, cli.yes, Some(&plugin_manager))
}
Commands::Rollback { package } => {
cmd::rollback::run(&package, cli.yes, Some(&plugin_manager))
}
Commands::Man {
package_name,
upstream,
raw,
page
} => cmd::man::run(&package_name, upstream, raw, page.as_deref()),
Commands::Package(args) => cmd::package::run(args),
Commands::Pgp(args) => cmd::pgp::run(args),
Commands::Helper(args) => cmd::helper::run(args),
Commands::Doctor => cmd::doctor::run(),
Commands::Audit {
all,
registry,
repo
} => cmd::audit::run(all, registry, repo.as_deref()),
Commands::External(args) => {
let (cmd_name, cmd_args) =
if let Some((first, rest)) = args.split_first() {
(first, rest.to_vec())
} else {
return Err(anyhow::anyhow!("No command specified"));
};
match plugin_manager.run_command(cmd_name, cmd_args) {
Ok(true) => Ok(()),
Ok(false) => {
let mut shadow_cmd = Cli::command().styles(styles);
shadow_cmd =
shadow_cmd.allow_external_subcommands(false);
let err = shadow_cmd
.clone()
.try_get_matches_from(std::env::args())
.err()
.unwrap_or_else(|| {
shadow_cmd.error(
clap::error::ErrorKind::InvalidSubcommand,
format!(
"unrecognized subcommand '{cmd_name}'"
)
)
});
let plugin_cmds = plugin_manager.list_commands()?;
if !plugin_cmds.is_empty() {
eprintln!(
"{}:",
"Available Plugin Commands".cyan().bold()
);
for (pcmd, pdesc) in plugin_cmds {
if pdesc.is_empty() {
eprintln!(" {pcmd}");
} else {
eprintln!(
" {:<12} {}",
pcmd,
pdesc.dimmed()
);
}
}
eprintln!();
}
err.exit();
}
Err(e) => Err(e)
}
}
};
unsafe {
std::env::remove_var("ZOI_CRASH_COMMAND");
}
match &result {
Ok(()) => {
if !is_flush_command {
let _ = zoi_telemetry::posthog_capture_dau_ping();
}
}
Err(e) => {
zoi_telemetry::sentry_capture_error(command_name, e.as_ref());
}
}
if let Err(e) = result {
eprintln!("Error: {e}");
zoi_telemetry::shutdown_sentry();
std::process::exit(1);
}
}
Ok(())
}