1use std::ffi::OsString;
9
10use clap::Parser;
11
12mod args;
13mod commands;
14mod config;
15mod config_toml;
16mod env;
17mod error;
18mod explain;
19mod filter;
20mod fix;
21mod format;
22mod install;
23mod instances;
24mod lint;
25mod lsp;
26mod mcp;
27mod migrate;
28mod module_cache;
29mod npmrc;
30mod output;
31mod registry;
32mod roots;
33mod trace;
34mod update;
35mod watch;
36mod write;
37
38use args::{Cli, Command, Format};
39use env::Env;
40pub use error::{CliError, Kind};
41use lint::Teardown;
42pub use lint::lint_to_json;
43use output::emit_error;
44
45pub fn run(args: impl IntoIterator<Item = OsString>) -> u8 {
49 run_with(args, Teardown::Free)
50}
51
52pub fn run_to_exit(args: impl IntoIterator<Item = OsString>) -> u8 {
56 run_with(args, Teardown::Leak)
57}
58
59fn run_with(args: impl IntoIterator<Item = OsString>, teardown: Teardown) -> u8 {
60 let raw: Vec<OsString> = args.into_iter().collect();
61 env::enable_timings();
62 let cli = match Cli::try_parse_from(&raw) {
63 Ok(cli) => cli,
64 Err(error) => return usage(&raw, &error),
65 };
66 match cli.command {
67 None if cli.lint.watch => watch::run(&cli.lint, Env::detect()),
68 None => lint::run(&cli.lint, Env::detect(), teardown),
69 Some(Command::Rules(args)) => commands::rules(&args),
70 Some(Command::Schema) => commands::schema(),
71 Some(Command::Init(args)) => commands::init(&args),
72 Some(Command::Config(args)) => config::run(&args),
73 Some(Command::ExplainUsage(args)) => explain::run(&args),
74 Some(Command::Migrate(args)) => migrate::run(&args),
75 Some(Command::Update(args)) => update::run(&args, Env::detect()),
76 Some(Command::List(args)) => instances::list(&args),
77 Some(Command::Json(args)) => instances::json(&args),
78 Some(Command::Format(args)) => format::run(&args, Env::detect(), teardown),
79 Some(Command::Mcp) => mcp::run(),
80 Some(Command::Lsp) => lsp::run(),
81 }
82}
83
84pub fn install_panic_hook(raw: &[OsString]) {
90 if !wants_json(raw) {
91 return;
92 }
93 let default = std::panic::take_hook();
94 std::panic::set_hook(Box::new(move |info| {
95 default(info);
96 emit_error(
97 &CliError::new(Kind::Internal, format!("internal error: {info}")),
98 Format::Json,
99 );
100 std::process::exit(2);
101 }));
102}
103
104fn usage(raw: &[OsString], error: &clap::Error) -> u8 {
107 if !error.use_stderr() {
108 let _ = error.print();
109 return 0;
110 }
111 if wants_json(raw) {
112 let message = error.to_string();
113 emit_error(&CliError::new(Kind::Usage, message.trim_end()), Format::Json);
114 } else {
115 let _ = error.print();
116 }
117 2
118}
119
120fn wants_json(raw: &[OsString]) -> bool {
123 let args: Vec<&str> = raw.iter().filter_map(|arg| arg.to_str()).collect();
124 let mut values = Vec::new();
125 for (index, arg) in args.iter().enumerate() {
126 if let Some(value) = arg.strip_prefix("--format=") {
127 values.push(value);
128 } else if *arg == "--format" {
129 values.extend(args.get(index + 1));
130 }
131 }
132 values
133 .iter()
134 .find(|value| !value.contains('='))
135 .or_else(|| values.first())
136 .is_some_and(|value| value.split('=').next() == Some("json"))
137}