cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 904af9764976d7546c92bac36448e4ec9f61b75f
parent 410a3a0d8f4109567e171bdf7d550b18582208b5
Author: triesap <tyson@radroots.org>
Date:   Sat, 25 Apr 2026 01:21:31 +0000

runtime: root repo-local workspace config

- resolve repo-local workspace config from the explicit root
- keep interactive workspace config resolution unchanged
- report repo-local config.toml in config show output
- cover explicit-root workspace relay loading in tests

Diffstat:
Msrc/runtime/config.rs | 8++++++++
Msrc/runtime/paths.rs | 26+++++++++++++++++++++++++-
Mtests/runtime_show.rs | 34++++++++++++++++++++++++++++++++++
3 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/src/runtime/config.rs b/src/runtime/config.rs @@ -2194,6 +2194,10 @@ target = "https://rpc.workspace.test/jsonrpc" ) ); assert_eq!( + resolved.paths.workspace_config_path, + PathBuf::from("/workspaces/radroots-cli/.local/radroots/dev/config.toml") + ); + assert_eq!( resolved.paths.app_data_root, PathBuf::from("/workspaces/radroots-cli/.local/radroots/dev/data/apps/cli") ); @@ -2250,6 +2254,10 @@ RADROOTS_CLI_PATHS_REPO_LOCAL_ROOT=.local/radroots/dev resolved.paths.app_data_root, PathBuf::from("/workspaces/radroots-cli/.local/radroots/dev/data/apps/cli") ); + assert_eq!( + resolved.paths.workspace_config_path, + PathBuf::from("/workspaces/radroots-cli/.local/radroots/dev/config.toml") + ); } #[test] diff --git a/src/runtime/paths.rs b/src/runtime/paths.rs @@ -51,6 +51,8 @@ pub(crate) fn resolve_paths( let (profile, profile_label, profile_source) = resolve_cli_path_profile(env, env_file)?; let override_selection = resolve_cli_path_overrides(current_dir.as_path(), env, env_file, profile)?; + let workspace_config_path = + resolve_workspace_config_path(current_dir.as_path(), profile, &override_selection)?; let resolved = resolver .resolve(profile, &override_selection.overrides) .map_err(|err| RuntimeError::Config(format!("resolve Radroots path roots: {err}")))?; @@ -91,7 +93,7 @@ pub(crate) fn resolve_paths( .display() .to_string(), app_config_path: app_paths.config.join(DEFAULT_CONFIG_FILE_NAME), - workspace_config_path: current_dir.join(DEFAULT_WORKSPACE_CONFIG_PATH), + workspace_config_path, app_data_root: app_paths.data, app_logs_root: app_paths.logs, shared_accounts_data_root: shared_accounts_paths.data, @@ -190,6 +192,28 @@ fn normalize_explicit_path_root(current_dir: &Path, value: &str) -> PathBuf { } } +fn resolve_workspace_config_path( + current_dir: &Path, + profile: RadrootsPathProfile, + override_selection: &CliPathOverrideSelection, +) -> Result<PathBuf, RuntimeError> { + match profile { + RadrootsPathProfile::InteractiveUser => Ok(current_dir.join(DEFAULT_WORKSPACE_CONFIG_PATH)), + RadrootsPathProfile::RepoLocal => override_selection + .repo_local_root + .as_ref() + .map(|root| root.join(DEFAULT_CONFIG_FILE_NAME)) + .ok_or_else(|| { + RuntimeError::Config(format!( + "{ENV_CLI_PATHS_REPO_LOCAL_ROOT} must be resolved when {ENV_CLI_PATHS_PROFILE}=repo_local" + )) + }), + _ => Err(RuntimeError::Config( + "cli only supports interactive_user and repo_local path profiles".to_owned(), + )), + } +} + struct PathEnvValueEntry { key: String, value: String, diff --git a/tests/runtime_show.rs b/tests/runtime_show.rs @@ -435,6 +435,10 @@ fn config_show_json_reports_repo_local_paths_when_requested() { .to_string() ); assert_eq!( + json["paths"]["workspace_config_path"], + repo_local_root.join("config.toml").display().to_string() + ); + assert_eq!( json["paths"]["app_data_root"], repo_local_root.join("data/apps/cli").display().to_string() ); @@ -466,6 +470,36 @@ fn config_show_json_reports_repo_local_paths_when_requested() { } #[test] +fn config_show_json_reads_repo_local_workspace_config_from_explicit_root() { + let dir = tempdir().expect("tempdir"); + let repo_local_root = dir.path().join("repo-runtime"); + fs::create_dir_all(&repo_local_root).expect("repo-local root"); + fs::write( + repo_local_root.join("config.toml"), + "[relay]\nurls = [\"wss://relay.repo-local\"]\npublish_policy = \"any\"\n", + ) + .expect("write repo-local workspace config"); + + let output = runtime_show_command_in(dir.path()) + .env("RADROOTS_CLI_PATHS_PROFILE", "repo_local") + .env("RADROOTS_CLI_PATHS_REPO_LOCAL_ROOT", &repo_local_root) + .args(["--json", "config", "show"]) + .output() + .expect("run config show"); + + assert!(output.status.success()); + let json: Value = serde_json::from_slice(output.stdout.as_slice()).expect("json output"); + assert_eq!( + json["paths"]["workspace_config_path"], + repo_local_root.join("config.toml").display().to_string() + ); + assert_eq!(json["config_files"]["workspace_present"], true); + assert_eq!(json["relay"]["count"], 1); + assert_eq!(json["relay"]["urls"][0], "wss://relay.repo-local"); + assert_eq!(json["relay"]["source"], "workspace config ยท local first"); +} + +#[test] fn config_show_json_reflects_environment_configuration() { let dir = tempdir().expect("tempdir"); let output = runtime_show_command_in(dir.path())