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,
326}
327
328#[derive(Subcommand, Debug)]
331pub enum ConfigCommand {
332 Show,
334}
335
336#[derive(Parser, Debug)]
339pub struct CompletionsArgs {
340 pub shell: clap_complete::Shell,
342}
343
344#[derive(Debug, Default, PartialEq)]
347pub struct UniversalFlags {
348 pub watch: bool,
349 pub coverage: bool,
350 pub dev: bool,
351 pub clean: bool,
352 pub fix: bool,
353 pub check: bool,
354 pub yes: bool,
355 pub dry_run: bool,
356}
357
358pub fn parse_command_name(cmd: &Command) -> &'static str {
362 match cmd {
363 Command::Dep(sub) => match sub {
364 DepCommand::Install(..) => "dep.install",
365 DepCommand::Remove(..) => "dep.remove",
366 DepCommand::Update(..) => "dep.update",
367 DepCommand::Outdated(..) => "dep.outdated",
368 DepCommand::List(..) => "dep.list",
369 DepCommand::Audit(..) => "dep.audit",
370 DepCommand::Lock(..) => "dep.lock",
371 DepCommand::Why(..) => "dep.why",
372 },
373 Command::Build(..) => "build",
374 Command::Start(..) => "start",
375 Command::Run(..) => "run",
376 Command::Fmt(..) => "fmt",
377 Command::RunFile(..) => "run-file",
378 Command::Exec(..) => "exec",
379 Command::Test(..) => "test",
380 Command::Lint(..) => "lint",
381 Command::Check(..) => "check",
382 Command::Db(sub) => match sub {
383 DbCommand::Migrate(..) => "db.migrate",
384 DbCommand::Rollback(..) => "db.rollback",
385 DbCommand::Seed(..) => "db.seed",
386 DbCommand::Create(..) => "db.create",
387 DbCommand::Drop(..) => "db.drop",
388 DbCommand::Reset(..) => "db.reset",
389 DbCommand::Status(..) => "db.status",
390 },
391 Command::Init => "init",
392 Command::Clean(..) => "clean",
393 Command::Release(..) => "release",
394 Command::Publish(..) => "publish",
395 Command::Tool(sub) => match sub {
396 ToolCommand::Info => "tool.info",
397 ToolCommand::Doctor => "tool.doctor",
398 ToolCommand::List => "tool.list",
399 ToolCommand::Docs => "tool.docs",
400 },
401 Command::Config(sub) => match sub {
402 ConfigCommand::Show => "config.show",
403 },
404 Command::Info => "info",
405 Command::Completions(..) => "completions",
406 Command::External(..) => unreachable!("External is dispatched before parse_command_name"),
407 }
408}
409
410pub fn collect_universal_flags(cmd: &Command) -> UniversalFlags {
412 match cmd {
413 Command::Build(args) => UniversalFlags {
414 dev: args.dev,
415 watch: args.watch,
416 clean: args.clean,
417 ..Default::default()
418 },
419 Command::Test(args) => UniversalFlags {
420 watch: args.watch,
421 coverage: args.coverage,
422 ..Default::default()
423 },
424 Command::Lint(args) => UniversalFlags {
425 fix: args.fix,
426 ..Default::default()
427 },
428 Command::Fmt(args) => UniversalFlags {
429 check: args.check,
430 ..Default::default()
431 },
432 Command::Db(DbCommand::Drop(args)) => UniversalFlags {
433 yes: args.yes,
434 ..Default::default()
435 },
436 Command::Db(DbCommand::Reset(args)) => UniversalFlags {
437 yes: args.yes,
438 ..Default::default()
439 },
440 Command::Release(args) => UniversalFlags {
441 dry_run: args.dry_run,
442 ..Default::default()
443 },
444 Command::Publish(args) => UniversalFlags {
445 yes: args.yes,
446 dry_run: args.dry_run,
447 ..Default::default()
448 },
449 Command::External(..) => {
450 unreachable!("External is dispatched before collect_universal_flags")
451 }
452 _ => UniversalFlags::default(),
453 }
454}
455
456pub fn collect_remaining_args(cmd: &Command) -> Vec<String> {
458 match cmd {
459 Command::Dep(sub) => match sub {
460 DepCommand::Install(a)
461 | DepCommand::Remove(a)
462 | DepCommand::Update(a)
463 | DepCommand::Outdated(a)
464 | DepCommand::List(a)
465 | DepCommand::Audit(a)
466 | DepCommand::Lock(a)
467 | DepCommand::Why(a) => a.args.clone(),
468 },
469 Command::Build(a) => a.args.clone(),
470 Command::Start(a) => a.args.clone(),
471 Command::Run(a) => a.args.clone(),
472 Command::Fmt(a) => a.args.clone(),
473 Command::RunFile(a) => a.args.clone(),
474 Command::Exec(a) => a.args.clone(),
475 Command::Test(a) => a.args.clone(),
476 Command::Lint(a) => a.args.clone(),
477 Command::Check(a) => a.args.clone(),
478 Command::Db(sub) => match sub {
479 DbCommand::Migrate(a)
480 | DbCommand::Rollback(a)
481 | DbCommand::Seed(a)
482 | DbCommand::Create(a)
483 | DbCommand::Status(a) => a.args.clone(),
484 DbCommand::Drop(a) => a.args.clone(),
485 DbCommand::Reset(a) => a.args.clone(),
486 },
487 Command::Clean(a) => a.args.clone(),
488 Command::Release(a) => a.args.clone(),
489 Command::Publish(a) => a.args.clone(),
490 Command::Init
491 | Command::Info
492 | Command::Tool(_)
493 | Command::Config(_)
494 | Command::Completions(_) => vec![],
495 Command::External(..) => {
496 unreachable!("External is dispatched before collect_remaining_args")
497 }
498 }
499}
500
501#[cfg(test)]
504mod tests {
505 use super::*;
506 use clap::Parser;
507
508 fn parse(args: &[&str]) -> Cli {
510 Cli::parse_from(args)
511 }
512
513 #[test]
516 fn command_name_dep_install() {
517 let cli = parse(&["ubt", "dep", "install"]);
518 assert_eq!(parse_command_name(&cli.command), "dep.install");
519 }
520
521 #[test]
522 fn command_name_dep_remove() {
523 let cli = parse(&["ubt", "dep", "remove"]);
524 assert_eq!(parse_command_name(&cli.command), "dep.remove");
525 }
526
527 #[test]
528 fn command_name_dep_update() {
529 let cli = parse(&["ubt", "dep", "update"]);
530 assert_eq!(parse_command_name(&cli.command), "dep.update");
531 }
532
533 #[test]
534 fn command_name_dep_outdated() {
535 let cli = parse(&["ubt", "dep", "outdated"]);
536 assert_eq!(parse_command_name(&cli.command), "dep.outdated");
537 }
538
539 #[test]
540 fn command_name_dep_list() {
541 let cli = parse(&["ubt", "dep", "list"]);
542 assert_eq!(parse_command_name(&cli.command), "dep.list");
543 }
544
545 #[test]
546 fn command_name_dep_audit() {
547 let cli = parse(&["ubt", "dep", "audit"]);
548 assert_eq!(parse_command_name(&cli.command), "dep.audit");
549 }
550
551 #[test]
552 fn command_name_dep_lock() {
553 let cli = parse(&["ubt", "dep", "lock"]);
554 assert_eq!(parse_command_name(&cli.command), "dep.lock");
555 }
556
557 #[test]
558 fn command_name_dep_why() {
559 let cli = parse(&["ubt", "dep", "why"]);
560 assert_eq!(parse_command_name(&cli.command), "dep.why");
561 }
562
563 #[test]
564 fn command_name_build() {
565 let cli = parse(&["ubt", "build"]);
566 assert_eq!(parse_command_name(&cli.command), "build");
567 }
568
569 #[test]
570 fn command_name_start() {
571 let cli = parse(&["ubt", "start"]);
572 assert_eq!(parse_command_name(&cli.command), "start");
573 }
574
575 #[test]
576 fn command_name_run() {
577 let cli = parse(&["ubt", "run", "dev"]);
578 assert_eq!(parse_command_name(&cli.command), "run");
579 }
580
581 #[test]
582 fn command_name_fmt() {
583 let cli = parse(&["ubt", "fmt"]);
584 assert_eq!(parse_command_name(&cli.command), "fmt");
585 }
586
587 #[test]
588 fn command_name_run_file() {
589 let cli = parse(&["ubt", "run-file", "script.ts"]);
590 assert_eq!(parse_command_name(&cli.command), "run-file");
591 }
592
593 #[test]
594 fn command_name_exec() {
595 let cli = parse(&["ubt", "exec", "node"]);
596 assert_eq!(parse_command_name(&cli.command), "exec");
597 }
598
599 #[test]
600 fn command_name_test() {
601 let cli = parse(&["ubt", "test"]);
602 assert_eq!(parse_command_name(&cli.command), "test");
603 }
604
605 #[test]
606 fn command_name_lint() {
607 let cli = parse(&["ubt", "lint"]);
608 assert_eq!(parse_command_name(&cli.command), "lint");
609 }
610
611 #[test]
612 fn command_name_check() {
613 let cli = parse(&["ubt", "check"]);
614 assert_eq!(parse_command_name(&cli.command), "check");
615 }
616
617 #[test]
618 fn command_name_db_migrate() {
619 let cli = parse(&["ubt", "db", "migrate"]);
620 assert_eq!(parse_command_name(&cli.command), "db.migrate");
621 }
622
623 #[test]
624 fn command_name_db_rollback() {
625 let cli = parse(&["ubt", "db", "rollback"]);
626 assert_eq!(parse_command_name(&cli.command), "db.rollback");
627 }
628
629 #[test]
630 fn command_name_db_seed() {
631 let cli = parse(&["ubt", "db", "seed"]);
632 assert_eq!(parse_command_name(&cli.command), "db.seed");
633 }
634
635 #[test]
636 fn command_name_db_create() {
637 let cli = parse(&["ubt", "db", "create"]);
638 assert_eq!(parse_command_name(&cli.command), "db.create");
639 }
640
641 #[test]
642 fn command_name_db_drop() {
643 let cli = parse(&["ubt", "db", "drop"]);
644 assert_eq!(parse_command_name(&cli.command), "db.drop");
645 }
646
647 #[test]
648 fn command_name_db_reset() {
649 let cli = parse(&["ubt", "db", "reset"]);
650 assert_eq!(parse_command_name(&cli.command), "db.reset");
651 }
652
653 #[test]
654 fn command_name_db_status() {
655 let cli = parse(&["ubt", "db", "status"]);
656 assert_eq!(parse_command_name(&cli.command), "db.status");
657 }
658
659 #[test]
660 fn command_name_init() {
661 let cli = parse(&["ubt", "init"]);
662 assert_eq!(parse_command_name(&cli.command), "init");
663 }
664
665 #[test]
666 fn command_name_clean() {
667 let cli = parse(&["ubt", "clean"]);
668 assert_eq!(parse_command_name(&cli.command), "clean");
669 }
670
671 #[test]
672 fn command_name_release() {
673 let cli = parse(&["ubt", "release"]);
674 assert_eq!(parse_command_name(&cli.command), "release");
675 }
676
677 #[test]
678 fn command_name_publish() {
679 let cli = parse(&["ubt", "publish"]);
680 assert_eq!(parse_command_name(&cli.command), "publish");
681 }
682
683 #[test]
684 fn command_name_tool_info() {
685 let cli = parse(&["ubt", "tool", "info"]);
686 assert_eq!(parse_command_name(&cli.command), "tool.info");
687 }
688
689 #[test]
690 fn command_name_tool_doctor() {
691 let cli = parse(&["ubt", "tool", "doctor"]);
692 assert_eq!(parse_command_name(&cli.command), "tool.doctor");
693 }
694
695 #[test]
696 fn command_name_tool_list() {
697 let cli = parse(&["ubt", "tool", "list"]);
698 assert_eq!(parse_command_name(&cli.command), "tool.list");
699 }
700
701 #[test]
702 fn command_name_tool_docs() {
703 let cli = parse(&["ubt", "tool", "docs"]);
704 assert_eq!(parse_command_name(&cli.command), "tool.docs");
705 }
706
707 #[test]
708 fn command_name_config_show() {
709 let cli = parse(&["ubt", "config", "show"]);
710 assert_eq!(parse_command_name(&cli.command), "config.show");
711 }
712
713 #[test]
714 fn command_name_info() {
715 let cli = parse(&["ubt", "info"]);
716 assert_eq!(parse_command_name(&cli.command), "info");
717 }
718
719 #[test]
720 fn command_name_completions() {
721 let cli = parse(&["ubt", "completions", "bash"]);
722 assert_eq!(parse_command_name(&cli.command), "completions");
723 }
724
725 #[test]
728 fn global_verbose_flag() {
729 let cli = parse(&["ubt", "-v", "info"]);
730 assert!(cli.verbose);
731 assert!(!cli.quiet);
732 }
733
734 #[test]
735 fn global_quiet_flag() {
736 let cli = parse(&["ubt", "-q", "info"]);
737 assert!(!cli.verbose);
738 assert!(cli.quiet);
739 }
740
741 #[test]
742 fn global_tool_flag() {
743 let cli = parse(&["ubt", "--tool", "npm", "info"]);
744 assert_eq!(cli.tool, Some("npm".to_string()));
745 }
746
747 #[test]
748 fn global_tool_flag_absent() {
749 let cli = parse(&["ubt", "info"]);
750 assert_eq!(cli.tool, None);
751 }
752
753 #[test]
756 fn universal_flags_build_dev() {
757 let cli = parse(&["ubt", "build", "--dev"]);
758 let flags = collect_universal_flags(&cli.command);
759 assert!(flags.dev);
760 assert!(!flags.watch);
761 assert!(!flags.clean);
762 }
763
764 #[test]
765 fn universal_flags_build_watch_clean() {
766 let cli = parse(&["ubt", "build", "--watch", "--clean"]);
767 let flags = collect_universal_flags(&cli.command);
768 assert!(flags.watch);
769 assert!(flags.clean);
770 }
771
772 #[test]
773 fn universal_flags_test_coverage_watch() {
774 let cli = parse(&["ubt", "test", "--coverage", "--watch"]);
775 let flags = collect_universal_flags(&cli.command);
776 assert!(flags.coverage);
777 assert!(flags.watch);
778 }
779
780 #[test]
781 fn universal_flags_lint_fix() {
782 let cli = parse(&["ubt", "lint", "--fix"]);
783 let flags = collect_universal_flags(&cli.command);
784 assert!(flags.fix);
785 }
786
787 #[test]
788 fn universal_flags_fmt_check() {
789 let cli = parse(&["ubt", "fmt", "--check"]);
790 let flags = collect_universal_flags(&cli.command);
791 assert!(flags.check);
792 }
793
794 #[test]
795 fn universal_flags_db_drop_yes() {
796 let cli = parse(&["ubt", "db", "drop", "--yes"]);
797 let flags = collect_universal_flags(&cli.command);
798 assert!(flags.yes);
799 }
800
801 #[test]
802 fn universal_flags_db_reset_yes() {
803 let cli = parse(&["ubt", "db", "reset", "-y"]);
804 let flags = collect_universal_flags(&cli.command);
805 assert!(flags.yes);
806 }
807
808 #[test]
809 fn universal_flags_publish_dry_run() {
810 let cli = parse(&["ubt", "publish", "--dry-run"]);
811 let flags = collect_universal_flags(&cli.command);
812 assert!(flags.dry_run);
813 assert!(!flags.yes);
814 }
815
816 #[test]
817 fn universal_flags_publish_yes_and_dry_run() {
818 let cli = parse(&["ubt", "publish", "--yes", "--dry-run"]);
819 let flags = collect_universal_flags(&cli.command);
820 assert!(flags.yes);
821 assert!(flags.dry_run);
822 }
823
824 #[test]
825 fn universal_flags_release_dry_run() {
826 let cli = parse(&["ubt", "release", "--dry-run"]);
827 let flags = collect_universal_flags(&cli.command);
828 assert!(flags.dry_run);
829 }
830
831 #[test]
832 fn universal_flags_default_for_info() {
833 let cli = parse(&["ubt", "info"]);
834 let flags = collect_universal_flags(&cli.command);
835 assert_eq!(flags, UniversalFlags::default());
836 }
837
838 #[test]
841 fn remaining_args_build() {
842 let cli = parse(&["ubt", "build", "--dev", "--", "extra1", "extra2"]);
843 let args = collect_remaining_args(&cli.command);
844 assert!(args.contains(&"extra1".to_string()));
845 assert!(args.contains(&"extra2".to_string()));
846 }
847
848 #[test]
849 fn remaining_args_start() {
850 let cli = parse(&["ubt", "start", "foo", "bar"]);
851 let args = collect_remaining_args(&cli.command);
852 assert_eq!(args, vec!["foo", "bar"]);
853 }
854
855 #[test]
856 fn remaining_args_test_with_flags() {
857 let cli = parse(&["ubt", "test", "--watch", "some-pattern"]);
858 let args = collect_remaining_args(&cli.command);
859 assert_eq!(args, vec!["some-pattern"]);
860 }
861
862 #[test]
863 fn remaining_args_dep_install() {
864 let cli = parse(&["ubt", "dep", "install", "lodash", "express"]);
865 let args = collect_remaining_args(&cli.command);
866 assert_eq!(args, vec!["lodash", "express"]);
867 }
868
869 #[test]
870 fn remaining_args_run() {
871 let cli = parse(&["ubt", "run", "dev", "--port", "3000"]);
872 let args = collect_remaining_args(&cli.command);
873 assert_eq!(args, vec!["--port", "3000"]);
874 }
875
876 #[test]
877 fn remaining_args_empty_for_init() {
878 let cli = parse(&["ubt", "init"]);
879 let args = collect_remaining_args(&cli.command);
880 assert!(args.is_empty());
881 }
882
883 #[test]
884 fn remaining_args_empty_for_info() {
885 let cli = parse(&["ubt", "info"]);
886 let args = collect_remaining_args(&cli.command);
887 assert!(args.is_empty());
888 }
889
890 #[test]
891 fn remaining_args_empty_for_tool() {
892 let cli = parse(&["ubt", "tool", "info"]);
893 let args = collect_remaining_args(&cli.command);
894 assert!(args.is_empty());
895 }
896
897 #[test]
898 fn remaining_args_exec() {
899 let cli = parse(&["ubt", "exec", "node", "-e", "console.log(1)"]);
900 let args = collect_remaining_args(&cli.command);
901 assert_eq!(args, vec!["-e", "console.log(1)"]);
902 }
903
904 #[test]
907 fn help_output_produces_error() {
908 let result = Cli::try_parse_from(["ubt", "--help"]);
909 assert!(result.is_err());
910 let err = result.unwrap_err();
911 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp);
912 }
913
914 #[test]
915 fn version_output_produces_error() {
916 let result = Cli::try_parse_from(["ubt", "--version"]);
917 assert!(result.is_err());
918 let err = result.unwrap_err();
919 assert_eq!(err.kind(), clap::error::ErrorKind::DisplayVersion);
920 }
921
922 #[test]
925 fn unknown_command_routes_to_external() {
926 let cli = parse(&["ubt", "nonexistent"]);
927 assert!(matches!(cli.command, Command::External(ref args) if args[0] == "nonexistent"));
928 }
929
930 #[test]
933 fn completions_bash() {
934 let cli = parse(&["ubt", "completions", "bash"]);
935 if let Command::Completions(args) = &cli.command {
936 assert_eq!(args.shell, clap_complete::Shell::Bash);
937 } else {
938 panic!("expected Completions command");
939 }
940 }
941
942 #[test]
943 fn completions_zsh() {
944 let cli = parse(&["ubt", "completions", "zsh"]);
945 if let Command::Completions(args) = &cli.command {
946 assert_eq!(args.shell, clap_complete::Shell::Zsh);
947 } else {
948 panic!("expected Completions command");
949 }
950 }
951
952 #[test]
953 fn completions_fish() {
954 let cli = parse(&["ubt", "completions", "fish"]);
955 if let Command::Completions(args) = &cli.command {
956 assert_eq!(args.shell, clap_complete::Shell::Fish);
957 } else {
958 panic!("expected Completions command");
959 }
960 }
961
962 #[test]
963 fn completions_powershell() {
964 let cli = parse(&["ubt", "completions", "powershell"]);
965 if let Command::Completions(args) = &cli.command {
966 assert_eq!(args.shell, clap_complete::Shell::PowerShell);
967 } else {
968 panic!("expected Completions command");
969 }
970 }
971
972 #[test]
975 fn run_file_alias() {
976 let cli = parse(&["ubt", "run:file", "script.ts"]);
977 assert_eq!(parse_command_name(&cli.command), "run-file");
978 }
979}