1use clap::{Parser, Subcommand};
2
3#[derive(Parser, Debug)]
6#[command(name = "ubt", version, about = "Universal Build Tool")]
7pub struct Cli {
8 #[arg(short, long, global = true)]
10 pub verbose: bool,
11
12 #[arg(short, long, global = true)]
14 pub quiet: bool,
15
16 #[arg(long, global = true, env = "UBT_TOOL")]
18 pub tool: Option<String>,
19
20 #[command(subcommand)]
21 pub command: Command,
22}
23
24#[derive(Subcommand, Debug)]
27pub enum Command {
28 #[command(subcommand)]
30 Dep(DepCommand),
31
32 Build(BuildArgs),
34
35 Start(PassthroughArgs),
37
38 Run(RunArgs),
40
41 Fmt(FmtArgs),
43
44 #[command(name = "run-file", alias = "run:file")]
46 RunFile(RunFileArgs),
47
48 Exec(ExecArgs),
50
51 Test(TestArgs),
53
54 Lint(LintArgs),
56
57 Check(PassthroughArgs),
59
60 #[command(subcommand)]
62 Db(DbCommand),
63
64 Init,
66
67 Clean(PassthroughArgs),
69
70 Release(ReleaseArgs),
72
73 Publish(PublishArgs),
75
76 #[command(subcommand)]
78 Tool(ToolCommand),
79
80 #[command(subcommand)]
82 Config(ConfigCommand),
83
84 Info,
86
87 Completions(CompletionsArgs),
89
90 #[command(external_subcommand)]
92 External(Vec<String>),
93}
94
95#[derive(Parser, Debug)]
98#[command(trailing_var_arg = true, allow_hyphen_values = true)]
99pub struct PassthroughArgs {
100 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
101 pub args: Vec<String>,
102}
103
104#[derive(Subcommand, Debug)]
107pub enum DepCommand {
108 Install(PassthroughArgs),
110
111 Remove(PassthroughArgs),
113
114 Update(PassthroughArgs),
116
117 Outdated(PassthroughArgs),
119
120 List(PassthroughArgs),
122
123 Audit(PassthroughArgs),
125
126 Lock(PassthroughArgs),
128
129 Why(PassthroughArgs),
131}
132
133#[derive(Parser, Debug)]
136#[command(trailing_var_arg = true, allow_hyphen_values = true)]
137pub struct BuildArgs {
138 #[arg(long)]
140 pub dev: bool,
141
142 #[arg(long)]
144 pub watch: bool,
145
146 #[arg(long)]
148 pub clean: bool,
149
150 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
151 pub args: Vec<String>,
152}
153
154#[derive(Parser, Debug)]
157#[command(trailing_var_arg = true, allow_hyphen_values = true)]
158pub struct RunArgs {
159 pub script: String,
161
162 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
163 pub args: Vec<String>,
164}
165
166#[derive(Parser, Debug)]
169#[command(trailing_var_arg = true, allow_hyphen_values = true)]
170pub struct FmtArgs {
171 #[arg(long)]
173 pub check: bool,
174
175 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
176 pub args: Vec<String>,
177}
178
179#[derive(Parser, Debug)]
182#[command(trailing_var_arg = true, allow_hyphen_values = true)]
183pub struct RunFileArgs {
184 pub file: String,
186
187 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
188 pub args: Vec<String>,
189}
190
191#[derive(Parser, Debug)]
194#[command(trailing_var_arg = true, allow_hyphen_values = true)]
195pub struct ExecArgs {
196 pub cmd: String,
198
199 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
200 pub args: Vec<String>,
201}
202
203#[derive(Parser, Debug)]
206#[command(trailing_var_arg = true, allow_hyphen_values = true)]
207pub struct TestArgs {
208 #[arg(long)]
210 pub watch: bool,
211
212 #[arg(long)]
214 pub coverage: bool,
215
216 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
217 pub args: Vec<String>,
218}
219
220#[derive(Parser, Debug)]
223#[command(trailing_var_arg = true, allow_hyphen_values = true)]
224pub struct LintArgs {
225 #[arg(long)]
227 pub fix: bool,
228
229 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
230 pub args: Vec<String>,
231}
232
233#[derive(Subcommand, Debug)]
236pub enum DbCommand {
237 Migrate(PassthroughArgs),
239
240 Rollback(PassthroughArgs),
242
243 Seed(PassthroughArgs),
245
246 Create(PassthroughArgs),
248
249 Drop(DbDropArgs),
251
252 Reset(DbResetArgs),
254
255 Status(PassthroughArgs),
257}
258
259#[derive(Parser, Debug)]
260#[command(trailing_var_arg = true, allow_hyphen_values = true)]
261pub struct DbDropArgs {
262 #[arg(short, long)]
264 pub yes: bool,
265
266 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
267 pub args: Vec<String>,
268}
269
270#[derive(Parser, Debug)]
271#[command(trailing_var_arg = true, allow_hyphen_values = true)]
272pub struct DbResetArgs {
273 #[arg(short, long)]
275 pub yes: bool,
276
277 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
278 pub args: Vec<String>,
279}
280
281#[derive(Parser, Debug)]
284#[command(trailing_var_arg = true, allow_hyphen_values = true)]
285pub struct ReleaseArgs {
286 #[arg(long)]
288 pub dry_run: bool,
289
290 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
291 pub args: Vec<String>,
292}
293
294#[derive(Parser, Debug)]
297#[command(trailing_var_arg = true, allow_hyphen_values = true)]
298pub struct PublishArgs {
299 #[arg(short, long)]
301 pub yes: bool,
302
303 #[arg(long)]
305 pub dry_run: bool,
306
307 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
308 pub args: Vec<String>,
309}
310
311#[derive(Subcommand, Debug)]
314pub enum ToolCommand {
315 Info,
317
318 Doctor,
320
321 List,
323
324 Docs(DocsArgs),
326}
327
328#[derive(Parser, Debug)]
329pub struct DocsArgs {
330 #[arg(long)]
332 pub open: bool,
333}
334
335#[derive(Subcommand, Debug)]
338pub enum ConfigCommand {
339 Show,
341}
342
343#[derive(Parser, Debug)]
346pub struct CompletionsArgs {
347 pub shell: clap_complete::Shell,
349}
350
351#[derive(Debug, Default, PartialEq)]
354pub struct UniversalFlags {
355 pub watch: bool,
356 pub coverage: bool,
357 pub dev: bool,
358 pub clean: bool,
359 pub fix: bool,
360 pub check: bool,
361 pub yes: bool,
362 pub dry_run: bool,
363}
364
365pub fn command_parts(cmd: &Command) -> (&'static str, Option<&Vec<String>>) {
370 match cmd {
371 Command::Dep(sub) => match sub {
372 DepCommand::Install(a) => ("dep.install", Some(&a.args)),
373 DepCommand::Remove(a) => ("dep.remove", Some(&a.args)),
374 DepCommand::Update(a) => ("dep.update", Some(&a.args)),
375 DepCommand::Outdated(a) => ("dep.outdated", Some(&a.args)),
376 DepCommand::List(a) => ("dep.list", Some(&a.args)),
377 DepCommand::Audit(a) => ("dep.audit", Some(&a.args)),
378 DepCommand::Lock(a) => ("dep.lock", Some(&a.args)),
379 DepCommand::Why(a) => ("dep.why", Some(&a.args)),
380 },
381 Command::Build(a) => ("build", Some(&a.args)),
382 Command::Start(a) => ("start", Some(&a.args)),
383 Command::Run(a) => ("run", Some(&a.args)),
384 Command::Fmt(a) => ("fmt", Some(&a.args)),
385 Command::RunFile(a) => ("run-file", Some(&a.args)),
386 Command::Exec(a) => ("exec", Some(&a.args)),
387 Command::Test(a) => ("test", Some(&a.args)),
388 Command::Lint(a) => ("lint", Some(&a.args)),
389 Command::Check(a) => ("check", Some(&a.args)),
390 Command::Db(sub) => match sub {
391 DbCommand::Migrate(a) => ("db.migrate", Some(&a.args)),
392 DbCommand::Rollback(a) => ("db.rollback", Some(&a.args)),
393 DbCommand::Seed(a) => ("db.seed", Some(&a.args)),
394 DbCommand::Create(a) => ("db.create", Some(&a.args)),
395 DbCommand::Drop(a) => ("db.drop", Some(&a.args)),
396 DbCommand::Reset(a) => ("db.reset", Some(&a.args)),
397 DbCommand::Status(a) => ("db.status", Some(&a.args)),
398 },
399 Command::Init => ("init", None),
400 Command::Clean(a) => ("clean", Some(&a.args)),
401 Command::Release(a) => ("release", Some(&a.args)),
402 Command::Publish(a) => ("publish", Some(&a.args)),
403 Command::Tool(sub) => match sub {
404 ToolCommand::Info => ("tool.info", None),
405 ToolCommand::Doctor => ("tool.doctor", None),
406 ToolCommand::List => ("tool.list", None),
407 ToolCommand::Docs(_) => ("tool.docs", None),
408 },
409 Command::Config(sub) => match sub {
410 ConfigCommand::Show => ("config.show", None),
411 },
412 Command::Info => ("info", None),
413 Command::Completions(..) => ("completions", None),
414 Command::External(..) => unreachable!("External is dispatched before command_parts"),
415 }
416}
417
418pub fn parse_command_name(cmd: &Command) -> &'static str {
420 command_parts(cmd).0
421}
422
423pub fn collect_universal_flags(cmd: &Command) -> UniversalFlags {
425 match cmd {
426 Command::Build(args) => UniversalFlags {
427 dev: args.dev,
428 watch: args.watch,
429 clean: args.clean,
430 ..Default::default()
431 },
432 Command::Test(args) => UniversalFlags {
433 watch: args.watch,
434 coverage: args.coverage,
435 ..Default::default()
436 },
437 Command::Lint(args) => UniversalFlags {
438 fix: args.fix,
439 ..Default::default()
440 },
441 Command::Fmt(args) => UniversalFlags {
442 check: args.check,
443 ..Default::default()
444 },
445 Command::Db(DbCommand::Drop(args)) => UniversalFlags {
446 yes: args.yes,
447 ..Default::default()
448 },
449 Command::Db(DbCommand::Reset(args)) => UniversalFlags {
450 yes: args.yes,
451 ..Default::default()
452 },
453 Command::Release(args) => UniversalFlags {
454 dry_run: args.dry_run,
455 ..Default::default()
456 },
457 Command::Publish(args) => UniversalFlags {
458 yes: args.yes,
459 dry_run: args.dry_run,
460 ..Default::default()
461 },
462 Command::External(..) => {
463 unreachable!("External is dispatched before collect_universal_flags")
464 }
465 _ => UniversalFlags::default(),
466 }
467}
468
469pub fn collect_remaining_args(cmd: &Command) -> Vec<String> {
471 command_parts(cmd).1.cloned().unwrap_or_default()
472}
473
474#[cfg(test)]
477mod tests {
478 use super::*;
479 use clap::Parser;
480
481 fn parse(args: &[&str]) -> Cli {
483 Cli::parse_from(args)
484 }
485
486 #[test]
489 fn command_name_dep_install() {
490 let cli = parse(&["ubt", "dep", "install"]);
491 assert_eq!(parse_command_name(&cli.command), "dep.install");
492 }
493
494 #[test]
495 fn command_name_dep_remove() {
496 let cli = parse(&["ubt", "dep", "remove"]);
497 assert_eq!(parse_command_name(&cli.command), "dep.remove");
498 }
499
500 #[test]
501 fn command_name_dep_update() {
502 let cli = parse(&["ubt", "dep", "update"]);
503 assert_eq!(parse_command_name(&cli.command), "dep.update");
504 }
505
506 #[test]
507 fn command_name_dep_outdated() {
508 let cli = parse(&["ubt", "dep", "outdated"]);
509 assert_eq!(parse_command_name(&cli.command), "dep.outdated");
510 }
511
512 #[test]
513 fn command_name_dep_list() {
514 let cli = parse(&["ubt", "dep", "list"]);
515 assert_eq!(parse_command_name(&cli.command), "dep.list");
516 }
517
518 #[test]
519 fn command_name_dep_audit() {
520 let cli = parse(&["ubt", "dep", "audit"]);
521 assert_eq!(parse_command_name(&cli.command), "dep.audit");
522 }
523
524 #[test]
525 fn command_name_dep_lock() {
526 let cli = parse(&["ubt", "dep", "lock"]);
527 assert_eq!(parse_command_name(&cli.command), "dep.lock");
528 }
529
530 #[test]
531 fn command_name_dep_why() {
532 let cli = parse(&["ubt", "dep", "why"]);
533 assert_eq!(parse_command_name(&cli.command), "dep.why");
534 }
535
536 #[test]
537 fn command_name_build() {
538 let cli = parse(&["ubt", "build"]);
539 assert_eq!(parse_command_name(&cli.command), "build");
540 }
541
542 #[test]
543 fn command_name_start() {
544 let cli = parse(&["ubt", "start"]);
545 assert_eq!(parse_command_name(&cli.command), "start");
546 }
547
548 #[test]
549 fn command_name_run() {
550 let cli = parse(&["ubt", "run", "dev"]);
551 assert_eq!(parse_command_name(&cli.command), "run");
552 }
553
554 #[test]
555 fn command_name_fmt() {
556 let cli = parse(&["ubt", "fmt"]);
557 assert_eq!(parse_command_name(&cli.command), "fmt");
558 }
559
560 #[test]
561 fn command_name_run_file() {
562 let cli = parse(&["ubt", "run-file", "script.ts"]);
563 assert_eq!(parse_command_name(&cli.command), "run-file");
564 }
565
566 #[test]
567 fn command_name_exec() {
568 let cli = parse(&["ubt", "exec", "node"]);
569 assert_eq!(parse_command_name(&cli.command), "exec");
570 }
571
572 #[test]
573 fn command_name_test() {
574 let cli = parse(&["ubt", "test"]);
575 assert_eq!(parse_command_name(&cli.command), "test");
576 }
577
578 #[test]
579 fn command_name_lint() {
580 let cli = parse(&["ubt", "lint"]);
581 assert_eq!(parse_command_name(&cli.command), "lint");
582 }
583
584 #[test]
585 fn command_name_check() {
586 let cli = parse(&["ubt", "check"]);
587 assert_eq!(parse_command_name(&cli.command), "check");
588 }
589
590 #[test]
591 fn command_name_db_migrate() {
592 let cli = parse(&["ubt", "db", "migrate"]);
593 assert_eq!(parse_command_name(&cli.command), "db.migrate");
594 }
595
596 #[test]
597 fn command_name_db_rollback() {
598 let cli = parse(&["ubt", "db", "rollback"]);
599 assert_eq!(parse_command_name(&cli.command), "db.rollback");
600 }
601
602 #[test]
603 fn command_name_db_seed() {
604 let cli = parse(&["ubt", "db", "seed"]);
605 assert_eq!(parse_command_name(&cli.command), "db.seed");
606 }
607
608 #[test]
609 fn command_name_db_create() {
610 let cli = parse(&["ubt", "db", "create"]);
611 assert_eq!(parse_command_name(&cli.command), "db.create");
612 }
613
614 #[test]
615 fn command_name_db_drop() {
616 let cli = parse(&["ubt", "db", "drop"]);
617 assert_eq!(parse_command_name(&cli.command), "db.drop");
618 }
619
620 #[test]
621 fn command_name_db_reset() {
622 let cli = parse(&["ubt", "db", "reset"]);
623 assert_eq!(parse_command_name(&cli.command), "db.reset");
624 }
625
626 #[test]
627 fn command_name_db_status() {
628 let cli = parse(&["ubt", "db", "status"]);
629 assert_eq!(parse_command_name(&cli.command), "db.status");
630 }
631
632 #[test]
633 fn command_name_init() {
634 let cli = parse(&["ubt", "init"]);
635 assert_eq!(parse_command_name(&cli.command), "init");
636 }
637
638 #[test]
639 fn command_name_clean() {
640 let cli = parse(&["ubt", "clean"]);
641 assert_eq!(parse_command_name(&cli.command), "clean");
642 }
643
644 #[test]
645 fn command_name_release() {
646 let cli = parse(&["ubt", "release"]);
647 assert_eq!(parse_command_name(&cli.command), "release");
648 }
649
650 #[test]
651 fn command_name_publish() {
652 let cli = parse(&["ubt", "publish"]);
653 assert_eq!(parse_command_name(&cli.command), "publish");
654 }
655
656 #[test]
657 fn command_name_tool_info() {
658 let cli = parse(&["ubt", "tool", "info"]);
659 assert_eq!(parse_command_name(&cli.command), "tool.info");
660 }
661
662 #[test]
663 fn command_name_tool_doctor() {
664 let cli = parse(&["ubt", "tool", "doctor"]);
665 assert_eq!(parse_command_name(&cli.command), "tool.doctor");
666 }
667
668 #[test]
669 fn command_name_tool_list() {
670 let cli = parse(&["ubt", "tool", "list"]);
671 assert_eq!(parse_command_name(&cli.command), "tool.list");
672 }
673
674 #[test]
675 fn command_name_tool_docs() {
676 let cli = parse(&["ubt", "tool", "docs"]);
677 assert_eq!(parse_command_name(&cli.command), "tool.docs");
678 }
679
680 #[test]
681 fn command_name_config_show() {
682 let cli = parse(&["ubt", "config", "show"]);
683 assert_eq!(parse_command_name(&cli.command), "config.show");
684 }
685
686 #[test]
687 fn command_name_info() {
688 let cli = parse(&["ubt", "info"]);
689 assert_eq!(parse_command_name(&cli.command), "info");
690 }
691
692 #[test]
693 fn command_name_completions() {
694 let cli = parse(&["ubt", "completions", "bash"]);
695 assert_eq!(parse_command_name(&cli.command), "completions");
696 }
697
698 #[test]
701 fn global_verbose_flag() {
702 let cli = parse(&["ubt", "-v", "info"]);
703 assert!(cli.verbose);
704 assert!(!cli.quiet);
705 }
706
707 #[test]
708 fn global_quiet_flag() {
709 let cli = parse(&["ubt", "-q", "info"]);
710 assert!(!cli.verbose);
711 assert!(cli.quiet);
712 }
713
714 #[test]
715 fn global_tool_flag() {
716 let cli = parse(&["ubt", "--tool", "npm", "info"]);
717 assert_eq!(cli.tool, Some("npm".to_string()));
718 }
719
720 #[test]
721 fn global_tool_flag_absent() {
722 let cli = parse(&["ubt", "info"]);
723 assert_eq!(cli.tool, None);
724 }
725
726 #[test]
729 fn universal_flags_build_dev() {
730 let cli = parse(&["ubt", "build", "--dev"]);
731 let flags = collect_universal_flags(&cli.command);
732 assert!(flags.dev);
733 assert!(!flags.watch);
734 assert!(!flags.clean);
735 }
736
737 #[test]
738 fn universal_flags_build_watch_clean() {
739 let cli = parse(&["ubt", "build", "--watch", "--clean"]);
740 let flags = collect_universal_flags(&cli.command);
741 assert!(flags.watch);
742 assert!(flags.clean);
743 }
744
745 #[test]
746 fn universal_flags_test_coverage_watch() {
747 let cli = parse(&["ubt", "test", "--coverage", "--watch"]);
748 let flags = collect_universal_flags(&cli.command);
749 assert!(flags.coverage);
750 assert!(flags.watch);
751 }
752
753 #[test]
754 fn universal_flags_lint_fix() {
755 let cli = parse(&["ubt", "lint", "--fix"]);
756 let flags = collect_universal_flags(&cli.command);
757 assert!(flags.fix);
758 }
759
760 #[test]
761 fn universal_flags_fmt_check() {
762 let cli = parse(&["ubt", "fmt", "--check"]);
763 let flags = collect_universal_flags(&cli.command);
764 assert!(flags.check);
765 }
766
767 #[test]
768 fn universal_flags_db_drop_yes() {
769 let cli = parse(&["ubt", "db", "drop", "--yes"]);
770 let flags = collect_universal_flags(&cli.command);
771 assert!(flags.yes);
772 }
773
774 #[test]
775 fn universal_flags_db_reset_yes() {
776 let cli = parse(&["ubt", "db", "reset", "-y"]);
777 let flags = collect_universal_flags(&cli.command);
778 assert!(flags.yes);
779 }
780
781 #[test]
782 fn universal_flags_publish_dry_run() {
783 let cli = parse(&["ubt", "publish", "--dry-run"]);
784 let flags = collect_universal_flags(&cli.command);
785 assert!(flags.dry_run);
786 assert!(!flags.yes);
787 }
788
789 #[test]
790 fn universal_flags_publish_yes_and_dry_run() {
791 let cli = parse(&["ubt", "publish", "--yes", "--dry-run"]);
792 let flags = collect_universal_flags(&cli.command);
793 assert!(flags.yes);
794 assert!(flags.dry_run);
795 }
796
797 #[test]
798 fn universal_flags_release_dry_run() {
799 let cli = parse(&["ubt", "release", "--dry-run"]);
800 let flags = collect_universal_flags(&cli.command);
801 assert!(flags.dry_run);
802 }
803
804 #[test]
805 fn universal_flags_default_for_info() {
806 let cli = parse(&["ubt", "info"]);
807 let flags = collect_universal_flags(&cli.command);
808 assert_eq!(flags, UniversalFlags::default());
809 }
810
811 #[test]
814 fn remaining_args_build() {
815 let cli = parse(&["ubt", "build", "--dev", "--", "extra1", "extra2"]);
816 let args = collect_remaining_args(&cli.command);
817 assert!(args.contains(&"extra1".to_string()));
818 assert!(args.contains(&"extra2".to_string()));
819 }
820
821 #[test]
822 fn remaining_args_start() {
823 let cli = parse(&["ubt", "start", "foo", "bar"]);
824 let args = collect_remaining_args(&cli.command);
825 assert_eq!(args, vec!["foo", "bar"]);
826 }
827
828 #[test]
829 fn remaining_args_test_with_flags() {
830 let cli = parse(&["ubt", "test", "--watch", "some-pattern"]);
831 let args = collect_remaining_args(&cli.command);
832 assert_eq!(args, vec!["some-pattern"]);
833 }
834
835 #[test]
836 fn remaining_args_dep_install() {
837 let cli = parse(&["ubt", "dep", "install", "lodash", "express"]);
838 let args = collect_remaining_args(&cli.command);
839 assert_eq!(args, vec!["lodash", "express"]);
840 }
841
842 #[test]
843 fn remaining_args_run() {
844 let cli = parse(&["ubt", "run", "dev", "--port", "3000"]);
845 let args = collect_remaining_args(&cli.command);
846 assert_eq!(args, vec!["--port", "3000"]);
847 }
848
849 #[test]
850 fn remaining_args_empty_for_init() {
851 let cli = parse(&["ubt", "init"]);
852 let args = collect_remaining_args(&cli.command);
853 assert!(args.is_empty());
854 }
855
856 #[test]
857 fn remaining_args_empty_for_info() {
858 let cli = parse(&["ubt", "info"]);
859 let args = collect_remaining_args(&cli.command);
860 assert!(args.is_empty());
861 }
862
863 #[test]
864 fn remaining_args_empty_for_tool() {
865 let cli = parse(&["ubt", "tool", "info"]);
866 let args = collect_remaining_args(&cli.command);
867 assert!(args.is_empty());
868 }
869
870 #[test]
871 fn remaining_args_exec() {
872 let cli = parse(&["ubt", "exec", "node", "-e", "console.log(1)"]);
873 let args = collect_remaining_args(&cli.command);
874 assert_eq!(args, vec!["-e", "console.log(1)"]);
875 }
876
877 #[test]
880 fn help_output_produces_error() {
881 let result = Cli::try_parse_from(["ubt", "--help"]);
882 assert!(result.is_err());
883 let err = result.unwrap_err();
884 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
885 }
886
887 #[test]
888 fn version_output_produces_error() {
889 let result = Cli::try_parse_from(["ubt", "--version"]);
890 assert!(result.is_err());
891 let err = result.unwrap_err();
892 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
893 }
894
895 #[test]
898 fn unknown_command_routes_to_external() {
899 let cli = parse(&["ubt", "nonexistent"]);
900 assert!(matches!(cli.command, Command::External(ref args) if args[0] == "nonexistent"));
901 }
902
903 #[test]
906 fn completions_bash() {
907 let cli = parse(&["ubt", "completions", "bash"]);
908 if let Command::Completions(args) = &cli.command {
909 assert_eq!(args.shell, clap_complete::Shell::Bash);
910 } else {
911 panic!("expected Completions command");
912 }
913 }
914
915 #[test]
916 fn completions_zsh() {
917 let cli = parse(&["ubt", "completions", "zsh"]);
918 if let Command::Completions(args) = &cli.command {
919 assert_eq!(args.shell, clap_complete::Shell::Zsh);
920 } else {
921 panic!("expected Completions command");
922 }
923 }
924
925 #[test]
926 fn completions_fish() {
927 let cli = parse(&["ubt", "completions", "fish"]);
928 if let Command::Completions(args) = &cli.command {
929 assert_eq!(args.shell, clap_complete::Shell::Fish);
930 } else {
931 panic!("expected Completions command");
932 }
933 }
934
935 #[test]
936 fn completions_powershell() {
937 let cli = parse(&["ubt", "completions", "powershell"]);
938 if let Command::Completions(args) = &cli.command {
939 assert_eq!(args.shell, clap_complete::Shell::PowerShell);
940 } else {
941 panic!("expected Completions command");
942 }
943 }
944
945 #[test]
948 fn run_file_alias() {
949 let cli = parse(&["ubt", "run:file", "script.ts"]);
950 assert_eq!(parse_command_name(&cli.command), "run-file");
951 }
952}