From 147295d042280fc8d57c60f495ed2f28188ccc2d Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 19:07:57 -0400 Subject: [PATCH 1/7] refactor: introduce Profile struct --- src/commands/mono/resolve.rs | 4 ++-- src/config/types.rs | 8 +++++++- src/profile/crud.rs | 26 ++++++++++++++++++++------ src/profile/display.rs | 24 ++++++++++++++++-------- tests/commands/mono/resolve.rs | 15 ++++++++++++--- tests/profile/crud.rs | 8 ++++---- 6 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/commands/mono/resolve.rs b/src/commands/mono/resolve.rs index ad1d9db..c69203d 100644 --- a/src/commands/mono/resolve.rs +++ b/src/commands/mono/resolve.rs @@ -37,10 +37,10 @@ pub fn resolve_repos_for_mono( list_profiles(config, io); format!("Profile '{profile_name}' not found") })?; - if profile_repos.is_empty() { + if profile_repos.test_repo.is_none() && profile_repos.deps.is_empty() { return Err(format!("Profile '{profile_name}' has no repositories")); } - Ok(profile_repos.clone()) + Ok(profile_repos.deps.clone()) } else if let Some(r) = &args.mono.repos { Ok(r.clone()) } else { diff --git a/src/config/types.rs b/src/config/types.rs index e3a6057..2829d29 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -125,6 +125,12 @@ impl From<&ResolvedArgs> for ConfigEntry { } } +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct Profile { + pub test_repo: Option, + pub deps: Vec, +} + /// Top-level configuration structure. #[derive(Serialize, Deserialize, Default)] pub struct SetupConfig { @@ -133,7 +139,7 @@ pub struct SetupConfig { pub configs: HashMap, /// Named profile entries mapping profile names to repository lists. #[serde(default)] - pub profiles: HashMap>, + pub profiles: HashMap, /// Path to the config file this was loaded from, if any. #[serde(skip)] pub path: Option, diff --git a/src/profile/crud.rs b/src/profile/crud.rs index bbfefd5..bfb513c 100644 --- a/src/profile/crud.rs +++ b/src/profile/crud.rs @@ -1,13 +1,19 @@ use crate::{ - config::{persist_or_dry_run, SetupConfig}, + config::{persist_or_dry_run, types::Profile, SetupConfig}, ctx::{IoCtx, RunFlags}, profile::print_profile_details, prompts::confirm_abort, }; /// Inserts or overwrites a named profile. -pub fn insert_profile(config: &mut SetupConfig, name: &str, repos: Vec) { - config.profiles.insert(name.to_string(), repos); +pub fn insert_profile(config: &mut SetupConfig, name: &str, deps: Vec) { + config.profiles.insert( + name.to_string(), + Profile { + test_repo: None, + deps, + }, + ); } /// Removes a named profile. Returns `true` if it existed. @@ -60,7 +66,15 @@ pub fn add_profile( writeln!(io.output, " Configuration saved to: {}", path.display()).ok(); }, )?; - print_profile_details(io.output, "Profile details:", "Repositories", &repos); + print_profile_details( + io.output, + "Profile details:", + "Repositories", + &Profile { + test_repo: None, + deps: repos.clone(), + }, + ); writeln!( io.output, " Usage: star-setup username/test-repo --profile {name}" @@ -79,7 +93,7 @@ pub fn remove_profile( io: &mut IoCtx<'_>, flags: RunFlags, ) -> Result<(), String> { - let repos = match config.profiles.get(name) { + let profile = match config.profiles.get(name) { None => { writeln!(io.output, " Warning: Profile '{name}' not found.").ok(); return Ok(()); @@ -91,7 +105,7 @@ pub fn remove_profile( io.output, &format!("Profile '{name}'"), "Repositories", - &repos, + &profile, ); if !confirm_abort( diff --git a/src/profile/display.rs b/src/profile/display.rs index 02231de..00dbec9 100644 --- a/src/profile/display.rs +++ b/src/profile/display.rs @@ -1,16 +1,25 @@ -use crate::{config::types::SetupConfig, ctx::IoCtx}; +use crate::{ + config::types::{Profile, SetupConfig}, + ctx::IoCtx, +}; use std::io::Write; pub fn print_profile_details( output: &mut (impl Write + ?Sized), title: &str, label: &str, - repos: &[String], + profile: &Profile, ) { + let Profile { test_repo, deps } = profile; writeln!(output, " {title}").ok(); - writeln!(output, " {label}: {}", repos.len()).ok(); - for repo in repos { - writeln!(output, " - {repo}").ok(); + if let Some(t) = test_repo { + writeln!(output, " {t}").ok(); + } + if !deps.is_empty() { + writeln!(output, " {label}: {}", deps.len()).ok(); + for d in deps { + writeln!(output, " - {d}").ok(); + } } } @@ -24,10 +33,9 @@ pub fn list_profiles(config: &SetupConfig, io: &mut IoCtx<'_>) { .ok(); return; } - writeln!(io.output, "Configured profiles:\n").ok(); - for (name, repos) in &config.profiles { - print_profile_details(io.output, name, "Repositories", repos); + for (name, profile) in &config.profiles { + print_profile_details(io.output, name, "Repositories", profile); writeln!(io.output).ok(); } } diff --git a/tests/commands/mono/resolve.rs b/tests/commands/mono/resolve.rs index 6b12c14..5cba42b 100644 --- a/tests/commands/mono/resolve.rs +++ b/tests/commands/mono/resolve.rs @@ -2,7 +2,7 @@ use crate::common::{default_resolved, with_ctx, with_io, MockRunner}; use star_setup::{ cli::BuildSystem, commands::{generate_mono_config, resolve_repos_for_mono, resolve_test_repo}, - config::SetupConfig, + config::{types::Profile, SetupConfig}, }; use std::{ fs::{create_dir_all, read_to_string, write}, @@ -54,7 +54,13 @@ fn test_resolve_test_repo_errors() { #[test] fn test_resolve_repos_for_mono_empty_profile_errors() { let mut config = SetupConfig::new(); - config.profiles.insert("emptyprofile".to_string(), vec![]); + config.profiles.insert( + "emptyprofile".to_string(), + Profile { + test_repo: None, + deps: vec![], + }, + ); let mut args = default_resolved(); args.mono.profile = Some("emptyprofile".to_string()); @@ -70,7 +76,10 @@ fn test_resolve_repos_for_mono_with_profile() { let mut config = SetupConfig::new(); config.profiles.insert( "myprofile".to_string(), - vec!["user/lib1".to_string(), "user/lib2".to_string()], + Profile { + test_repo: None, + deps: vec!["user/lib1".to_string(), "user/lib2".to_string()], + }, ); let mut args = default_resolved(); args.mono.profile = Some("myprofile".to_string()); diff --git a/tests/profile/crud.rs b/tests/profile/crud.rs index 3471e91..6792538 100644 --- a/tests/profile/crud.rs +++ b/tests/profile/crud.rs @@ -45,7 +45,7 @@ fn test_save_and_load_profile_roundtrip() { let loaded = load_config(&[path], false, false, &mut io.output); assert!(loaded.profiles.contains_key("myprofile")); assert_eq!( - loaded.profiles["myprofile"], + loaded.profiles["myprofile"].deps, vec!["user/repo1", "user/repo2"] ); }); @@ -103,7 +103,7 @@ fn test_add_profile_overwrites_existing() { let args = vec!["myprofile".to_string(), "new/repo".to_string()]; add_profile(&mut config, &args, true, io, make_flags()).unwrap(); - assert_eq!(config.profiles["myprofile"], vec!["new/repo"]); + assert_eq!(config.profiles["myprofile"].deps, vec!["new/repo"]); }); } @@ -120,7 +120,7 @@ fn test_add_profile_multiple_repos() { "user/repo3".to_string(), ]; add_profile(&mut config, &args, true, io, make_flags()).unwrap(); - assert_eq!(config.profiles["myprofile"].len(), 3); + assert_eq!(config.profiles["myprofile"].deps.len(), 3); }); } @@ -134,7 +134,7 @@ fn test_add_profile_aborts_when_exists_and_not_confirmed() { let args = vec!["myprofile".to_string(), "new/repo".to_string()]; add_profile(&mut config, &args, false, io, make_flags()).unwrap(); - assert_eq!(config.profiles["myprofile"], vec!["old/repo"]); + assert_eq!(config.profiles["myprofile"].deps, vec!["old/repo"]); }); } From 28ba0daac563a2fe77e3b85179a2385bda1680f5 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 19:38:29 -0400 Subject: [PATCH 2/7] chore: rename SetupConfig to Config --- src/cli/args.rs | 4 ++-- src/cli/resolve.rs | 4 ++-- src/commands/handlers.rs | 8 +++---- src/commands/mono/mode.rs | 4 ++-- src/commands/mono/resolve.rs | 4 ++-- src/config/crud.rs | 14 +++++------ src/config/display.rs | 4 ++-- src/config/io.rs | 16 ++++++------- src/config/mod.rs | 2 +- src/config/types.rs | 44 +++++++++++++++++----------------- src/profile/crud.rs | 12 +++++----- src/profile/display.rs | 4 ++-- tests/cli/resolve.rs | 14 +++++------ tests/commands/mono/mode.rs | 12 ++++------ tests/commands/mono/resolve.rs | 12 +++++----- tests/common/args.rs | 10 ++++---- tests/config/crud.rs | 26 ++++++++++---------- tests/config/io.rs | 10 ++++---- tests/profile/crud.rs | 32 ++++++++++++------------- tests/profile/display.rs | 6 ++--- 20 files changed, 119 insertions(+), 123 deletions(-) diff --git a/src/cli/args.rs b/src/cli/args.rs index 20208cb..a52987e 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -3,7 +3,7 @@ use crate::{ resolve_with_config, BuildFlags, ConfigCommand, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ProfileCommand, ResolvedArgs, WorkspaceCommand, }, - config::SetupConfig, + config::Config, }; use clap::{Parser, Subcommand}; @@ -57,7 +57,7 @@ impl Args { /// Parses CLI arguments and resolves them against the provided `SetupConfig`. /// # Errors /// Returns an error if the named config does not exist in the loaded `SetupConfig`. - pub fn parse_with_config(config: &SetupConfig) -> Result { + pub fn parse_with_config(config: &Config) -> Result { resolve_with_config(Args::parse(), config) } } diff --git a/src/cli/resolve.rs b/src/cli/resolve.rs index 45b2553..67c925d 100644 --- a/src/cli/resolve.rs +++ b/src/cli/resolve.rs @@ -3,7 +3,7 @@ use crate::{ Args, BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags, }, - config::{ConfigEntry, SetupConfig}, + config::{Config, ConfigEntry}, ctx::RunFlags, }; @@ -115,7 +115,7 @@ fn resolve_mono_flags(mono: MonoRepoFlags, default: Option<&ConfigEntry>) -> Res /// Resolves raw `Args` into `ResolvedArgs` by applying config defaults and CLI overrides. /// # Errors /// Returns an error if the named config does not exist in the provided `SetupConfig`. -pub fn resolve_with_config(args: Args, config: &SetupConfig) -> Result { +pub fn resolve_with_config(args: Args, config: &Config) -> Result { let config_name = args.config_name.as_deref().unwrap_or("default"); let default = config.configs.get(config_name); diff --git a/src/commands/handlers.rs b/src/commands/handlers.rs index d0941b8..0fe5e13 100644 --- a/src/commands/handlers.rs +++ b/src/commands/handlers.rs @@ -3,9 +3,7 @@ use crate::{ ConfigAction, ProfileAction, WorkspaceAction, WorkspaceAction::{Clean, Status, Update}, }, - config::{ - add_config, create_default_config, list_configs, remove_config, ConfigEntry, SetupConfig, - }, + config::{add_config, create_default_config, list_configs, remove_config, Config, ConfigEntry}, ctx::{with_runner, IoCtx, RunFlags}, profile::{add_profile, list_profiles, remove_profile}, workspace::resolve_workspace, @@ -17,7 +15,7 @@ use std::{error::Error, iter::once, path::PathBuf}; /// Returns an error if configuration initializing, addition, or removal fails. pub fn handle_config_cmd( action: ConfigAction, - config: &mut SetupConfig, + config: &mut Config, config_path: PathBuf, yes: bool, io: &mut IoCtx, @@ -46,7 +44,7 @@ pub fn handle_config_cmd( /// Returns an error if adding or removing profiles encounters an I/O or validation failure. pub fn handle_profile_cmd( action: ProfileAction, - config: &mut SetupConfig, + config: &mut Config, yes: bool, io: &mut IoCtx, flags: RunFlags, diff --git a/src/commands/mono/mode.rs b/src/commands/mono/mode.rs index 2edf795..a413545 100644 --- a/src/commands/mono/mode.rs +++ b/src/commands/mono/mode.rs @@ -8,7 +8,7 @@ use crate::{ }, prepare_build_dir, print_mode_header, resolve_repos_for_mono, resolve_test_repo, ModeHeader, }, - config::SetupConfig, + config::Config, ctx::RunCtx, repository::{clone_repos, repo_dir_name}, utils::{detect_or_dry_run, dry_run_or_do}, @@ -24,7 +24,7 @@ use std::{ /// Returns an error if no repository is specified, directory creation fails, or any build system command fails. pub fn mono_repo_mode( args: &ResolvedArgs, - config: &SetupConfig, + config: &Config, base_dir: &Path, ctx: &mut RunCtx<'_, '_>, ) -> Result<(), String> { diff --git a/src/commands/mono/resolve.rs b/src/commands/mono/resolve.rs index c69203d..77d786b 100644 --- a/src/commands/mono/resolve.rs +++ b/src/commands/mono/resolve.rs @@ -1,4 +1,4 @@ -use crate::{cli::ResolvedArgs, config::SetupConfig, ctx::IoCtx, profile::list_profiles}; +use crate::{cli::ResolvedArgs, config::Config, ctx::IoCtx, profile::list_profiles}; /// Normalizes a repository input to `username/repo` format. /// # Errors @@ -29,7 +29,7 @@ pub fn resolve_test_repo(repo_input: &str) -> Result { /// Returns an error if the specified profile does not exist, or has no repositories. pub fn resolve_repos_for_mono( args: &ResolvedArgs, - config: &SetupConfig, + config: &Config, io: &mut IoCtx<'_>, ) -> Result, String> { if let Some(profile_name) = &args.mono.profile { diff --git a/src/config/crud.rs b/src/config/crud.rs index 1570091..47a6ebc 100644 --- a/src/config/crud.rs +++ b/src/config/crud.rs @@ -1,5 +1,5 @@ use crate::{ - config::{format_entry, persist_or_dry_run, save_config, ConfigEntry, SetupConfig}, + config::{format_entry, persist_or_dry_run, save_config, Config, ConfigEntry}, ctx::{IoCtx, RunFlags}, prompts::confirm_abort, }; @@ -7,18 +7,18 @@ use dunce::canonicalize; use std::path::PathBuf; /// Inserts or overwrites a named configuration entry. -pub fn insert_config(config: &mut SetupConfig, name: &str, entry: ConfigEntry) { +pub fn insert_config(config: &mut Config, name: &str, entry: ConfigEntry) { config.configs.insert(name.to_string(), entry); } /// Removes a named configuration entry. Returns `true` if it existed. -pub fn remove_config_entry(config: &mut SetupConfig, name: &str) -> bool { +pub fn remove_config_entry(config: &mut Config, name: &str) -> bool { config.configs.remove(name).is_some() } /// Returns `true` if a configuration with the given name exists. #[must_use] -pub fn has_config(config: &SetupConfig, name: &str) -> bool { +pub fn has_config(config: &Config, name: &str) -> bool { config.configs.contains_key(name) } @@ -51,7 +51,7 @@ pub fn create_default_config( ) .ok(); } else { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); config .configs @@ -79,7 +79,7 @@ pub fn create_default_config( /// # Errors /// Returns an error if saving the config file fails. pub fn add_config( - config: &mut SetupConfig, + config: &mut Config, name: &str, entry: ConfigEntry, yes: bool, @@ -120,7 +120,7 @@ pub fn add_config( /// # Errors /// Returns an error if saving the config file fails. pub fn remove_config( - config: &mut SetupConfig, + config: &mut Config, name: &str, yes: bool, io: &mut IoCtx<'_>, diff --git a/src/config/display.rs b/src/config/display.rs index 7d0e376..c66d794 100644 --- a/src/config/display.rs +++ b/src/config/display.rs @@ -1,5 +1,5 @@ use crate::{ - config::{ConfigEntry, SetupConfig}, + config::{Config, ConfigEntry}, ctx::IoCtx, }; use std::fmt::Write as FmtWrite; @@ -44,7 +44,7 @@ pub fn format_entry(e: &ConfigEntry) -> String { } /// Lists all saved configuration entries. -pub fn list_configs(config: &SetupConfig, io: &mut IoCtx<'_>) { +pub fn list_configs(config: &Config, io: &mut IoCtx<'_>) { if config.configs.is_empty() { writeln!(io.output, " No configurations created.").ok(); writeln!( diff --git a/src/config/io.rs b/src/config/io.rs index 534062f..3f8faac 100644 --- a/src/config/io.rs +++ b/src/config/io.rs @@ -1,5 +1,5 @@ use crate::{ - config::SetupConfig, + config::Config, ctx::{IoCtx, RunFlags}, }; use serde_json::{from_str, to_string_pretty}; @@ -37,7 +37,7 @@ pub fn load_config( verbose: bool, timing: bool, output: &mut impl Write, -) -> SetupConfig { +) -> Config { if verbose { writeln!(output, "Loading config").ok(); } @@ -53,7 +53,7 @@ pub fn load_config( } let result = crate::time!(timing, output, "Read config", { match read_to_string(path) { - Ok(contents) => match from_str::(&contents) { + Ok(contents) => match from_str::(&contents) { Ok(mut config) => { config.path = Some(path.clone()); if verbose { @@ -93,14 +93,14 @@ pub fn load_config( if verbose || timing { writeln!(output).ok(); } - SetupConfig::new() + Config::new() } /// Serializes the configuration and writes it to the path stored in `config.path`. /// # Errors /// Returns an error if serialization fails or if the file cannot be written. pub fn save_config( - config: &mut SetupConfig, + config: &mut Config, timing: bool, output: &mut impl Write, ) -> Result { @@ -128,12 +128,12 @@ pub fn save_config( /// # Errors /// Returns an error if saving the config file fails. pub fn persist_or_dry_run( - config: &mut SetupConfig, + config: &mut Config, flags: RunFlags, io: &mut IoCtx<'_>, would_msg: &str, - mutate: impl FnOnce(&mut SetupConfig), - on_saved: impl FnOnce(&SetupConfig, &Path, &mut IoCtx<'_>), + mutate: impl FnOnce(&mut Config), + on_saved: impl FnOnce(&Config, &Path, &mut IoCtx<'_>), ) -> Result<(), String> { if flags.dry_run { writeln!(io.output, " {would_msg}").ok(); diff --git a/src/config/mod.rs b/src/config/mod.rs index b74b6a5..ca7cc2d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -7,4 +7,4 @@ pub use display::{format_entry, list_configs}; pub mod io; pub use io::{config_locations, load_config, persist_or_dry_run, save_config}; pub mod types; -pub use types::{ConfigEntry, SetupConfig}; +pub use types::{Config, ConfigEntry}; diff --git a/src/config/types.rs b/src/config/types.rs index 2829d29..f3632f8 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -4,6 +4,28 @@ use crate::cli::{ use serde::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; +/// Top-level configuration structure. +#[derive(Serialize, Deserialize, Default)] +pub struct Config { + /// Named configuration entries. + #[serde(default)] + pub configs: HashMap, + /// Named profile entries mapping profile names to repository lists. + #[serde(default)] + pub profiles: HashMap, + /// Path to the config file this was loaded from, if any. + #[serde(skip)] + pub path: Option, +} + +impl Config { + /// Creates a new empty `Config`. + #[must_use] + pub fn new() -> Self { + Self::default() + } +} + /// Represents a single named configuration entry. #[allow(clippy::struct_excessive_bools)] #[derive(Serialize, Deserialize)] @@ -130,25 +152,3 @@ pub struct Profile { pub test_repo: Option, pub deps: Vec, } - -/// Top-level configuration structure. -#[derive(Serialize, Deserialize, Default)] -pub struct SetupConfig { - /// Named configuration entries. - #[serde(default)] - pub configs: HashMap, - /// Named profile entries mapping profile names to repository lists. - #[serde(default)] - pub profiles: HashMap, - /// Path to the config file this was loaded from, if any. - #[serde(skip)] - pub path: Option, -} - -impl SetupConfig { - /// Creates a new empty `SetupConfig`. - #[must_use] - pub fn new() -> Self { - Self::default() - } -} diff --git a/src/profile/crud.rs b/src/profile/crud.rs index bfb513c..439a72a 100644 --- a/src/profile/crud.rs +++ b/src/profile/crud.rs @@ -1,12 +1,12 @@ use crate::{ - config::{persist_or_dry_run, types::Profile, SetupConfig}, + config::{persist_or_dry_run, types::Profile, Config}, ctx::{IoCtx, RunFlags}, profile::print_profile_details, prompts::confirm_abort, }; /// Inserts or overwrites a named profile. -pub fn insert_profile(config: &mut SetupConfig, name: &str, deps: Vec) { +pub fn insert_profile(config: &mut Config, name: &str, deps: Vec) { config.profiles.insert( name.to_string(), Profile { @@ -17,13 +17,13 @@ pub fn insert_profile(config: &mut SetupConfig, name: &str, deps: Vec) { } /// Removes a named profile. Returns `true` if it existed. -pub fn remove_profile_entry(config: &mut SetupConfig, name: &str) -> bool { +pub fn remove_profile_entry(config: &mut Config, name: &str) -> bool { config.profiles.remove(name).is_some() } /// Returns `true` if a profile with the given name exists. #[must_use] -pub fn has_profile(config: &SetupConfig, name: &str) -> bool { +pub fn has_profile(config: &Config, name: &str) -> bool { config.profiles.contains_key(name) } @@ -32,7 +32,7 @@ pub fn has_profile(config: &SetupConfig, name: &str) -> bool { /// # Errors /// Returns an error if fewer than two arguments are provided or if saving fails. pub fn add_profile( - config: &mut SetupConfig, + config: &mut Config, args: &[String], yes: bool, io: &mut IoCtx<'_>, @@ -87,7 +87,7 @@ pub fn add_profile( /// # Errors /// Returns an error if saving the config file fails. pub fn remove_profile( - config: &mut SetupConfig, + config: &mut Config, name: &str, yes: bool, io: &mut IoCtx<'_>, diff --git a/src/profile/display.rs b/src/profile/display.rs index 00dbec9..baad993 100644 --- a/src/profile/display.rs +++ b/src/profile/display.rs @@ -1,5 +1,5 @@ use crate::{ - config::types::{Profile, SetupConfig}, + config::types::{Config, Profile}, ctx::IoCtx, }; use std::io::Write; @@ -24,7 +24,7 @@ pub fn print_profile_details( } /// Lists all configured profiles. -pub fn list_profiles(config: &SetupConfig, io: &mut IoCtx<'_>) { +pub fn list_profiles(config: &Config, io: &mut IoCtx<'_>) { if config.profiles.is_empty() { writeln!( io.output, diff --git a/tests/cli/resolve.rs b/tests/cli/resolve.rs index 0db2577..7e8a74e 100644 --- a/tests/cli/resolve.rs +++ b/tests/cli/resolve.rs @@ -1,12 +1,12 @@ use crate::common::default_args; use star_setup::{ cli::{resolve_bool, resolve_with_config, BuildType}, - config::{ConfigEntry, SetupConfig}, + config::{Config, ConfigEntry}, }; /// Helper to quickly build a `SetupConfig` with a populated profile entry. -fn config_with_entry(name: &str, entry: ConfigEntry) -> SetupConfig { - let mut config = SetupConfig::new(); +fn config_with_entry(name: &str, entry: ConfigEntry) -> Config { + let mut config = Config::new(); config.configs.insert(name.to_string(), entry); config } @@ -80,7 +80,7 @@ fn test_resolve_bool() { /* ===== RESOLVE_WITH_CONFIG ===== */ #[test] fn test_resolve_with_config_defaults_when_no_config() { - let config = SetupConfig::new(); + let config = Config::new(); let resolved = resolve_with_config(default_args(), &config).unwrap(); assert!(!resolved.connection.ssh); assert_eq!(resolved.build.build_type, BuildType::Debug); @@ -130,7 +130,7 @@ fn test_resolve_with_config_cli_overrides_config() { #[test] fn test_resolve_with_config_errors_on_missing_config_name() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_args(); args.config_name = Some("nonexistent".to_string()); @@ -139,7 +139,7 @@ fn test_resolve_with_config_errors_on_missing_config_name() { #[test] fn test_resolve_with_config_mono_repo_from_repos() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_args(); args.mono.repos = Some(vec!["user/lib1".to_string()]); @@ -149,7 +149,7 @@ fn test_resolve_with_config_mono_repo_from_repos() { #[test] fn test_resolve_with_config_mono_repo_from_profile() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_args(); args.mono.profile = Some("myprofile".to_string()); diff --git a/tests/commands/mono/mode.rs b/tests/commands/mono/mode.rs index e626f4f..8aac778 100644 --- a/tests/commands/mono/mode.rs +++ b/tests/commands/mono/mode.rs @@ -1,7 +1,5 @@ use crate::common::{default_resolved_mono, with_ctx, with_ctx_runner, MockRunner}; -use star_setup::{ - cli::BuildSystem, commands::mono_repo_mode, config::SetupConfig, ctx::DryRunRunner, -}; +use star_setup::{cli::BuildSystem, commands::mono_repo_mode, config::Config, ctx::DryRunRunner}; use std::{ fs::{create_dir_all, read_dir, write}, path::Path, @@ -23,7 +21,7 @@ fn test_mono_repo_mode_clones_and_configures() { make_cmake_repo(&repos_path, "user-lib1"); make_cmake_repo(&repos_path, "user-test-repo"); - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); }); let out = String::from_utf8(output).unwrap(); @@ -39,7 +37,7 @@ fn test_mono_repo_mode_dry_run_makes_no_fs_changes() { with_ctx(DryRunRunner, |tmp_path, ctx| { ctx.flags.dry_run = true; - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); assert!(read_dir(tmp_path).unwrap().next().is_none()); }); @@ -56,7 +54,7 @@ fn test_mono_repo_mode_dry_run_with_build_system_makes_no_fs_changes() { with_ctx(DryRunRunner, |tmp_path, ctx| { ctx.flags.dry_run = true; - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); assert!( read_dir(tmp_path).unwrap().next().is_none(), @@ -77,7 +75,7 @@ fn test_mono_repo_mode_with_build_system_flag() { make_cmake_repo(&repos_path, "user-lib1"); make_cmake_repo(&repos_path, "user-test-repo"); - mono_repo_mode(&args, &SetupConfig::new(), tmp_path, ctx).unwrap(); + mono_repo_mode(&args, &Config::new(), tmp_path, ctx).unwrap(); }); assert!(runner.calls.iter().any(|(cmd, _)| cmd[0] == "cmake")); diff --git a/tests/commands/mono/resolve.rs b/tests/commands/mono/resolve.rs index 5cba42b..3b3f180 100644 --- a/tests/commands/mono/resolve.rs +++ b/tests/commands/mono/resolve.rs @@ -2,7 +2,7 @@ use crate::common::{default_resolved, with_ctx, with_io, MockRunner}; use star_setup::{ cli::BuildSystem, commands::{generate_mono_config, resolve_repos_for_mono, resolve_test_repo}, - config::{types::Profile, SetupConfig}, + config::{types::Profile, Config}, }; use std::{ fs::{create_dir_all, read_to_string, write}, @@ -53,7 +53,7 @@ fn test_resolve_test_repo_errors() { /* ===== RESOLVE_REPOS_FOR_MONO ===== */ #[test] fn test_resolve_repos_for_mono_empty_profile_errors() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.profiles.insert( "emptyprofile".to_string(), Profile { @@ -73,7 +73,7 @@ fn test_resolve_repos_for_mono_empty_profile_errors() { #[test] fn test_resolve_repos_for_mono_with_profile() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.profiles.insert( "myprofile".to_string(), Profile { @@ -93,7 +93,7 @@ fn test_resolve_repos_for_mono_with_profile() { #[test] fn test_resolve_repos_for_mono_with_explicit_repos() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_resolved(); args.mono.repos = Some(vec!["user/lib1".to_string(), "user/lib2".to_string()]); @@ -106,7 +106,7 @@ fn test_resolve_repos_for_mono_with_explicit_repos() { #[test] fn test_resolve_repos_for_mono_no_repos_or_profile_errors() { - let config = SetupConfig::new(); + let config = Config::new(); let args = default_resolved(); with_io(|io| { @@ -120,7 +120,7 @@ fn test_resolve_repos_for_mono_no_repos_or_profile_errors() { #[test] fn test_resolve_repos_for_mono_profile_not_found_errors() { - let config = SetupConfig::new(); + let config = Config::new(); let mut args = default_resolved(); args.mono.profile = Some("nonexistent".to_string()); diff --git a/tests/common/args.rs b/tests/common/args.rs index c8152a0..5c6756b 100644 --- a/tests/common/args.rs +++ b/tests/common/args.rs @@ -5,7 +5,7 @@ use star_setup::{ resolve_with_config, Args, BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, }, - config::SetupConfig, + config::Config, }; pub fn default_args() -> Args { @@ -54,18 +54,18 @@ pub fn default_resolved() -> ResolvedArgs { let mut args = default_args(); args.repo = Some("user/repo".to_string()); args.build.no_build = true; - resolve_with_config(args, &SetupConfig::new()).unwrap() + resolve_with_config(args, &Config::new()).unwrap() } pub fn default_resolved_with_no_build(no_build: bool) -> ResolvedArgs { let mut args = default_args(); args.repo = Some("user/repo".to_string()); args.build.no_build = no_build; - resolve_with_config(args, &SetupConfig::new()).unwrap() + resolve_with_config(args, &Config::new()).unwrap() } pub fn default_resolved_interactive() -> ResolvedArgs { - resolve_with_config(default_args(), &SetupConfig::new()).unwrap() + resolve_with_config(default_args(), &Config::new()).unwrap() } pub fn default_resolved_mono(repos: Vec) -> ResolvedArgs { @@ -75,5 +75,5 @@ pub fn default_resolved_mono(repos: Vec) -> ResolvedArgs { args.build.no_build = true; args.mono.mono_repo = true; args.mono.repos = Some(repos); - resolve_with_config(args, &SetupConfig::new()).unwrap() + resolve_with_config(args, &Config::new()).unwrap() } diff --git a/tests/config/crud.rs b/tests/config/crud.rs index 48d8fe0..2e43fc3 100644 --- a/tests/config/crud.rs +++ b/tests/config/crud.rs @@ -1,7 +1,7 @@ use crate::common::{make_flags, with_io_dir, with_io_input_output, with_io_output}; use star_setup::config::{ add_config, create_default_config, has_config, insert_config, list_configs, remove_config, - remove_config_entry, save_config, ConfigEntry, SetupConfig, + remove_config_entry, save_config, Config, ConfigEntry, }; use std::fs::{read_to_string, write}; use tempfile::TempDir; @@ -9,7 +9,7 @@ use tempfile::TempDir; /* ===== HAS_CONFIG ===== */ #[test] fn test_has_config_false() { - let config = SetupConfig::new(); + let config = Config::new(); assert!(!has_config(&config, "nonexistent")); } @@ -17,7 +17,7 @@ fn test_has_config_false() { fn test_add_config_inserts_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); add_config( @@ -39,7 +39,7 @@ fn test_add_config_inserts_and_saves() { fn test_add_config_aborts_when_exists_and_not_confirmed() { with_io_input_output(b"n\n", |io| { let tmp = TempDir::new().unwrap(); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); insert_config( &mut config, @@ -66,14 +66,14 @@ fn test_add_config_aborts_when_exists_and_not_confirmed() { /* ===== INSERT_CONFIG ===== */ #[test] fn test_has_config_true() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config(&mut config, "myconfig", ConfigEntry::default()); assert!(has_config(&config, "myconfig")); } #[test] fn test_insert_config() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config( &mut config, "myconfig", @@ -88,7 +88,7 @@ fn test_insert_config() { #[test] fn test_remove_config_entry_exists() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config(&mut config, "myconfig", ConfigEntry::default()); assert!(remove_config_entry(&mut config, "myconfig")); assert!(!config.configs.contains_key("myconfig")); @@ -120,7 +120,7 @@ fn test_create_default_config_aborts_when_exists_and_not_confirmed() { #[test] fn test_list_configs_empty() { let ((), out) = with_io_output(|io| { - let config = SetupConfig::new(); + let config = Config::new(); list_configs(&config, io); }); assert!(out.contains("No configurations created")); @@ -129,7 +129,7 @@ fn test_list_configs_empty() { #[test] fn test_list_configs_with_entries() { let ((), out) = with_io_output(|io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_config(&mut config, "myconfig", ConfigEntry::default()); list_configs(&config, io); }); @@ -140,7 +140,7 @@ fn test_list_configs_with_entries() { /* ===== REMOVE_CONFIG_ENTRY ===== */ #[test] fn test_remove_config_entry_missing() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); assert!(!remove_config_entry(&mut config, "nonexistent")); } @@ -149,7 +149,7 @@ fn test_remove_config_entry_missing() { fn test_remove_config_removes_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); insert_config(&mut config, "myconfig", ConfigEntry::default()); save_config(&mut config, false, &mut io.output).unwrap(); @@ -161,7 +161,7 @@ fn test_remove_config_removes_and_saves() { #[test] fn test_remove_config_not_found() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); with_io_output(|io| { remove_config(&mut config, "nonexistent", true, io, make_flags()).unwrap(); }); @@ -171,7 +171,7 @@ fn test_remove_config_not_found() { fn test_remove_config_aborts_when_not_confirmed() { with_io_input_output(b"n\n", |io| { let tmp = TempDir::new().unwrap(); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); insert_config(&mut config, "myconfig", ConfigEntry::default()); diff --git a/tests/config/io.rs b/tests/config/io.rs index 42e0122..a73d143 100644 --- a/tests/config/io.rs +++ b/tests/config/io.rs @@ -1,7 +1,7 @@ use crate::common::with_io_output; use star_setup::{ cli::BuildType, - config::{insert_config, load_config, save_config, ConfigEntry, SetupConfig}, + config::{insert_config, load_config, save_config, Config, ConfigEntry}, }; use std::{fs::write, path::PathBuf}; use tempfile::TempDir; @@ -12,7 +12,7 @@ fn test_save_and_load_roundtrip() { let tmp = TempDir::new().unwrap(); let path = tmp.path().join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); config.configs.insert( "default".to_string(), @@ -76,8 +76,8 @@ fn test_load_config_first_valid_wins() { let path1 = tmp1.path().join(".star-setup.json"); let path2 = tmp2.path().join(".star-setup.json"); - let mut config1 = SetupConfig::new(); - let mut config2 = SetupConfig::new(); + let mut config1 = Config::new(); + let mut config2 = Config::new(); with_io_output(|io| { config1.path = Some(path1.clone()); @@ -103,7 +103,7 @@ fn test_load_config_falls_through_invalid_to_valid() { write(&path1, "{invalid json").unwrap(); - let mut config2 = SetupConfig::new(); + let mut config2 = Config::new(); config2.path = Some(path2.clone()); insert_config(&mut config2, "second", ConfigEntry::default()); diff --git a/tests/profile/crud.rs b/tests/profile/crud.rs index 6792538..da5e175 100644 --- a/tests/profile/crud.rs +++ b/tests/profile/crud.rs @@ -1,6 +1,6 @@ use crate::common::{make_flags, with_io_dir, with_io_input_output, with_io_output}; use star_setup::{ - config::{load_config, save_config, SetupConfig}, + config::{load_config, save_config, Config}, profile::{add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry}, }; use tempfile::TempDir; @@ -8,7 +8,7 @@ use tempfile::TempDir; /* ===== HAS_PROFILE ===== */ #[test] fn test_has_profile_false() { - let config = SetupConfig::new(); + let config = Config::new(); assert!(!has_profile(&config, "nonexistent")); } @@ -16,7 +16,7 @@ fn test_has_profile_false() { fn test_add_profile_inserts_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); let args = vec!["myprofile".to_string(), "user/repo1".to_string()]; @@ -31,7 +31,7 @@ fn test_add_profile_inserts_and_saves() { fn test_save_and_load_profile_roundtrip() { let tmp = TempDir::new().unwrap(); let path = tmp.path().join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); insert_profile( &mut config, @@ -54,14 +54,14 @@ fn test_save_and_load_profile_roundtrip() { /* ===== INSERT_PROFILE ===== */ #[test] fn test_insert_profile() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); assert!(config.profiles.contains_key("myprofile")); } #[test] fn test_remove_profile_entry_exists() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_profile(&mut config, "myprofile", vec![]); assert!(remove_profile_entry(&mut config, "myprofile")); assert!(!config.profiles.contains_key("myprofile")); @@ -69,7 +69,7 @@ fn test_remove_profile_entry_exists() { #[test] fn test_has_profile_true() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_profile(&mut config, "myprofile", vec![]); assert!(has_profile(&config, "myprofile")); } @@ -77,7 +77,7 @@ fn test_has_profile_true() { /* ===== ADD_PROFILE ===== */ #[test] fn test_add_profile_errors_on_insufficient_args() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); let args = vec!["myprofile".to_string()]; with_io_output(|io| { let result = add_profile(&mut config, &args, true, io, make_flags()); @@ -87,7 +87,7 @@ fn test_add_profile_errors_on_insufficient_args() { #[test] fn test_add_profile_errors_on_empty_args() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); with_io_output(|io| { let result = add_profile(&mut config, &[], true, io, make_flags()); assert!(result.is_err()); @@ -97,7 +97,7 @@ fn test_add_profile_errors_on_empty_args() { #[test] fn test_add_profile_overwrites_existing() { with_io_dir(|tmp, io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.join(".star-setup.json")); insert_profile(&mut config, "myprofile", vec!["old/repo".to_string()]); @@ -110,7 +110,7 @@ fn test_add_profile_overwrites_existing() { #[test] fn test_add_profile_multiple_repos() { with_io_dir(|tmp, io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.join(".star-setup.json")); let args = vec![ @@ -128,7 +128,7 @@ fn test_add_profile_multiple_repos() { fn test_add_profile_aborts_when_exists_and_not_confirmed() { with_io_input_output(b"n\n", |io| { let tmp = TempDir::new().unwrap(); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); insert_profile(&mut config, "myprofile", vec!["old/repo".to_string()]); @@ -141,7 +141,7 @@ fn test_add_profile_aborts_when_exists_and_not_confirmed() { /* ===== REMOVE_PROFILE_ENTRY ===== */ #[test] fn test_remove_profile_entry_missing() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); assert!(!remove_profile_entry(&mut config, "nonexistent")); } @@ -150,7 +150,7 @@ fn test_remove_profile_entry_missing() { fn test_remove_profile_removes_and_saves() { with_io_dir(|tmp, io| { let path = tmp.join(".star-setup.json"); - let mut config = SetupConfig::new(); + let mut config = Config::new(); config.path = Some(path.clone()); insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); save_config(&mut config, false, &mut io.output).unwrap(); @@ -162,7 +162,7 @@ fn test_remove_profile_removes_and_saves() { #[test] fn test_remove_profile_not_found() { - let mut config = SetupConfig::new(); + let mut config = Config::new(); with_io_output(|io| { remove_profile(&mut config, "nonexistent", true, io, make_flags()).unwrap(); }); @@ -171,7 +171,7 @@ fn test_remove_profile_not_found() { #[test] fn test_remove_profile_aborts_when_not_confirmed() { with_io_input_output(b"n\n", |io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); remove_profile(&mut config, "myprofile", false, io, make_flags()).unwrap(); diff --git a/tests/profile/display.rs b/tests/profile/display.rs index 6b127d3..2d17be2 100644 --- a/tests/profile/display.rs +++ b/tests/profile/display.rs @@ -1,13 +1,13 @@ use crate::common::with_io_output; use star_setup::{ - config::SetupConfig, + config::Config, profile::{insert_profile, list_profiles}, }; #[test] fn test_list_profiles_empty() { let ((), out) = with_io_output(|io| { - let config = SetupConfig::new(); + let config = Config::new(); list_profiles(&config, io); }); assert!(out.contains("No profiles configured")); @@ -16,7 +16,7 @@ fn test_list_profiles_empty() { #[test] fn test_list_profiles_with_entries() { let ((), out) = with_io_output(|io| { - let mut config = SetupConfig::new(); + let mut config = Config::new(); insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); list_profiles(&config, io); }); From b25bb3e811ea08da5d8973c46471ac9d06db43ae Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 19:44:50 -0400 Subject: [PATCH 3/7] chore: move profile type to profile module --- src/config/types.rs | 11 +++-------- src/profile/crud.rs | 4 ++-- src/profile/display.rs | 5 +---- src/profile/mod.rs | 2 ++ src/profile/types.rs | 7 +++++++ tests/commands/mono/resolve.rs | 3 ++- 6 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 src/profile/types.rs diff --git a/src/config/types.rs b/src/config/types.rs index f3632f8..decc891 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1,5 +1,6 @@ -use crate::cli::{ - BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, +use crate::{ + cli::{BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs}, + profile::Profile, }; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; @@ -146,9 +147,3 @@ impl From<&ResolvedArgs> for ConfigEntry { } } } - -#[derive(Serialize, Deserialize, Default, Clone)] -pub struct Profile { - pub test_repo: Option, - pub deps: Vec, -} diff --git a/src/profile/crud.rs b/src/profile/crud.rs index 439a72a..90549fa 100644 --- a/src/profile/crud.rs +++ b/src/profile/crud.rs @@ -1,7 +1,7 @@ use crate::{ - config::{persist_or_dry_run, types::Profile, Config}, + config::{persist_or_dry_run, Config}, ctx::{IoCtx, RunFlags}, - profile::print_profile_details, + profile::{print_profile_details, Profile}, prompts::confirm_abort, }; diff --git a/src/profile/display.rs b/src/profile/display.rs index baad993..b2a3bc0 100644 --- a/src/profile/display.rs +++ b/src/profile/display.rs @@ -1,7 +1,4 @@ -use crate::{ - config::types::{Config, Profile}, - ctx::IoCtx, -}; +use crate::{config::Config, ctx::IoCtx, profile::Profile}; use std::io::Write; pub fn print_profile_details( diff --git a/src/profile/mod.rs b/src/profile/mod.rs index ce2c008..493425e 100644 --- a/src/profile/mod.rs +++ b/src/profile/mod.rs @@ -2,3 +2,5 @@ pub mod crud; pub use crud::{add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry}; pub mod display; pub use display::{list_profiles, print_profile_details}; +pub mod types; +pub use types::Profile; diff --git a/src/profile/types.rs b/src/profile/types.rs new file mode 100644 index 0000000..ddaaeb4 --- /dev/null +++ b/src/profile/types.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Clone)] +pub struct Profile { + pub test_repo: Option, + pub deps: Vec, +} diff --git a/tests/commands/mono/resolve.rs b/tests/commands/mono/resolve.rs index 3b3f180..955a743 100644 --- a/tests/commands/mono/resolve.rs +++ b/tests/commands/mono/resolve.rs @@ -2,7 +2,8 @@ use crate::common::{default_resolved, with_ctx, with_io, MockRunner}; use star_setup::{ cli::BuildSystem, commands::{generate_mono_config, resolve_repos_for_mono, resolve_test_repo}, - config::{types::Profile, Config}, + config::Config, + profile::Profile, }; use std::{ fs::{create_dir_all, read_to_string, write}, From 825fb50ee0a364c4298266d4ed0c3ce1713c13ec Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 20:05:37 -0400 Subject: [PATCH 4/7] feat: accept test repo in profile add via --test-repo flag --- src/cli/commands.rs | 5 +- src/commands/handlers.rs | 18 +++-- src/profile/crud.rs | 37 ++------- src/profile/types.rs | 11 +++ tests/profile.rs | 2 + tests/profile/crud.rs | 165 +++++++++++++++++++++++++++++---------- tests/profile/display.rs | 11 ++- tests/profile/types.rs | 11 +++ 8 files changed, 179 insertions(+), 81 deletions(-) create mode 100644 tests/profile/types.rs diff --git a/src/cli/commands.rs b/src/cli/commands.rs index 0036324..4bcef76 100644 --- a/src/cli/commands.rs +++ b/src/cli/commands.rs @@ -51,7 +51,10 @@ pub enum ProfileAction { Add { /// Name of the profile. name: String, - /// Repository list (username/repo ...). + /// Test repository (user/repo). + #[arg(long)] + test_repo: Option, + /// Repository list (user/lib1 user/lib2). repos: Vec, }, /// Remove a named profile. diff --git a/src/commands/handlers.rs b/src/commands/handlers.rs index 0fe5e13..21711b8 100644 --- a/src/commands/handlers.rs +++ b/src/commands/handlers.rs @@ -1,14 +1,14 @@ use crate::{ cli::{ - ConfigAction, ProfileAction, WorkspaceAction, - WorkspaceAction::{Clean, Status, Update}, + ConfigAction, ProfileAction, + WorkspaceAction::{self, Clean, Status, Update}, }, config::{add_config, create_default_config, list_configs, remove_config, Config, ConfigEntry}, ctx::{with_runner, IoCtx, RunFlags}, - profile::{add_profile, list_profiles, remove_profile}, + profile::{add_profile, list_profiles, remove_profile, Profile}, workspace::resolve_workspace, }; -use std::{error::Error, iter::once, path::PathBuf}; +use std::{error::Error, path::PathBuf}; /// Handles configuration-related subcommands. /// # Errors @@ -52,9 +52,13 @@ pub fn handle_profile_cmd( match action { ProfileAction::List => list_profiles(config, io), ProfileAction::Remove { name } => remove_profile(config, &name, yes, io, flags)?, - ProfileAction::Add { name, repos } => { - let vals = once(name).chain(repos).collect::>(); - add_profile(config, &vals, yes, io, flags)?; + ProfileAction::Add { + name, + test_repo, + repos, + } => { + let profile = Profile::from_args(test_repo, repos)?; + add_profile(config, &name, &profile, yes, io, flags)?; } } Ok(()) diff --git a/src/profile/crud.rs b/src/profile/crud.rs index 90549fa..8e2dd8b 100644 --- a/src/profile/crud.rs +++ b/src/profile/crud.rs @@ -6,14 +6,8 @@ use crate::{ }; /// Inserts or overwrites a named profile. -pub fn insert_profile(config: &mut Config, name: &str, deps: Vec) { - config.profiles.insert( - name.to_string(), - Profile { - test_repo: None, - deps, - }, - ); +pub fn insert_profile(config: &mut Config, name: &str, profile: Profile) { + config.profiles.insert(name.to_string(), profile); } /// Removes a named profile. Returns `true` if it existed. @@ -28,24 +22,17 @@ pub fn has_profile(config: &Config, name: &str) -> bool { } /// Adds a new profile to the configuration. -/// args: [name, repo1, repo2, ...] /// # Errors -/// Returns an error if fewer than two arguments are provided or if saving fails. +/// Returns an error if saving fails. pub fn add_profile( config: &mut Config, - args: &[String], + name: &str, + profile: &Profile, yes: bool, io: &mut IoCtx<'_>, flags: RunFlags, ) -> Result<(), String> { - if args.len() < 2 { - return Err("profile add requires NAME REPO1 [REPO2 ...]".to_string()); - } - - let name = args[0].clone(); - let repos = args[1..].to_vec(); - - if has_profile(config, &name) + if has_profile(config, name) && !confirm_abort( &format!(" Warning: Profile '{name}' already exists. Overwrite?"), yes, @@ -60,21 +47,13 @@ pub fn add_profile( flags, io, &format!("Would save profile '{name}' to config file"), - |config| insert_profile(config, &name, repos.clone()), + |config| insert_profile(config, name, profile.clone()), |_config, path, io| { writeln!(io.output, " Profile '{name}' added successfully").ok(); writeln!(io.output, " Configuration saved to: {}", path.display()).ok(); }, )?; - print_profile_details( - io.output, - "Profile details:", - "Repositories", - &Profile { - test_repo: None, - deps: repos.clone(), - }, - ); + print_profile_details(io.output, "Profile details:", "Repositories", profile); writeln!( io.output, " Usage: star-setup username/test-repo --profile {name}" diff --git a/src/profile/types.rs b/src/profile/types.rs index ddaaeb4..329120b 100644 --- a/src/profile/types.rs +++ b/src/profile/types.rs @@ -5,3 +5,14 @@ pub struct Profile { pub test_repo: Option, pub deps: Vec, } + +impl Profile { + /// # Errors + /// Returns an error if neither a test repo nor any dependency is given. + pub fn from_args(test_repo: Option, deps: Vec) -> Result { + if test_repo.is_none() && deps.is_empty() { + return Err("profile requires --test-repo or at least one dependency repo".to_string()); + } + Ok(Self { test_repo, deps }) + } +} diff --git a/tests/profile.rs b/tests/profile.rs index e08ad54..a84ee34 100644 --- a/tests/profile.rs +++ b/tests/profile.rs @@ -5,3 +5,5 @@ mod common; mod crud; #[path = "profile/display.rs"] mod display; +#[path = "profile/types.rs"] +mod types; diff --git a/tests/profile/crud.rs b/tests/profile/crud.rs index da5e175..88b44a8 100644 --- a/tests/profile/crud.rs +++ b/tests/profile/crud.rs @@ -1,7 +1,9 @@ use crate::common::{make_flags, with_io_dir, with_io_input_output, with_io_output}; use star_setup::{ config::{load_config, save_config, Config}, - profile::{add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry}, + profile::{ + add_profile, has_profile, insert_profile, remove_profile, remove_profile_entry, Profile, + }, }; use tempfile::TempDir; @@ -19,8 +21,19 @@ fn test_add_profile_inserts_and_saves() { let mut config = Config::new(); config.path = Some(path.clone()); - let args = vec!["myprofile".to_string(), "user/repo1".to_string()]; - add_profile(&mut config, &args, true, io, make_flags()).unwrap(); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); + assert!(has_profile(&config, "myprofile")); assert!(path.exists()); }); @@ -33,15 +46,18 @@ fn test_save_and_load_profile_roundtrip() { let path = tmp.path().join(".star-setup.json"); let mut config = Config::new(); config.path = Some(path.clone()); + insert_profile( &mut config, "myprofile", - vec!["user/repo1".to_string(), "user/repo2".to_string()], + Profile { + test_repo: None, + deps: vec!["user/repo1".to_string(), "user/repo2".to_string()], + }, ); with_io_output(|io| { save_config(&mut config, false, &mut io.output).unwrap(); - let loaded = load_config(&[path], false, false, &mut io.output); assert!(loaded.profiles.contains_key("myprofile")); assert_eq!( @@ -55,14 +71,25 @@ fn test_save_and_load_profile_roundtrip() { #[test] fn test_insert_profile() { let mut config = Config::new(); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + ); + assert!(config.profiles.contains_key("myprofile")); } #[test] fn test_remove_profile_entry_exists() { let mut config = Config::new(); - insert_profile(&mut config, "myprofile", vec![]); + + insert_profile(&mut config, "myprofile", Profile::default()); + assert!(remove_profile_entry(&mut config, "myprofile")); assert!(!config.profiles.contains_key("myprofile")); } @@ -70,39 +97,39 @@ fn test_remove_profile_entry_exists() { #[test] fn test_has_profile_true() { let mut config = Config::new(); - insert_profile(&mut config, "myprofile", vec![]); - assert!(has_profile(&config, "myprofile")); -} -/* ===== ADD_PROFILE ===== */ -#[test] -fn test_add_profile_errors_on_insufficient_args() { - let mut config = Config::new(); - let args = vec!["myprofile".to_string()]; - with_io_output(|io| { - let result = add_profile(&mut config, &args, true, io, make_flags()); - assert!(result.is_err()); - }); -} + insert_profile(&mut config, "myprofile", Profile::default()); -#[test] -fn test_add_profile_errors_on_empty_args() { - let mut config = Config::new(); - with_io_output(|io| { - let result = add_profile(&mut config, &[], true, io, make_flags()); - assert!(result.is_err()); - }); + assert!(has_profile(&config, "myprofile")); } +/* ===== ADD_PROFILE ===== */ #[test] fn test_add_profile_overwrites_existing() { with_io_dir(|tmp, io| { let mut config = Config::new(); config.path = Some(tmp.join(".star-setup.json")); - insert_profile(&mut config, "myprofile", vec!["old/repo".to_string()]); - let args = vec!["myprofile".to_string(), "new/repo".to_string()]; - add_profile(&mut config, &args, true, io, make_flags()).unwrap(); + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["old/repo".to_string()], + }, + ); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["new/repo".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); assert_eq!(config.profiles["myprofile"].deps, vec!["new/repo"]); }); } @@ -113,13 +140,23 @@ fn test_add_profile_multiple_repos() { let mut config = Config::new(); config.path = Some(tmp.join(".star-setup.json")); - let args = vec![ - "myprofile".to_string(), - "user/repo1".to_string(), - "user/repo2".to_string(), - "user/repo3".to_string(), - ]; - add_profile(&mut config, &args, true, io, make_flags()).unwrap(); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec![ + "user/repo1".to_string(), + "user/repo2".to_string(), + "user/repo3".to_string(), + ], + }, + true, + io, + make_flags(), + ) + .unwrap(); + assert_eq!(config.profiles["myprofile"].deps.len(), 3); }); } @@ -130,10 +167,27 @@ fn test_add_profile_aborts_when_exists_and_not_confirmed() { let tmp = TempDir::new().unwrap(); let mut config = Config::new(); config.path = Some(tmp.path().join(".star-setup.json")); - insert_profile(&mut config, "myprofile", vec!["old/repo".to_string()]); - let args = vec!["myprofile".to_string(), "new/repo".to_string()]; - add_profile(&mut config, &args, false, io, make_flags()).unwrap(); + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["old/repo".to_string()], + }, + ); + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["new/repo".to_string()], + }, + false, + io, + make_flags(), + ) + .unwrap(); assert_eq!(config.profiles["myprofile"].deps, vec!["old/repo"]); }); } @@ -152,10 +206,24 @@ fn test_remove_profile_removes_and_saves() { let path = tmp.join(".star-setup.json"); let mut config = Config::new(); config.path = Some(path.clone()); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); + save_config(&mut config, false, &mut io.output).unwrap(); remove_profile(&mut config, "myprofile", true, io, make_flags()).unwrap(); + assert!(!has_profile(&config, "myprofile")); }); } @@ -172,9 +240,22 @@ fn test_remove_profile_not_found() { fn test_remove_profile_aborts_when_not_confirmed() { with_io_input_output(b"n\n", |io| { let mut config = Config::new(); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + + add_profile( + &mut config, + "myprofile", + &Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + true, + io, + make_flags(), + ) + .unwrap(); remove_profile(&mut config, "myprofile", false, io, make_flags()).unwrap(); + assert!(has_profile(&config, "myprofile")); }); } diff --git a/tests/profile/display.rs b/tests/profile/display.rs index 2d17be2..8e1bda3 100644 --- a/tests/profile/display.rs +++ b/tests/profile/display.rs @@ -1,7 +1,7 @@ use crate::common::with_io_output; use star_setup::{ config::Config, - profile::{insert_profile, list_profiles}, + profile::{insert_profile, list_profiles, Profile}, }; #[test] @@ -17,7 +17,14 @@ fn test_list_profiles_empty() { fn test_list_profiles_with_entries() { let ((), out) = with_io_output(|io| { let mut config = Config::new(); - insert_profile(&mut config, "myprofile", vec!["user/repo1".to_string()]); + insert_profile( + &mut config, + "myprofile", + Profile { + test_repo: None, + deps: vec!["user/repo1".to_string()], + }, + ); list_profiles(&config, io); }); assert!(out.contains("myprofile")); diff --git a/tests/profile/types.rs b/tests/profile/types.rs new file mode 100644 index 0000000..cb67eef --- /dev/null +++ b/tests/profile/types.rs @@ -0,0 +1,11 @@ +use star_setup::profile::Profile; + +#[test] +fn test_profile_from_args_errors_when_empty() { + assert!(Profile::from_args(None, vec![]).is_err()); +} + +#[test] +fn test_profile_from_args_test_repo_only() { + assert!(Profile::from_args(Some("user/app".to_string()), vec![]).is_ok()); +} From 2b3ac99b0ca287f0c35d6fc107501e78790b04dd Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 20:41:00 -0400 Subject: [PATCH 5/7] feat: fall back to profile test repo when positional repo omitted --- src/commands/mono/mod.rs | 4 +- src/commands/mono/mode.rs | 11 ++-- src/commands/mono/resolve.rs | 58 +++++++++++++------- src/profile/types.rs | 2 +- tests/commands/mono/resolve.rs | 97 ++++++++++++++++++++-------------- 5 files changed, 107 insertions(+), 65 deletions(-) diff --git a/src/commands/mono/mod.rs b/src/commands/mono/mod.rs index ed0aaa4..3f72cc7 100644 --- a/src/commands/mono/mod.rs +++ b/src/commands/mono/mod.rs @@ -7,7 +7,9 @@ pub use display::{print_setup_complete, resolve_setup_paths}; pub mod mode; pub use mode::mono_repo_mode; pub mod resolve; -pub use resolve::{resolve_repos_for_mono, resolve_test_repo}; +pub use resolve::{ + resolve_profile, resolve_repos_for_mono, resolve_test_repo, resolve_test_repo_for_mono, +}; pub mod wraps; pub use wraps::hoist_wraps; pub mod setup; diff --git a/src/commands/mono/mode.rs b/src/commands/mono/mode.rs index a413545..c67eeb1 100644 --- a/src/commands/mono/mode.rs +++ b/src/commands/mono/mode.rs @@ -1,12 +1,13 @@ use crate::{ cli::{detect_mono_build_system, BuildSystem::Npm, ResolvedArgs}, commands::{ - build_project, build_repo_list, extract_repo_input, maybe_open_dev_server, + build_project, build_repo_list, maybe_open_dev_server, mono::{ display::{resolve_setup_paths, SetupPaths}, generate_mono_config, generate_watch_scripts, open_watch_scripts, print_setup_complete, + resolve::{resolve_profile, resolve_test_repo_for_mono}, }, - prepare_build_dir, print_mode_header, resolve_repos_for_mono, resolve_test_repo, ModeHeader, + prepare_build_dir, print_mode_header, resolve_repos_for_mono, ModeHeader, }, config::Config, ctx::RunCtx, @@ -29,9 +30,9 @@ pub fn mono_repo_mode( ctx: &mut RunCtx<'_, '_>, ) -> Result<(), String> { let total = Instant::now(); - let repo_input = extract_repo_input(args)?; - let test_repo = resolve_test_repo(repo_input)?; - let deps = resolve_repos_for_mono(args, config, &mut ctx.io)?; + let profile = resolve_profile(args, config, &mut ctx.io)?; + let test_repo = resolve_test_repo_for_mono(args, profile)?; + let deps = resolve_repos_for_mono(args, profile); let repos = build_repo_list(&test_repo, &deps); print_mode_header( diff --git a/src/commands/mono/resolve.rs b/src/commands/mono/resolve.rs index 77d786b..af3a62d 100644 --- a/src/commands/mono/resolve.rs +++ b/src/commands/mono/resolve.rs @@ -1,4 +1,9 @@ -use crate::{cli::ResolvedArgs, config::Config, ctx::IoCtx, profile::list_profiles}; +use crate::{ + cli::ResolvedArgs, + config::Config, + ctx::IoCtx, + profile::{list_profiles, Profile}, +}; /// Normalizes a repository input to `username/repo` format. /// # Errors @@ -24,26 +29,43 @@ pub fn resolve_test_repo(repo_input: &str) -> Result { } } -/// Resolves the list of repositories for mono-repo mode from a profile or explicit repo list. +/// Resolves the named profile for mono-repo mode, if one was requested. /// # Errors -/// Returns an error if the specified profile does not exist, or has no repositories. -pub fn resolve_repos_for_mono( +/// Returns an error if `--profile` names a profile that doesn't exist. +pub fn resolve_profile<'a>( args: &ResolvedArgs, - config: &Config, + config: &'a Config, io: &mut IoCtx<'_>, -) -> Result, String> { - if let Some(profile_name) = &args.mono.profile { - let profile_repos = config.profiles.get(profile_name).ok_or_else(|| { +) -> Result, String> { + match &args.mono.profile { + Some(name) => config.profiles.get(name).map(Some).ok_or_else(|| { list_profiles(config, io); - format!("Profile '{profile_name}' not found") - })?; - if profile_repos.test_repo.is_none() && profile_repos.deps.is_empty() { - return Err(format!("Profile '{profile_name}' has no repositories")); - } - Ok(profile_repos.deps.clone()) - } else if let Some(r) = &args.mono.repos { - Ok(r.clone()) - } else { - Err("No repos or profile specified for mono-repo mode".to_string()) + format!("Profile '{name}' not found") + }), + None => Ok(None), } } + +/// Resolves the test repo for mono-repo mode: CLI positional wins, else the profile's. +/// # Errors +/// Returns an error if neither a positional repo nor a profile test repo is available. +pub fn resolve_test_repo_for_mono( + args: &ResolvedArgs, + profile: Option<&Profile>, +) -> Result { + match args.repo.as_deref() { + Some(r) => resolve_test_repo(r.trim_end_matches('/')), + None => profile + .and_then(|p| p.test_repo.clone()) + .ok_or_else(|| "No repository specified".to_string()), + } +} + +/// Resolves the dependency repositories for mono-repo mode from a profile or explicit list. +#[must_use] +pub fn resolve_repos_for_mono(args: &ResolvedArgs, profile: Option<&Profile>) -> Vec { + profile + .map(|p| p.deps.clone()) + .or_else(|| args.mono.repos.clone()) + .unwrap_or_default() +} diff --git a/src/profile/types.rs b/src/profile/types.rs index 329120b..9a0b47c 100644 --- a/src/profile/types.rs +++ b/src/profile/types.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Default, Clone)] +#[derive(Serialize, Deserialize, Default, Clone, Debug)] pub struct Profile { pub test_repo: Option, pub deps: Vec, diff --git a/tests/commands/mono/resolve.rs b/tests/commands/mono/resolve.rs index 955a743..d7cf9c9 100644 --- a/tests/commands/mono/resolve.rs +++ b/tests/commands/mono/resolve.rs @@ -1,7 +1,11 @@ use crate::common::{default_resolved, with_ctx, with_io, MockRunner}; use star_setup::{ cli::BuildSystem, - commands::{generate_mono_config, resolve_repos_for_mono, resolve_test_repo}, + commands::{ + generate_mono_config, + mono::{resolve_profile, resolve_test_repo_for_mono}, + resolve_repos_for_mono, resolve_test_repo, + }, config::Config, profile::Profile, }; @@ -53,80 +57,93 @@ fn test_resolve_test_repo_errors() { /* ===== RESOLVE_REPOS_FOR_MONO ===== */ #[test] -fn test_resolve_repos_for_mono_empty_profile_errors() { +fn test_resolve_test_repo_for_mono_errors_when_profile_empty() { let mut config = Config::new(); - config.profiles.insert( - "emptyprofile".to_string(), - Profile { - test_repo: None, - deps: vec![], - }, - ); + config + .profiles + .insert("emptyprofile".to_string(), Profile::default()); let mut args = default_resolved(); + args.repo = None; args.mono.profile = Some("emptyprofile".to_string()); - with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("has no repositories")); + let profile = resolve_profile(&args, &config, io).unwrap(); + assert!(resolve_test_repo_for_mono(&args, profile).is_err()); }); } #[test] -fn test_resolve_repos_for_mono_with_profile() { +fn test_resolve_test_repo_for_mono_prefers_positional_over_profile() { let mut config = Config::new(); config.profiles.insert( "myprofile".to_string(), Profile { - test_repo: None, - deps: vec!["user/lib1".to_string(), "user/lib2".to_string()], + test_repo: Some("user/other".to_string()), + deps: vec![], }, ); - let mut args = default_resolved(); - args.mono.profile = Some("myprofile".to_string()); - + let args = default_resolved(); // args.repo already Some("user/repo") with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), vec!["user/lib1", "user/lib2"]); + let profile = resolve_profile(&args, &config, io).unwrap(); + assert_eq!( + resolve_test_repo_for_mono(&args, profile), + Ok("user/repo".to_string()) + ); }); } #[test] -fn test_resolve_repos_for_mono_with_explicit_repos() { - let config = Config::new(); +fn test_resolve_test_repo_for_mono_falls_back_to_profile() { + let mut config = Config::new(); + config.profiles.insert( + "myprofile".to_string(), + Profile { + test_repo: Some("user/app".to_string()), + deps: vec![], + }, + ); let mut args = default_resolved(); - args.mono.repos = Some(vec!["user/lib1".to_string(), "user/lib2".to_string()]); - + args.repo = None; + args.mono.profile = Some("myprofile".to_string()); with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_ok()); - assert_eq!(result.unwrap(), vec!["user/lib1", "user/lib2"]); + let profile = resolve_profile(&args, &config, io).unwrap(); + assert_eq!( + resolve_test_repo_for_mono(&args, profile), + Ok("user/app".to_string()) + ); }); } #[test] -fn test_resolve_repos_for_mono_no_repos_or_profile_errors() { - let config = Config::new(); +fn test_resolve_repos_for_mono_with_profile() { + let profile = Profile { + test_repo: None, + deps: vec!["user/lib1".to_string(), "user/lib2".to_string()], + }; let args = default_resolved(); + assert_eq!( + resolve_repos_for_mono(&args, Some(&profile)), + vec!["user/lib1", "user/lib2"] + ); +} - with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("No repos or profile specified")); - }); +#[test] +fn test_resolve_repos_for_mono_with_explicit_repos() { + let mut args = default_resolved(); + args.mono.repos = Some(vec!["user/lib1".to_string(), "user/lib2".to_string()]); + assert_eq!( + resolve_repos_for_mono(&args, None), + vec!["user/lib1", "user/lib2"] + ); } #[test] -fn test_resolve_repos_for_mono_profile_not_found_errors() { +fn test_resolve_profile_not_found_errors() { let config = Config::new(); let mut args = default_resolved(); args.mono.profile = Some("nonexistent".to_string()); with_io(|io| { - let result = resolve_repos_for_mono(&args, &config, io); + let result = resolve_profile(&args, &config, io); assert!(result.is_err()); assert!(result.unwrap_err().contains("not found")); }); From d87b13ef26dba28fa47ffd070eb67e4216f750a8 Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 20:44:13 -0400 Subject: [PATCH 6/7] docs: update profile examples for optional test repo --- README.md | 12 +++++++++--- src/profile/crud.rs | 6 +----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 187ac0d..f1493f5 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,10 @@ Clones multiple repositories into a single workspace and auto-detects the build # Clone and build a test repo and a manual repo list star-setup username/repo --repos user/lib1 user/lib2 -# Clone and build a test repo and a saved profile +# Clone and build a saved profile +star-setup --profile myprofile + +# Override a profile's test repo star-setup username/repo --profile myprofile ``` @@ -280,7 +283,10 @@ star-setup username/repo --config myconfig ### Profile Mode Profiles represent a saved ecosystem of libraries commonly used together. ```bash -# Add a profile +# Add a profile with a test repo and dependencies +star-setup profile add myprofile --test-repo user/app user/lib1 user/lib2 + +# Add a dependency-only profile star-setup profile add myprofile user/lib1 user/lib2 # List profiles @@ -290,7 +296,7 @@ star-setup profile list star-setup profile remove myprofile # Use a profile -star-setup username/repo --profile myprofile +star-setup --profile myprofile ``` ### Development diff --git a/src/profile/crud.rs b/src/profile/crud.rs index 8e2dd8b..9aaad5e 100644 --- a/src/profile/crud.rs +++ b/src/profile/crud.rs @@ -54,11 +54,7 @@ pub fn add_profile( }, )?; print_profile_details(io.output, "Profile details:", "Repositories", profile); - writeln!( - io.output, - " Usage: star-setup username/test-repo --profile {name}" - ) - .ok(); + writeln!(io.output, " Usage: star-setup --profile {name}").ok(); Ok(()) } From f7077feac6707908c368b5f0b7aeba53e65db43a Mon Sep 17 00:00:00 2001 From: Masonlet Date: Sat, 4 Jul 2026 20:45:28 -0400 Subject: [PATCH 7/7] chore: bump v0.4.8 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5528cb1..509f766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "star-setup" -version = "0.4.7" +version = "0.4.8" edition = "2021" repository = "https://github.com/star-setup/core" description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems"