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
91#[derive(Parser, Debug)]
94#[command(trailing_var_arg = true, allow_hyphen_values = true)]
95pub struct PassthroughArgs {
96 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
97 pub args: Vec<String>,
98}
99
100#[derive(Subcommand, Debug)]
103pub enum DepCommand {
104 Install(PassthroughArgs),
106
107 Remove(PassthroughArgs),
109
110 Update(PassthroughArgs),
112
113 Outdated(PassthroughArgs),
115
116 List(PassthroughArgs),
118
119 Audit(PassthroughArgs),
121
122 Lock(PassthroughArgs),
124
125 Why(PassthroughArgs),
127}
128
129#[derive(Parser, Debug)]
132#[command(trailing_var_arg = true, allow_hyphen_values = true)]
133pub struct BuildArgs {
134 #[arg(long)]
136 pub dev: bool,
137
138 #[arg(long)]
140 pub watch: bool,
141
142 #[arg(long)]
144 pub clean: bool,
145
146 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
147 pub args: Vec<String>,
148}
149
150#[derive(Parser, Debug)]
153#[command(trailing_var_arg = true, allow_hyphen_values = true)]
154pub struct RunArgs {
155 pub script: String,
157
158 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
159 pub args: Vec<String>,
160}
161
162#[derive(Parser, Debug)]
165#[command(trailing_var_arg = true, allow_hyphen_values = true)]
166pub struct FmtArgs {
167 #[arg(long)]
169 pub check: bool,
170
171 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
172 pub args: Vec<String>,
173}
174
175#[derive(Parser, Debug)]
178#[command(trailing_var_arg = true, allow_hyphen_values = true)]
179pub struct RunFileArgs {
180 pub file: String,
182
183 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
184 pub args: Vec<String>,
185}
186
187#[derive(Parser, Debug)]
190#[command(trailing_var_arg = true, allow_hyphen_values = true)]
191pub struct ExecArgs {
192 pub cmd: String,
194
195 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
196 pub args: Vec<String>,
197}
198
199#[derive(Parser, Debug)]
202#[command(trailing_var_arg = true, allow_hyphen_values = true)]
203pub struct TestArgs {
204 #[arg(long)]
206 pub watch: bool,
207
208 #[arg(long)]
210 pub coverage: bool,
211
212 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
213 pub args: Vec<String>,
214}
215
216#[derive(Parser, Debug)]
219#[command(trailing_var_arg = true, allow_hyphen_values = true)]
220pub struct LintArgs {
221 #[arg(long)]
223 pub fix: bool,
224
225 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
226 pub args: Vec<String>,
227}
228
229#[derive(Subcommand, Debug)]
232pub enum DbCommand {
233 Migrate(PassthroughArgs),
235
236 Rollback(PassthroughArgs),
238
239 Seed(PassthroughArgs),
241
242 Create(PassthroughArgs),
244
245 Drop(DbDropArgs),
247
248 Reset(DbResetArgs),
250
251 Status(PassthroughArgs),
253}
254
255#[derive(Parser, Debug)]
256#[command(trailing_var_arg = true, allow_hyphen_values = true)]
257pub struct DbDropArgs {
258 #[arg(short, long)]
260 pub yes: bool,
261
262 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
263 pub args: Vec<String>,
264}
265
266#[derive(Parser, Debug)]
267#[command(trailing_var_arg = true, allow_hyphen_values = true)]
268pub struct DbResetArgs {
269 #[arg(short, long)]
271 pub yes: bool,
272
273 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
274 pub args: Vec<String>,
275}
276
277#[derive(Parser, Debug)]
280#[command(trailing_var_arg = true, allow_hyphen_values = true)]
281pub struct ReleaseArgs {
282 #[arg(long)]
284 pub dry_run: bool,
285
286 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
287 pub args: Vec<String>,
288}
289
290#[derive(Parser, Debug)]
293#[command(trailing_var_arg = true, allow_hyphen_values = true)]
294pub struct PublishArgs {
295 #[arg(short, long)]
297 pub yes: bool,
298
299 #[arg(long)]
301 pub dry_run: bool,
302
303 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
304 pub args: Vec<String>,
305}
306
307#[derive(Subcommand, Debug)]
310pub enum ToolCommand {
311 Info,
313
314 Doctor,
316
317 List,
319
320 Docs,
322}
323
324#[derive(Subcommand, Debug)]
327pub enum ConfigCommand {
328 Show,
330}
331
332#[derive(Parser, Debug)]
335pub struct CompletionsArgs {
336 pub shell: clap_complete::Shell,
338}
339
340#[derive(Debug, Default, PartialEq)]
343pub struct UniversalFlags {
344 pub watch: bool,
345 pub coverage: bool,
346 pub dev: bool,
347 pub clean: bool,
348 pub fix: bool,
349 pub check: bool,
350 pub yes: bool,
351 pub dry_run: bool,
352}
353
354pub fn parse_command_name(cmd: &Command) -> &'static str {
358 match cmd {
359 Command::Dep(sub) => match sub {
360 DepCommand::Install(..) => "dep.install",
361 DepCommand::Remove(..) => "dep.remove",
362 DepCommand::Update(..) => "dep.update",
363 DepCommand::Outdated(..) => "dep.outdated",
364 DepCommand::List(..) => "dep.list",
365 DepCommand::Audit(..) => "dep.audit",
366 DepCommand::Lock(..) => "dep.lock",
367 DepCommand::Why(..) => "dep.why",
368 },
369 Command::Build(..) => "build",
370 Command::Start(..) => "start",
371 Command::Run(..) => "run",
372 Command::Fmt(..) => "fmt",
373 Command::RunFile(..) => "run-file",
374 Command::Exec(..) => "exec",
375 Command::Test(..) => "test",
376 Command::Lint(..) => "lint",
377 Command::Check(..) => "check",
378 Command::Db(sub) => match sub {
379 DbCommand::Migrate(..) => "db.migrate",
380 DbCommand::Rollback(..) => "db.rollback",
381 DbCommand::Seed(..) => "db.seed",
382 DbCommand::Create(..) => "db.create",
383 DbCommand::Drop(..) => "db.drop",
384 DbCommand::Reset(..) => "db.reset",
385 DbCommand::Status(..) => "db.status",
386 },
387 Command::Init => "init",
388 Command::Clean(..) => "clean",
389 Command::Release(..) => "release",
390 Command::Publish(..) => "publish",
391 Command::Tool(sub) => match sub {
392 ToolCommand::Info => "tool.info",
393 ToolCommand::Doctor => "tool.doctor",
394 ToolCommand::List => "tool.list",
395 ToolCommand::Docs => "tool.docs",
396 },
397 Command::Config(sub) => match sub {
398 ConfigCommand::Show => "config.show",
399 },
400 Command::Info => "info",
401 Command::Completions(..) => "completions",
402 }
403}
404
405pub fn collect_universal_flags(cmd: &Command) -> UniversalFlags {
407 match cmd {
408 Command::Build(args) => UniversalFlags {
409 dev: args.dev,
410 watch: args.watch,
411 clean: args.clean,
412 ..Default::default()
413 },
414 Command::Test(args) => UniversalFlags {
415 watch: args.watch,
416 coverage: args.coverage,
417 ..Default::default()
418 },
419 Command::Lint(args) => UniversalFlags {
420 fix: args.fix,
421 ..Default::default()
422 },
423 Command::Fmt(args) => UniversalFlags {
424 check: args.check,
425 ..Default::default()
426 },
427 Command::Db(DbCommand::Drop(args)) => UniversalFlags {
428 yes: args.yes,
429 ..Default::default()
430 },
431 Command::Db(DbCommand::Reset(args)) => UniversalFlags {
432 yes: args.yes,
433 ..Default::default()
434 },
435 Command::Release(args) => UniversalFlags {
436 dry_run: args.dry_run,
437 ..Default::default()
438 },
439 Command::Publish(args) => UniversalFlags {
440 yes: args.yes,
441 dry_run: args.dry_run,
442 ..Default::default()
443 },
444 _ => UniversalFlags::default(),
445 }
446}
447
448pub fn collect_remaining_args(cmd: &Command) -> Vec<String> {
450 match cmd {
451 Command::Dep(sub) => match sub {
452 DepCommand::Install(a)
453 | DepCommand::Remove(a)
454 | DepCommand::Update(a)
455 | DepCommand::Outdated(a)
456 | DepCommand::List(a)
457 | DepCommand::Audit(a)
458 | DepCommand::Lock(a)
459 | DepCommand::Why(a) => a.args.clone(),
460 },
461 Command::Build(a) => a.args.clone(),
462 Command::Start(a) => a.args.clone(),
463 Command::Run(a) => a.args.clone(),
464 Command::Fmt(a) => a.args.clone(),
465 Command::RunFile(a) => a.args.clone(),
466 Command::Exec(a) => a.args.clone(),
467 Command::Test(a) => a.args.clone(),
468 Command::Lint(a) => a.args.clone(),
469 Command::Check(a) => a.args.clone(),
470 Command::Db(sub) => match sub {
471 DbCommand::Migrate(a)
472 | DbCommand::Rollback(a)
473 | DbCommand::Seed(a)
474 | DbCommand::Create(a)
475 | DbCommand::Status(a) => a.args.clone(),
476 DbCommand::Drop(a) => a.args.clone(),
477 DbCommand::Reset(a) => a.args.clone(),
478 },
479 Command::Clean(a) => a.args.clone(),
480 Command::Release(a) => a.args.clone(),
481 Command::Publish(a) => a.args.clone(),
482 Command::Init
483 | Command::Info
484 | Command::Tool(_)
485 | Command::Config(_)
486 | Command::Completions(_) => vec![],
487 }
488}
489
490#[cfg(test)]
493mod tests {
494 use super::*;
495 use clap::Parser;
496
497 fn parse(args: &[&str]) -> Cli {
499 Cli::parse_from(args)
500 }
501
502 #[test]
505 fn command_name_dep_install() {
506 let cli = parse(&["ubt", "dep", "install"]);
507 assert_eq!(parse_command_name(&cli.command), "dep.install");
508 }
509
510 #[test]
511 fn command_name_dep_remove() {
512 let cli = parse(&["ubt", "dep", "remove"]);
513 assert_eq!(parse_command_name(&cli.command), "dep.remove");
514 }
515
516 #[test]
517 fn command_name_dep_update() {
518 let cli = parse(&["ubt", "dep", "update"]);
519 assert_eq!(parse_command_name(&cli.command), "dep.update");
520 }
521
522 #[test]
523 fn command_name_dep_outdated() {
524 let cli = parse(&["ubt", "dep", "outdated"]);
525 assert_eq!(parse_command_name(&cli.command), "dep.outdated");
526 }
527
528 #[test]
529 fn command_name_dep_list() {
530 let cli = parse(&["ubt", "dep", "list"]);
531 assert_eq!(parse_command_name(&cli.command), "dep.list");
532 }
533
534 #[test]
535 fn command_name_dep_audit() {
536 let cli = parse(&["ubt", "dep", "audit"]);
537 assert_eq!(parse_command_name(&cli.command), "dep.audit");
538 }
539
540 #[test]
541 fn command_name_dep_lock() {
542 let cli = parse(&["ubt", "dep", "lock"]);
543 assert_eq!(parse_command_name(&cli.command), "dep.lock");
544 }
545
546 #[test]
547 fn command_name_dep_why() {
548 let cli = parse(&["ubt", "dep", "why"]);
549 assert_eq!(parse_command_name(&cli.command), "dep.why");
550 }
551
552 #[test]
553 fn command_name_build() {
554 let cli = parse(&["ubt", "build"]);
555 assert_eq!(parse_command_name(&cli.command), "build");
556 }
557
558 #[test]
559 fn command_name_start() {
560 let cli = parse(&["ubt", "start"]);
561 assert_eq!(parse_command_name(&cli.command), "start");
562 }
563
564 #[test]
565 fn command_name_run() {
566 let cli = parse(&["ubt", "run", "dev"]);
567 assert_eq!(parse_command_name(&cli.command), "run");
568 }
569
570 #[test]
571 fn command_name_fmt() {
572 let cli = parse(&["ubt", "fmt"]);
573 assert_eq!(parse_command_name(&cli.command), "fmt");
574 }
575
576 #[test]
577 fn command_name_run_file() {
578 let cli = parse(&["ubt", "run-file", "script.ts"]);
579 assert_eq!(parse_command_name(&cli.command), "run-file");
580 }
581
582 #[test]
583 fn command_name_exec() {
584 let cli = parse(&["ubt", "exec", "node"]);
585 assert_eq!(parse_command_name(&cli.command), "exec");
586 }
587
588 #[test]
589 fn command_name_test() {
590 let cli = parse(&["ubt", "test"]);
591 assert_eq!(parse_command_name(&cli.command), "test");
592 }
593
594 #[test]
595 fn command_name_lint() {
596 let cli = parse(&["ubt", "lint"]);
597 assert_eq!(parse_command_name(&cli.command), "lint");
598 }
599
600 #[test]
601 fn command_name_check() {
602 let cli = parse(&["ubt", "check"]);
603 assert_eq!(parse_command_name(&cli.command), "check");
604 }
605
606 #[test]
607 fn command_name_db_migrate() {
608 let cli = parse(&["ubt", "db", "migrate"]);
609 assert_eq!(parse_command_name(&cli.command), "db.migrate");
610 }
611
612 #[test]
613 fn command_name_db_rollback() {
614 let cli = parse(&["ubt", "db", "rollback"]);
615 assert_eq!(parse_command_name(&cli.command), "db.rollback");
616 }
617
618 #[test]
619 fn command_name_db_seed() {
620 let cli = parse(&["ubt", "db", "seed"]);
621 assert_eq!(parse_command_name(&cli.command), "db.seed");
622 }
623
624 #[test]
625 fn command_name_db_create() {
626 let cli = parse(&["ubt", "db", "create"]);
627 assert_eq!(parse_command_name(&cli.command), "db.create");
628 }
629
630 #[test]
631 fn command_name_db_drop() {
632 let cli = parse(&["ubt", "db", "drop"]);
633 assert_eq!(parse_command_name(&cli.command), "db.drop");
634 }
635
636 #[test]
637 fn command_name_db_reset() {
638 let cli = parse(&["ubt", "db", "reset"]);
639 assert_eq!(parse_command_name(&cli.command), "db.reset");
640 }
641
642 #[test]
643 fn command_name_db_status() {
644 let cli = parse(&["ubt", "db", "status"]);
645 assert_eq!(parse_command_name(&cli.command), "db.status");
646 }
647
648 #[test]
649 fn command_name_init() {
650 let cli = parse(&["ubt", "init"]);
651 assert_eq!(parse_command_name(&cli.command), "init");
652 }
653
654 #[test]
655 fn command_name_clean() {
656 let cli = parse(&["ubt", "clean"]);
657 assert_eq!(parse_command_name(&cli.command), "clean");
658 }
659
660 #[test]
661 fn command_name_release() {
662 let cli = parse(&["ubt", "release"]);
663 assert_eq!(parse_command_name(&cli.command), "release");
664 }
665
666 #[test]
667 fn command_name_publish() {
668 let cli = parse(&["ubt", "publish"]);
669 assert_eq!(parse_command_name(&cli.command), "publish");
670 }
671
672 #[test]
673 fn command_name_tool_info() {
674 let cli = parse(&["ubt", "tool", "info"]);
675 assert_eq!(parse_command_name(&cli.command), "tool.info");
676 }
677
678 #[test]
679 fn command_name_tool_doctor() {
680 let cli = parse(&["ubt", "tool", "doctor"]);
681 assert_eq!(parse_command_name(&cli.command), "tool.doctor");
682 }
683
684 #[test]
685 fn command_name_tool_list() {
686 let cli = parse(&["ubt", "tool", "list"]);
687 assert_eq!(parse_command_name(&cli.command), "tool.list");
688 }
689
690 #[test]
691 fn command_name_tool_docs() {
692 let cli = parse(&["ubt", "tool", "docs"]);
693 assert_eq!(parse_command_name(&cli.command), "tool.docs");
694 }
695
696 #[test]
697 fn command_name_config_show() {
698 let cli = parse(&["ubt", "config", "show"]);
699 assert_eq!(parse_command_name(&cli.command), "config.show");
700 }
701
702 #[test]
703 fn command_name_info() {
704 let cli = parse(&["ubt", "info"]);
705 assert_eq!(parse_command_name(&cli.command), "info");
706 }
707
708 #[test]
709 fn command_name_completions() {
710 let cli = parse(&["ubt", "completions", "bash"]);
711 assert_eq!(parse_command_name(&cli.command), "completions");
712 }
713
714 #[test]
717 fn global_verbose_flag() {
718 let cli = parse(&["ubt", "-v", "info"]);
719 assert!(cli.verbose);
720 assert!(!cli.quiet);
721 }
722
723 #[test]
724 fn global_quiet_flag() {
725 let cli = parse(&["ubt", "-q", "info"]);
726 assert!(!cli.verbose);
727 assert!(cli.quiet);
728 }
729
730 #[test]
731 fn global_tool_flag() {
732 let cli = parse(&["ubt", "--tool", "npm", "info"]);
733 assert_eq!(cli.tool, Some("npm".to_string()));
734 }
735
736 #[test]
737 fn global_tool_flag_absent() {
738 let cli = parse(&["ubt", "info"]);
739 assert_eq!(cli.tool, None);
740 }
741
742 #[test]
745 fn universal_flags_build_dev() {
746 let cli = parse(&["ubt", "build", "--dev"]);
747 let flags = collect_universal_flags(&cli.command);
748 assert!(flags.dev);
749 assert!(!flags.watch);
750 assert!(!flags.clean);
751 }
752
753 #[test]
754 fn universal_flags_build_watch_clean() {
755 let cli = parse(&["ubt", "build", "--watch", "--clean"]);
756 let flags = collect_universal_flags(&cli.command);
757 assert!(flags.watch);
758 assert!(flags.clean);
759 }
760
761 #[test]
762 fn universal_flags_test_coverage_watch() {
763 let cli = parse(&["ubt", "test", "--coverage", "--watch"]);
764 let flags = collect_universal_flags(&cli.command);
765 assert!(flags.coverage);
766 assert!(flags.watch);
767 }
768
769 #[test]
770 fn universal_flags_lint_fix() {
771 let cli = parse(&["ubt", "lint", "--fix"]);
772 let flags = collect_universal_flags(&cli.command);
773 assert!(flags.fix);
774 }
775
776 #[test]
777 fn universal_flags_fmt_check() {
778 let cli = parse(&["ubt", "fmt", "--check"]);
779 let flags = collect_universal_flags(&cli.command);
780 assert!(flags.check);
781 }
782
783 #[test]
784 fn universal_flags_db_drop_yes() {
785 let cli = parse(&["ubt", "db", "drop", "--yes"]);
786 let flags = collect_universal_flags(&cli.command);
787 assert!(flags.yes);
788 }
789
790 #[test]
791 fn universal_flags_db_reset_yes() {
792 let cli = parse(&["ubt", "db", "reset", "-y"]);
793 let flags = collect_universal_flags(&cli.command);
794 assert!(flags.yes);
795 }
796
797 #[test]
798 fn universal_flags_publish_dry_run() {
799 let cli = parse(&["ubt", "publish", "--dry-run"]);
800 let flags = collect_universal_flags(&cli.command);
801 assert!(flags.dry_run);
802 assert!(!flags.yes);
803 }
804
805 #[test]
806 fn universal_flags_publish_yes_and_dry_run() {
807 let cli = parse(&["ubt", "publish", "--yes", "--dry-run"]);
808 let flags = collect_universal_flags(&cli.command);
809 assert!(flags.yes);
810 assert!(flags.dry_run);
811 }
812
813 #[test]
814 fn universal_flags_release_dry_run() {
815 let cli = parse(&["ubt", "release", "--dry-run"]);
816 let flags = collect_universal_flags(&cli.command);
817 assert!(flags.dry_run);
818 }
819
820 #[test]
821 fn universal_flags_default_for_info() {
822 let cli = parse(&["ubt", "info"]);
823 let flags = collect_universal_flags(&cli.command);
824 assert_eq!(flags, UniversalFlags::default());
825 }
826
827 #[test]
830 fn remaining_args_build() {
831 let cli = parse(&["ubt", "build", "--dev", "--", "extra1", "extra2"]);
832 let args = collect_remaining_args(&cli.command);
833 assert!(args.contains(&"extra1".to_string()));
834 assert!(args.contains(&"extra2".to_string()));
835 }
836
837 #[test]
838 fn remaining_args_start() {
839 let cli = parse(&["ubt", "start", "foo", "bar"]);
840 let args = collect_remaining_args(&cli.command);
841 assert_eq!(args, vec!["foo", "bar"]);
842 }
843
844 #[test]
845 fn remaining_args_test_with_flags() {
846 let cli = parse(&["ubt", "test", "--watch", "some-pattern"]);
847 let args = collect_remaining_args(&cli.command);
848 assert_eq!(args, vec!["some-pattern"]);
849 }
850
851 #[test]
852 fn remaining_args_dep_install() {
853 let cli = parse(&["ubt", "dep", "install", "lodash", "express"]);
854 let args = collect_remaining_args(&cli.command);
855 assert_eq!(args, vec!["lodash", "express"]);
856 }
857
858 #[test]
859 fn remaining_args_run() {
860 let cli = parse(&["ubt", "run", "dev", "--port", "3000"]);
861 let args = collect_remaining_args(&cli.command);
862 assert_eq!(args, vec!["--port", "3000"]);
863 }
864
865 #[test]
866 fn remaining_args_empty_for_init() {
867 let cli = parse(&["ubt", "init"]);
868 let args = collect_remaining_args(&cli.command);
869 assert!(args.is_empty());
870 }
871
872 #[test]
873 fn remaining_args_empty_for_info() {
874 let cli = parse(&["ubt", "info"]);
875 let args = collect_remaining_args(&cli.command);
876 assert!(args.is_empty());
877 }
878
879 #[test]
880 fn remaining_args_empty_for_tool() {
881 let cli = parse(&["ubt", "tool", "info"]);
882 let args = collect_remaining_args(&cli.command);
883 assert!(args.is_empty());
884 }
885
886 #[test]
887 fn remaining_args_exec() {
888 let cli = parse(&["ubt", "exec", "node", "-e", "console.log(1)"]);
889 let args = collect_remaining_args(&cli.command);
890 assert_eq!(args, vec!["-e", "console.log(1)"]);
891 }
892
893 #[test]
896 fn help_output_produces_error() {
897 let result = Cli::try_parse_from(["ubt", "--help"]);
898 assert!(result.is_err());
899 let err = result.unwrap_err();
900 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
901 }
902
903 #[test]
904 fn version_output_produces_error() {
905 let result = Cli::try_parse_from(["ubt", "--version"]);
906 assert!(result.is_err());
907 let err = result.unwrap_err();
908 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
909 }
910
911 #[test]
914 fn error_on_unknown_command() {
915 let result = Cli::try_parse_from(["ubt", "nonexistent"]);
916 assert!(result.is_err());
917 }
918
919 #[test]
922 fn completions_bash() {
923 let cli = parse(&["ubt", "completions", "bash"]);
924 if let Command::Completions(args) = &cli.command {
925 assert_eq!(args.shell, clap_complete::Shell::Bash);
926 } else {
927 panic!("expected Completions command");
928 }
929 }
930
931 #[test]
932 fn completions_zsh() {
933 let cli = parse(&["ubt", "completions", "zsh"]);
934 if let Command::Completions(args) = &cli.command {
935 assert_eq!(args.shell, clap_complete::Shell::Zsh);
936 } else {
937 panic!("expected Completions command");
938 }
939 }
940
941 #[test]
942 fn completions_fish() {
943 let cli = parse(&["ubt", "completions", "fish"]);
944 if let Command::Completions(args) = &cli.command {
945 assert_eq!(args.shell, clap_complete::Shell::Fish);
946 } else {
947 panic!("expected Completions command");
948 }
949 }
950
951 #[test]
952 fn completions_powershell() {
953 let cli = parse(&["ubt", "completions", "powershell"]);
954 if let Command::Completions(args) = &cli.command {
955 assert_eq!(args.shell, clap_complete::Shell::PowerShell);
956 } else {
957 panic!("expected Completions command");
958 }
959 }
960
961 #[test]
964 fn run_file_alias() {
965 let cli = parse(&["ubt", "run:file", "script.ts"]);
966 assert_eq!(parse_command_name(&cli.command), "run-file");
967 }
968}