1use std::io::{self};
2use std::path::PathBuf;
3
4use clap::builder::styling;
5use clap::{
6 ColorChoice, CommandFactory, FromArgMatches, Parser, Subcommand, ValueHint
7};
8use clap_complete::{Shell, generate};
9use colored::Colorize;
10use zoi_common::Runnable;
11
12use crate::pkg::lock;
13use crate::{cmd, utils};
14
15const BRANCH: &str = "Production";
18const STATUS: &str = "Release";
20const NUMBER: &str = "1.25.5";
22const PKG_SOURCE_HELP: &str =
24 "Package identifier (e.g. @repo/name, #git@repo/name, path, or URL)";
25
26#[derive(Parser)]
31#[command(name = "zoi", author, about, long_about = None, disable_version_flag = true,
32 trailing_var_arg = true,
33 color = ColorChoice::Auto,
34 arg_required_else_help = true,
35)]
36pub struct Cli {
37 #[command(subcommand)]
39 command: Option<Commands>,
40
41 #[arg(
43 short = 'v',
44 long = "version",
45 help = "Print detailed version information"
46 )]
47 version_flag: bool,
48
49 #[arg(
51 short = 'y',
52 long,
53 help = "Automatically answer yes to all prompts",
54 global = true
55 )]
56 yes: bool,
57
58 #[arg(
60 long = "root",
61 help = "Operate on a different root directory",
62 global = true,
63 value_hint = ValueHint::DirPath
64 )]
65 pub root: Option<std::path::PathBuf>,
66
67 #[arg(
69 long = "offline",
70 help = "Do not attempt to connect to the network",
71 global = true
72 )]
73 pub offline: bool,
74
75 #[arg(
77 long = "pkg-dir",
78 help = "Additional directory to search for .zpa archives",
79 global = true,
80 value_hint = ValueHint::DirPath
81 )]
82 pub pkg_dirs: Vec<std::path::PathBuf>
84}
85
86#[derive(clap::ValueEnum, Clone, Debug, Copy, PartialEq, Eq)]
88pub enum SetupScope {
89 User,
91 System
93}
94
95#[derive(clap::ValueEnum, Clone, Debug, Copy)]
97pub enum InstallScope {
98 User,
100 System,
102 Project
104}
105
106#[derive(Subcommand)]
108enum Commands {
109 #[command(hide = true)]
111 GenerateCompletions {
112 #[arg(value_enum)]
114 shell: Shell
115 },
116
117 #[command(hide = true)]
119 Complete {
120 #[arg(value_enum)]
122 shell: Shell,
123 index: usize,
125 words: Vec<String>
127 },
128
129 #[command(hide = true)]
131 GenerateManual,
132
133 #[command(
135 alias = "v",
136 long_about = "Displays the version number, build status, branch, and \
137 commit hash. This is the same output provided by the -v \
138 and --version flags."
139 )]
140 Version,
141
142 #[command(long_about = "Displays the full application name, description, \
144 author, license, and homepage information.")]
145 About,
146
147 #[command(long_about = "Detects and displays key system details, \
149 including the OS, CPU architecture, Linux \
150 distribution (if applicable), and available \
151 package managers.")]
152 Info,
153
154 #[command(
156 alias = "dl",
157 long_about = "Downloads the binary archive (.zpa) or source bundle \
158 (.zsa) for a package to the local cache or a specified \
159 directory."
160 )]
161 Download {
162 #[arg(value_name = "PACKAGE", required = true, help = PKG_SOURCE_HELP)]
164 package: String,
165
166 #[arg(long, group = "type")]
168 archive: bool,
169
170 #[arg(long, group = "type")]
172 source: bool,
173
174 #[arg(short, long)]
176 output_dir: Option<PathBuf>
177 },
178
179 #[command(
181 alias = "sy",
182 long_about = "Clones the official package database from GitLab to \
183 your local machine (~/.zoi/pkgs/db). If the database \
184 already exists, it verifies the remote URL and pulls \
185 the latest changes."
186 )]
187 Sync {
188 #[command(subcommand)]
190 command: Option<SyncCommands>,
191
192 #[arg(short, long)]
194 verbose: bool,
195
196 #[arg(long)]
198 fallback: bool,
199
200 #[arg(long = "no-pm")]
202 no_package_managers: bool,
203
204 #[arg(long)]
207 force: bool,
208
209 #[arg(long)]
212 local: bool,
213
214 #[arg(long)]
217 frozen: bool,
218
219 #[arg(long, value_enum, conflicts_with = "local")]
221 scope: Option<SetupScope>
222 },
223
224 Migrate(cmd::migrate::MigrateCommand),
226
227 #[command(alias = "ls")]
229 List {
230 #[arg(short, long)]
232 all: bool,
233 #[arg(short, long)]
235 outdated: bool,
236 #[arg(long)]
238 registry: Option<String>,
239 #[arg(long)]
241 repo: Option<String>,
242 #[arg(short = 't', long = "type")]
244 package_type: Option<String>,
245 #[arg(short = 'm', long)]
247 foreign: bool,
248 #[arg(long, hide = true)]
250 names: bool,
251 #[arg(long, hide = true)]
253 completion: bool
254 },
255
256 Show {
258 #[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
260 package_name: String,
261 #[arg(long)]
263 raw: bool,
264 #[arg(long)]
266 purl: bool
267 },
268
269 Pin {
271 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
273 package: String,
274 version: String
276 },
277
278 Provides {
280 term: String
282 },
283
284 Tree {
286 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
288 packages: Vec<String>
289 },
290
291 Unpin {
293 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
295 package: String
296 },
297
298 #[command(
300 alias = "m",
301 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.",
302 group(clap::ArgGroup::new("mode").required(true).args(["as_dependency", "as_explicit"]))
303 )]
304 Mark {
305 #[arg(value_name = "INST_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
307 packages: Vec<String>,
308
309 #[arg(long, aliases = ["asdeps"])]
311 as_dependency: bool,
312
313 #[arg(long, aliases = ["asexpl"], conflicts_with = "as_dependency")]
315 as_explicit: bool
316 },
317
318 #[command(
320 alias = "up",
321 arg_required_else_help = true,
322 group(clap::ArgGroup::new("target").required(true).args(["package_names", "all"]))
323 )]
324 Update {
325 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
327 package_names: Vec<String>,
328
329 #[arg(long, conflicts_with = "package_names")]
331 all: bool,
332
333 #[arg(long)]
335 dry_run: bool,
336 #[arg(long)]
338 explain: bool,
339 #[arg(long, requires = "dry_run")]
341 plan_json: bool,
342 #[arg(long, short)]
344 verbose: bool,
345 #[arg(long, requires = "all")]
348 interactive: bool
349 },
350
351 #[command(aliases = ["i", "in", "add"])]
354 Install(cmd::install::args::InstallArgs),
355
356 #[command(alias = "u")]
358 Use {
359 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
361 packages: Vec<String>,
362
363 #[arg(short, long)]
365 global: bool
366 },
367
368 #[command(
370 aliases = ["un", "rm", "remove"],
371 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."
372 )]
373 Uninstall(cmd::uninstall::args::UninstallArgs),
374
375 #[command(long_about = "Execute a command from zoi.yaml. If no command \
377 is specified, it will launch an interactive \
378 prompt to choose one.")]
379 Run {
380 cmd_alias: Option<String>,
382 args: Vec<String>
384 },
385
386 #[command(long_about = "Checks for required packages and runs setup \
388 commands for a defined environment. If no \
389 environment is specified, it launches an \
390 interactive prompt.")]
391 Env {
392 env_alias: Option<String>,
394
395 #[arg(long, value_enum, hide = true)]
397 export_shell: Option<Shell>
398 },
399
400 #[command(
402 alias = "develop",
403 long_about = "Loads the project configuration from zoi.yaml, ensures \
404 all required packages are installed locally, sets up \
405 environment variables (PATH, LD_LIBRARY_PATH, etc.), \
406 and drops you into a subshell."
407 )]
408 Dev {
409 #[arg(short, long)]
411 run: Option<String>,
412 #[arg(long)]
414 repo: Option<String>
415 },
416
417 #[command(
419 alias = "ug",
420 long_about = "Upgrades Zoi to the latest version. By default, it \
421 attempts a delta upgrade (bsdiff) to minimize download \
422 size. If the delta upgrade is unavailable or fails, it \
423 automatically falls back to a full download."
424 )]
425 Upgrade {
426 #[arg(long)]
428 force: bool,
429
430 #[arg(long)]
432 tag: Option<String>,
433
434 #[arg(long)]
436 branch: Option<String>
437 },
438
439 Autoremove {
442 #[arg(long)]
444 dry_run: bool
445 },
446
447 Why {
449 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
451 package_name: String
452 },
453
454 #[command(alias = "owns")]
456 Owner {
457 #[arg(value_hint = ValueHint::FilePath)]
459 path: std::path::PathBuf
460 },
461
462 Files {
464 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
466 package: String
467 },
468
469 History {
471 #[arg(long, conflicts_with = "export")]
474 verify: bool,
475 #[arg(long, value_hint = ValueHint::FilePath, conflicts_with = "verify")]
478 export: Option<std::path::PathBuf>,
479 #[arg(long, requires = "export")]
481 ndjson: bool
482 },
483
484 #[command(
486 alias = "s",
487 long_about = "Searches for a case-insensitive term in the name, \
488 description, and tags of all available packages in the \
489 database. Filter by repo, type, or tags."
490 )]
491 Search {
492 search_term: String,
494 #[arg(long)]
496 registry: Option<String>,
497 #[arg(long)]
499 repo: Option<String>,
500 #[arg(long = "type")]
502 package_type: Option<String>,
503 #[arg(short = 't', long = "tag", value_delimiter = ',', num_args = 1..)]
505 tags: Option<Vec<String>>,
506 #[arg(long, default_value = "name")]
508 sort: String,
509 #[arg(short, long)]
511 files: bool,
512 #[arg(short = 'i', long)]
514 interactive: bool
515 },
516
517 #[command(alias = "svc")]
519 Service(cmd::service::ServiceCommand),
520
521 #[command(
524 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.",
525 arg_required_else_help = true,
526 group(clap::ArgGroup::new("shell_action").required(true).args(["shell", "hook", "packages"]).multiple(true))
527 )]
528 Shell {
529 #[arg(value_enum)]
531 shell: Option<Shell>,
532 #[arg(long)]
534 hook: bool,
535 #[arg(long, value_enum, default_value = "user")]
537 scope: SetupScope,
538 #[arg(short, long = "package", value_name = "ALL_PACKAGES")]
540 packages: Vec<String>,
541 #[arg(short, long)]
544 run: Option<String>,
545 #[arg(long, short)]
547 verbose: bool
548 },
549
550 #[command(
552 alias = "x",
553 long_about = "Resolves a package and its dependencies, installs them \
554 if needed, then runs the requested binary directly. By \
555 default runs the first binary the package provides. \
556 Uses bwrap for sandboxed packages."
557 )]
558 Exec {
559 #[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
561 source: String,
562
563 #[arg(long)]
566 bin: Option<String>,
567
568 #[arg(long, short)]
570 verbose: bool,
571
572 #[arg(value_name = "ARGS")]
574 args: Vec<String>
575 },
576
577 Clean {
579 #[arg(long)]
581 dry_run: bool
582 },
583
584 Clone {
586 #[arg(value_name = "ALL_PACKAGES", required = true, help = PKG_SOURCE_HELP)]
588 package: String,
589 #[arg(value_name = "LOCATION")]
591 location: Option<String>
592 },
593
594 Cache {
596 #[command(subcommand)]
598 command: CacheCommands
599 },
600
601 #[command(alias = "tx")]
603 Transaction {
604 #[command(subcommand)]
606 command: TransactionCommands
607 },
608
609 #[command(alias = "reg")]
611 Registry(cmd::registry::RegistryCommand),
612
613 Home(cmd::home::HomeCommand),
615
616 System(cmd::system::SystemCommand),
618
619 #[command(
621 aliases = ["repositories"],
622 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>)."
623 )]
624 Repo(cmd::repo::RepoCommand),
625
626 #[command(long_about = "Manage opt-in anonymous telemetry used to \
628 understand package popularity. Default is \
629 disabled.")]
630 Telemetry {
631 #[arg(value_enum)]
633 action: TelemetryAction
634 },
635
636 Create {
638 #[arg(value_name = "ALL_SOURCES", help = PKG_SOURCE_HELP)]
640 source: String,
641 app_name: Option<String>
643 },
644
645 #[command(
647 alias = "dg",
648 long_about = "Interactively choose and install an older version of a \
649 package from the local store or archive cache. This is \
650 useful if a recent update has introduced bugs or \
651 compatibility issues."
652 )]
653 Downgrade {
654 #[arg(value_name = "INST_PACKAGES", help = PKG_SOURCE_HELP)]
656 package: String
657 },
658
659 #[command(alias = "ext")]
661 Extension(ExtensionCommand),
662
663 Rollback {
665 #[arg(value_name = "INST_PACKAGES", required_unless_present = "last_transaction", help = PKG_SOURCE_HELP)]
667 package: Option<String>,
668
669 #[arg(long, conflicts_with = "package")]
671 last_transaction: bool
672 },
673
674 Man {
676 #[arg(value_name = "ALL_PACKAGES", help = PKG_SOURCE_HELP)]
678 package_name: String,
679 #[arg(long)]
681 upstream: bool,
682 #[arg(long)]
684 raw: bool,
685 #[arg(long)]
687 no_tui: bool
688 },
689
690 #[command(alias = "pkg")]
692 Package(cmd::package::PackageCommand),
693
694 Pgp(cmd::pgp::PgpCommand),
696
697 Helper(cmd::helper::HelperCommand),
699
700 Doctor,
702
703 Audit {
705 #[arg(short, long)]
708 all: bool,
709 #[arg(long)]
711 registry: Option<String>,
712 #[arg(long)]
714 repo: Option<String>
715 },
716
717 #[command(external_subcommand)]
719 External(Vec<String>)
720}
721
722#[derive(clap::Parser, Debug)]
724pub struct ExtensionCommand {
725 #[command(subcommand)]
727 pub command: ExtensionCommands
728}
729
730#[derive(clap::Subcommand, Debug)]
732pub enum ExtensionCommands {
733 Add {
735 #[arg(required = true)]
737 name: String
738 },
739 Remove {
741 #[arg(required = true)]
743 name: String
744 }
745}
746
747#[derive(clap::Subcommand, Clone)]
749pub enum SyncCommands {
750 Add {
752 url: String
754 },
755 Remove {
757 handle: String
759 },
760 #[command(alias = "ls")]
762 List,
763 Set {
765 url: String
767 }
768}
769
770#[derive(clap::Subcommand)]
772pub enum CacheCommands {
773 Add {
775 #[arg(required = true)]
777 files: Vec<std::path::PathBuf>
778 },
779 #[command(alias = "clean")]
781 Clear {
782 #[arg(long)]
784 dry_run: bool
785 },
786 #[command(alias = "ls")]
788 List,
789 Mirror {
791 #[command(subcommand)]
793 command: CacheMirrorCommands
794 }
795}
796
797#[derive(clap::Subcommand)]
799pub enum CacheMirrorCommands {
800 Add {
802 url: String
804 },
805 Remove {
807 url: String
809 },
810 #[command(alias = "ls")]
812 List
813}
814
815#[derive(clap::Subcommand)]
817pub enum TransactionCommands {
818 #[command(alias = "ls")]
820 List,
821 Show {
823 id: String
825 },
826 Files {
828 id: String
830 }
831}
832
833#[derive(clap::ValueEnum, Clone)]
835enum TelemetryAction {
836 Status,
838 Enable,
840 Disable
842}
843
844pub fn run() -> anyhow::Result<()> {
851 let styles = styling::Styles::styled()
852 .header(
853 styling::AnsiColor::Yellow.on_default() | styling::Effects::BOLD
854 )
855 .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD)
856 .literal(styling::AnsiColor::Green.on_default())
857 .placeholder(styling::AnsiColor::Cyan.on_default());
858
859 let commit: &str = option_env!("ZOI_COMMIT_HASH").unwrap_or("dev");
860 let cmd = Cli::command().styles(styles.clone());
861 let matches = cmd.clone().get_matches();
862 let cli = match Cli::from_arg_matches(&matches) {
863 Ok(cli) => cli,
864 Err(err) => {
865 err.print()?;
866 return Err(anyhow::anyhow!("Failed to parse arguments"));
867 }
868 };
869
870 if let Some(root) = cli.root {
871 crate::pkg::sysroot::set_sysroot(root);
872 }
873
874 let config = crate::pkg::config::read_config().unwrap_or_default();
875
876 let is_offline = cli.offline || config.offline_mode;
877 crate::pkg::offline::set_offline(is_offline);
878
879 let mut all_pkg_dirs = cli.pkg_dirs;
880 for dir in config.pkg_dirs {
881 let path = std::path::PathBuf::from(dir);
882 if !all_pkg_dirs.contains(&path) {
883 all_pkg_dirs.push(path);
884 }
885 }
886 crate::pkg::pkgdir::set_pkg_dirs(all_pkg_dirs);
887
888 utils::check_path();
889
890 if let Err(e) = crate::pkg::pgp::ensure_builtin_keys() {
891 eprintln!(
892 "{}: Failed to ensure builtin PGP keys: {}",
893 "Warning".yellow(),
894 e
895 );
896 }
897
898 let plugin_manager = crate::pkg::plugin::PluginManager::new()?;
899 if let Err(e) = plugin_manager.load_all(cli.yes) {
900 eprintln!("{}: Failed to load plugins: {}", "Warning".yellow(), e);
901 }
902
903 if cli.version_flag {
904 cmd::version::run(BRANCH, STATUS, NUMBER, commit);
905 return Ok(());
906 }
907
908 if let Some(command) = cli.command {
909 let needs_lock = matches!(
910 command,
911 Commands::Install { .. }
912 | Commands::Uninstall { .. }
913 | Commands::Update { .. }
914 | Commands::Autoremove { .. }
915 | Commands::Rollback { .. }
916 | Commands::Package(_)
917 );
918
919 let _lock_guard = if needs_lock {
920 Some(lock::acquire_lock()?)
921 } else {
922 None
923 };
924
925 let result = match command {
926 Commands::GenerateCompletions { shell } => {
927 let mut cmd = Cli::command();
928 let bin_name = cmd.get_name().to_string();
929 generate(shell, &mut cmd, bin_name, &mut io::stdout());
930 Ok(())
931 }
932 Commands::Complete {
933 shell,
934 index,
935 words
936 } => cmd::complete::run(shell, index, &words),
937 Commands::GenerateManual => cmd::gen_man::run().map_err(Into::into),
938 Commands::Version => {
939 cmd::version::run(BRANCH, STATUS, NUMBER, commit);
940 Ok(())
941 }
942 Commands::About => {
943 cmd::about::run(BRANCH, STATUS, NUMBER, commit);
944 Ok(())
945 }
946 Commands::Info => cmd::info::run(BRANCH, STATUS, NUMBER, commit),
947 Commands::Sync {
948 command,
949 verbose,
950 fallback,
951 no_package_managers,
952 force,
953 local,
954 frozen,
955 scope
956 } => {
957 if let Some(cmd) = command {
958 match cmd {
959 SyncCommands::Add { url } => {
960 cmd::sync::add_registry(&url)
961 }
962 SyncCommands::Remove { handle } => {
963 cmd::sync::remove_registry(&handle)
964 }
965 SyncCommands::List => cmd::sync::list_registries(),
966 SyncCommands::Set { url } => {
967 cmd::sync::set_registry(&url)
968 }
969 }
970 } else if local {
971 plugin_manager.trigger_hook("on_pre_sync", None)?;
972 let res =
973 cmd::sync::run_local(verbose, fallback, force, frozen);
974 plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
975 res
976 } else {
977 plugin_manager.trigger_hook("on_pre_sync", None)?;
978 let res = cmd::sync::run(
979 verbose,
980 fallback,
981 no_package_managers,
982 force,
983 scope
984 );
985 plugin_manager.trigger_hook_nonfatal("on_post_sync", None);
986 res
987 }
988 }
989 Commands::Migrate(args) => cmd::migrate::run(args),
990 Commands::List {
991 all,
992 outdated,
993 registry,
994 repo,
995 package_type,
996 foreign,
997 names,
998 completion
999 } => cmd::list::run(
1000 all,
1001 outdated,
1002 registry.as_deref(),
1003 repo.as_deref(),
1004 package_type.as_deref(),
1005 foreign,
1006 names,
1007 completion
1008 ),
1009 Commands::Show {
1010 package_name,
1011 raw,
1012 purl
1013 } => cmd::show::run(&package_name, raw, purl),
1014 Commands::Pin { package, version } => {
1015 cmd::pin::run(&package, &version)
1016 }
1017 Commands::Provides { term } => cmd::provides::run(&term),
1018 Commands::Tree { packages } => cmd::tree::run(&packages),
1019 Commands::Unpin { package } => cmd::unpin::run(&package),
1020 Commands::Mark {
1021 packages,
1022 as_dependency,
1023 as_explicit
1024 } => cmd::mark::run(&packages, as_dependency, as_explicit),
1025 Commands::Update {
1026 package_names,
1027 all,
1028 dry_run,
1029 explain,
1030 plan_json,
1031 verbose,
1032 interactive
1033 } => cmd::update::run(
1034 all,
1035 &package_names,
1036 cli.yes,
1037 dry_run,
1038 explain,
1039 plan_json,
1040 verbose,
1041 interactive
1042 )
1043 .map_err(|e| cmd::ux::with_failure_hint("update", e)),
1044 Commands::Install(args) => args
1045 .run(cli.yes)
1046 .map_err(|e| cmd::ux::with_failure_hint("install", e)),
1047 Commands::Use { packages, global } => {
1048 cmd::use_cmd::run(&packages, global)
1049 }
1050 Commands::Uninstall(args) => args
1051 .run(cli.yes)
1052 .map_err(|e| cmd::ux::with_failure_hint("uninstall", e)),
1053 Commands::Run { cmd_alias, args } => {
1054 cmd::run::run(cmd_alias.as_deref(), &args)
1055 }
1056 Commands::Env {
1057 env_alias,
1058 export_shell
1059 } => cmd::env::run(env_alias.as_deref(), export_shell),
1060 Commands::Dev { run, repo } => cmd::dev::run(run, repo),
1061 Commands::Upgrade { force, tag, branch } => {
1062 match cmd::upgrade::run(
1063 BRANCH, STATUS, NUMBER, force, tag, branch
1064 ) {
1065 Ok(()) => {
1066 println!(
1067 "\n{}",
1068 "Zoi upgraded successfully! Please restart your \
1069 shell for changes to take effect."
1070 .green()
1071 );
1072 println!(
1073 "\n{}: https://github.com/zillowe/zoi/blob/main/CHANGELOG.md",
1074 "Changelog".cyan().bold()
1075 );
1076 println!(
1077 "\n{}: To update shell completions, run 'zoi \
1078 shell <your-shell>'.",
1079 "Hint".cyan().bold()
1080 );
1081 }
1082 Err(e) if e.to_string() == "already_on_latest" => {}
1083 Err(e) if e.to_string() == "managed_by_package_manager" => {
1084 }
1085 Err(e) => return Err(e)
1086 }
1087 Ok(())
1088 }
1089 Commands::Autoremove { dry_run } => {
1090 cmd::autoremove::run(cli.yes, dry_run)
1091 }
1092 Commands::Why { package_name } => cmd::why::run(&package_name),
1093 Commands::Owner { path } => cmd::owner::run(&path),
1094 Commands::Files { package } => cmd::files::run(&package),
1095 Commands::History {
1096 verify,
1097 export,
1098 ndjson
1099 } => cmd::history::run(verify, export, ndjson),
1100 Commands::Search {
1101 search_term,
1102 registry,
1103 repo,
1104 package_type,
1105 tags,
1106 sort,
1107 files,
1108 interactive
1109 } => cmd::search::run(
1110 &search_term,
1111 registry.as_deref(),
1112 repo.as_deref(),
1113 package_type.as_deref(),
1114 tags,
1115 &sort,
1116 files,
1117 interactive
1118 ),
1119 Commands::Service(args) => cmd::service::run(args),
1120 Commands::Shell {
1121 shell,
1122 hook,
1123 scope,
1124 packages,
1125 run,
1126 verbose
1127 } => {
1128 let target_shell = shell
1129 .or_else(crate::pkg::utils::get_current_shell)
1130 .unwrap_or(Shell::Bash);
1131 if hook {
1132 cmd::shell::print_hook(target_shell)
1133 } else if !packages.is_empty() {
1134 cmd::shell::enter_ephemeral_shell(
1135 &packages,
1136 run,
1137 verbose,
1138 Some(&plugin_manager)
1139 )
1140 } else {
1141 cmd::shell::run(target_shell, scope)
1142 }
1143 }
1144 Commands::Exec {
1145 source,
1146 bin,
1147 verbose,
1148 args
1149 } => cmd::exec::run(&source, bin, &args, verbose),
1150 Commands::Download {
1151 package,
1152 archive: _,
1153 source,
1154 output_dir
1155 } => {
1156 let download_type = if source {
1157 cmd::download::DownloadType::Source
1158 } else {
1159 cmd::download::DownloadType::Archive
1160 };
1161 cmd::download::run(&package, download_type, output_dir)
1162 }
1163 Commands::Clean { dry_run } => cmd::clean::run(dry_run),
1164 Commands::Clone { package, location } => {
1165 cmd::clone::run(&package, location, cli.yes)
1166 }
1167 Commands::Cache { command } => match command {
1168 CacheCommands::Add { files } => cmd::cache::add(&files),
1169 CacheCommands::Clear { dry_run } => cmd::cache::clear(dry_run),
1170 CacheCommands::List => cmd::cache::list(),
1171 CacheCommands::Mirror { command } => match command {
1172 CacheMirrorCommands::Add { url } => {
1173 cmd::cache::add_mirror(&url)
1174 }
1175 CacheMirrorCommands::Remove { url } => {
1176 cmd::cache::remove_mirror(&url)
1177 }
1178 CacheMirrorCommands::List => cmd::cache::list_mirrors()
1179 }
1180 },
1181 Commands::Transaction { command } => match command {
1182 TransactionCommands::List => cmd::transaction::list(),
1183 TransactionCommands::Show { id } => cmd::transaction::show(&id),
1184 TransactionCommands::Files { id } => {
1185 cmd::transaction::files(&id)
1186 }
1187 },
1188 Commands::Repo(args) => cmd::repo::run(args),
1189 Commands::Registry(args) => cmd::registry::run(args),
1190 Commands::Home(args) => cmd::home::run(args),
1191 Commands::System(args) => cmd::system::run(args, cli.yes),
1192 Commands::Telemetry { action } => {
1193 use cmd::telemetry::{TelemetryCommand, run};
1194 let cmd = match action {
1195 TelemetryAction::Status => TelemetryCommand::Status,
1196 TelemetryAction::Enable => TelemetryCommand::Enable,
1197 TelemetryAction::Disable => TelemetryCommand::Disable
1198 };
1199 run(cmd)
1200 }
1201 Commands::Create { source, app_name } => cmd::create::run(
1202 cmd::create::CreateCommand { source, app_name },
1203 cli.yes,
1204 Some(&plugin_manager)
1205 ),
1206 Commands::Downgrade { package } => {
1207 cmd::downgrade::run(&package, cli.yes, Some(&plugin_manager))
1208 }
1209 Commands::Extension(args) => {
1210 cmd::extension::run(args, cli.yes, Some(&plugin_manager))
1211 }
1212 Commands::Rollback {
1213 package,
1214 last_transaction
1215 } => {
1216 if last_transaction {
1217 cmd::rollback::run_transaction_rollback(
1218 cli.yes,
1219 Some(&plugin_manager)
1220 )
1221 } else if let Some(pkg) = package {
1222 cmd::rollback::run(&pkg, cli.yes, Some(&plugin_manager))
1223 } else {
1224 Ok(())
1225 }
1226 }
1227 Commands::Man {
1228 package_name,
1229 upstream,
1230 raw,
1231 no_tui
1232 } => cmd::man::run(&package_name, upstream, raw, no_tui),
1233 Commands::Package(args) => cmd::package::run(args),
1234 Commands::Pgp(args) => cmd::pgp::run(args),
1235 Commands::Helper(args) => cmd::helper::run(args),
1236 Commands::Doctor => cmd::doctor::run(),
1237 Commands::Audit {
1238 all,
1239 registry,
1240 repo
1241 } => cmd::audit::run(all, registry, repo.as_deref()),
1242 Commands::External(args) => {
1243 let (cmd_name, cmd_args) =
1244 if let Some((first, rest)) = args.split_first() {
1245 (first, rest.to_vec())
1246 } else {
1247 return Err(anyhow::anyhow!("No command specified"));
1248 };
1249
1250 match plugin_manager.run_command(cmd_name, cmd_args) {
1251 Ok(true) => Ok(()),
1252 Ok(false) => {
1253 let mut shadow_cmd = Cli::command().styles(styles);
1254 shadow_cmd =
1255 shadow_cmd.allow_external_subcommands(false);
1256
1257 let err = shadow_cmd
1258 .clone()
1259 .try_get_matches_from(std::env::args())
1260 .err()
1261 .unwrap_or_else(|| {
1262 shadow_cmd.error(
1263 clap::error::ErrorKind::InvalidSubcommand,
1264 format!(
1265 "unrecognized subcommand '{cmd_name}'"
1266 )
1267 )
1268 });
1269
1270 let plugin_cmds = plugin_manager.list_commands()?;
1271 if !plugin_cmds.is_empty() {
1272 eprintln!(
1273 "{}:",
1274 "Available Plugin Commands".cyan().bold()
1275 );
1276 for (pcmd, pdesc) in plugin_cmds {
1277 if pdesc.is_empty() {
1278 eprintln!(" {pcmd}");
1279 } else {
1280 eprintln!(
1281 " {:<12} {}",
1282 pcmd,
1283 pdesc.dimmed()
1284 );
1285 }
1286 }
1287 eprintln!();
1288 }
1289
1290 err.exit();
1291 }
1292 Err(e) => Err(e)
1293 }
1294 }
1295 };
1296
1297 if let Err(e) = result {
1298 eprintln!("Error: {e}");
1299 std::process::exit(1);
1300 }
1301 }
1302 Ok(())
1303}