Skip to content

Commit ac304d9

Browse files
committed
fix(cli): fix panic when using --config option
Closes #332
1 parent 2201e81 commit ac304d9

2 files changed

Lines changed: 80 additions & 3 deletions

File tree

src/cli.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ use {
77
std::{env, path::PathBuf},
88
};
99

10+
#[cfg(test)]
11+
#[path = "cli_test.rs"]
12+
mod cli_test;
13+
1014
#[derive(Debug)]
1115
pub enum Subcommand {
1216
Fix,
@@ -314,9 +318,31 @@ fn create() -> Command {
314318
}
315319

316320
fn config_option(_command: &str) -> Arg {
317-
Arg::new("config").long("config").value_name("PATH").long_help(cformat!(
318-
r#"Path to a specific config file to use. When set, config file discovery is skipped."#
319-
))
321+
Arg::new("config")
322+
.long("config")
323+
.value_name("PATH")
324+
.value_parser(ValueParser::new(parse_config_path))
325+
.long_help(cformat!(
326+
r#"Path to a specific config file to use. When set, config file discovery is skipped."#
327+
))
328+
}
329+
330+
fn parse_config_path(raw: &str) -> Result<PathBuf, String> {
331+
let path = PathBuf::from(raw);
332+
let resolved = if path.is_absolute() {
333+
path.clone()
334+
} else {
335+
env::current_dir()
336+
.map_err(|e| format!("could not resolve current directory: {e}"))?
337+
.join(&path)
338+
};
339+
if !resolved.exists() {
340+
return Err(format!("file not found: {}", path.display()));
341+
}
342+
if !resolved.is_file() {
343+
return Err(format!("not a file: {}", path.display()));
344+
}
345+
Ok(path)
320346
}
321347

322348
fn dependencies_option(command: &str) -> Arg {

src/cli_test.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use {
2+
crate::cli::Cli,
3+
std::{env, fs, path::PathBuf},
4+
};
5+
6+
fn write_temp_config(name: &str) -> PathBuf {
7+
let path = env::temp_dir().join(format!("syncpack-cli-test-{}-{name}", std::process::id()));
8+
fs::write(&path, "{}").expect("write temp config");
9+
path
10+
}
11+
12+
#[test]
13+
fn config_flag_accepts_existing_file() {
14+
let temp = write_temp_config("ok.json");
15+
let args = ["syncpack", "lint", "--config", temp.to_str().unwrap()]
16+
.iter()
17+
.map(|s| s.to_string())
18+
.collect::<Vec<_>>();
19+
20+
let cli = Cli::parse(&args).expect("--config should parse");
21+
let config_path = cli.config_path.expect("config_path should be Some");
22+
assert!(config_path.is_absolute());
23+
assert_eq!(config_path, temp);
24+
25+
let _ = fs::remove_file(&temp);
26+
}
27+
28+
#[test]
29+
fn config_flag_rejects_missing_file_with_clap_error() {
30+
let args = ["syncpack", "lint", "--config", "definitely-not-a-real-file.ts"]
31+
.iter()
32+
.map(|s| s.to_string())
33+
.collect::<Vec<_>>();
34+
35+
let err = Cli::parse(&args).expect_err("missing file must be rejected");
36+
let msg = format!("{err:?}");
37+
assert!(msg.contains("file not found"), "expected clap-style error, got: {msg}");
38+
}
39+
40+
#[test]
41+
fn config_flag_rejects_directory_with_clap_error() {
42+
let dir = env::temp_dir();
43+
let args = ["syncpack", "lint", "--config", dir.to_str().unwrap()]
44+
.iter()
45+
.map(|s| s.to_string())
46+
.collect::<Vec<_>>();
47+
48+
let err = Cli::parse(&args).expect_err("directory must be rejected");
49+
let msg = format!("{err:?}");
50+
assert!(msg.contains("not a file"), "expected clap-style error, got: {msg}");
51+
}

0 commit comments

Comments
 (0)