fix(cli): idx config prints JSON only when JSON is asked for

Auto output stays plain text when piped, as in 0.3.2, so scripts that
read $(idx config path) or $(idx config get KEY) keep working; -o json,
IDX_OUTPUT=json, or general.output = "json" print JSON. The config
goldens return to their 0.3.2 content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
hermes 2026-10-18 02:11:31 +00:00
commit 57eccc5843
10 changed files with 62 additions and 18 deletions

View file

@ -1,7 +1,7 @@
use clap::{Args, Subcommand};
use crate::config::{config_path, ensure_default_config, get_config_value, set_config_value};
use crate::output::{OutputFormat, json};
use crate::output::{OutputSetting, json};
use idx_core::error::IdxError;
#[derive(Debug, Args)]
@ -23,10 +23,11 @@ pub enum ConfigSubcommand {
Path,
}
pub fn handle(cmd: &ConfigCmd, format: &OutputFormat) -> Result<(), IdxError> {
// Each result as plain text, or one JSON document with -o json.
pub fn handle(cmd: &ConfigCmd, setting: OutputSetting) -> Result<(), IdxError> {
// Plain text, as scripts read `$(idx config path)`, unless JSON is asked
// for (-o json, IDX_OUTPUT, or general.output); auto stays text when piped.
let print = |text: String, document: serde_json::Value| {
if format.is_json() {
if matches!(setting, OutputSetting::Json | OutputSetting::Jsonl) {
json::print_json(&document)
} else {
println!("{}", crate::output::text::terminal_safe(&text));

View file

@ -14,6 +14,8 @@ use idx_core::error::IdxError;
pub struct IdxConfig {
pub core: CoreConfig,
pub output: OutputFormat,
/// The setting `output` came from, before auto was resolved.
pub output_setting: OutputSetting,
pub no_color: bool,
}
@ -32,7 +34,7 @@ impl IdxConfig {
pub fn load_with_cli(cli: &Cli) -> Result<Self, IdxError> {
let mut core = CoreConfig::load()?;
core.verbose = cli.verbose > 0;
let mut output = OutputSetting::Auto.resolve(stdout_is_terminal());
let mut setting = OutputSetting::Auto;
let mut no_color = false;
let path = config_path()?;
@ -41,8 +43,8 @@ impl IdxConfig {
let parsed: FileConfig =
toml::from_str(&raw).map_err(|e| IdxError::ConfigError(e.to_string()))?;
if let Some(general) = parsed.general {
if let Some(setting) = general.output {
output = setting.resolve(stdout_is_terminal());
if let Some(file_setting) = general.output {
setting = file_setting;
}
if let Some(color) = general.color {
no_color = !color;
@ -51,25 +53,25 @@ impl IdxConfig {
}
if let Ok(value) = std::env::var("IDX_OUTPUT") {
let setting = OutputSetting::parse(&value).ok_or_else(|| {
setting = OutputSetting::parse(&value).ok_or_else(|| {
IdxError::ConfigError(format!(
"invalid IDX_OUTPUT value: '{value}', expected 'auto', 'table', 'json', or 'jsonl'"
))
})?;
output = setting.resolve(stdout_is_terminal());
}
if let Ok(value) = std::env::var("IDX_NO_COLOR") {
no_color = value == "1" || value.eq_ignore_ascii_case("true");
}
if let Some(setting) = cli.output {
output = setting.resolve(stdout_is_terminal());
if let Some(cli_setting) = cli.output {
setting = cli_setting;
}
no_color = no_color || cli.no_color || !stdout_is_terminal();
Ok(Self {
core,
output,
output: setting.resolve(stdout_is_terminal()),
output_setting: setting,
no_color,
})
}

View file

@ -62,7 +62,7 @@ fn run() -> Result<(), IdxError> {
}
}
Commands::Config(cfg) => {
if let Err(err) = cli::config::handle(cfg, &config.output) {
if let Err(err) = cli::config::handle(cfg, config.output_setting) {
emit_error(&err, &config.output);
return Err(err);
}

View file

@ -980,6 +980,43 @@ fn config_set_and_get_provider_round_trip() {
.stdout(predicate::str::contains("msn"));
}
#[test]
fn config_prints_json_only_when_json_is_asked_for() {
// Piped with auto output, scripts read plain text: `$(idx config path)`.
let root = test_env_dir("config-output-format");
bin_with_root(&root)
.args(["config", "set", "general.provider", "msn"])
.assert()
.success()
.stdout("ok\n");
bin_with_root(&root)
.args(["config", "get", "general.provider"])
.assert()
.success()
.stdout("msn\n");
let flag = run_success_json(bin_with_root(&root).args([
"-o",
"json",
"config",
"get",
"general.provider",
]));
assert_eq!(
flag,
serde_json::json!({"key": "general.provider", "value": "msn"})
);
let env = run_success_json(
bin_with_root(&root)
.env("IDX_OUTPUT", "json")
.args(["config", "path"]),
);
assert!(
env["path"].as_str().unwrap().ends_with("config.toml"),
"{env}"
);
}
#[test]
fn config_set_and_get_ownership_db_path_round_trip() {
let root = test_env_dir("config-ownership-db-path");

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"key":"general.provider","value":"msn"}
msn
--- stderr

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"path":"<ROOT>/config/idx/config.toml"}
<ROOT>/config/idx/config.toml
--- stderr

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"path":"<ROOT>/config/idx/config.toml"}
<ROOT>/config/idx/config.toml
--- stderr

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"key":"general.output","ok":true,"value":"jsonl"}
ok
--- stderr

View file

@ -1,4 +1,4 @@
exit: 0
--- stdout
{"key":"general.provider","ok":true,"value":"yahoo"}
ok
--- stderr