Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c155136
refactor: extract runner selection and context creation into `with_ru…
masonlet Jun 28, 2026
9745d60
chore: rename cmd var
masonlet Jun 28, 2026
da83404
chore: rename command vars
masonlet Jun 28, 2026
dd850c7
refactor: extract common input reading logic in prompts
masonlet Jun 28, 2026
5960ea0
refactor(cli): simplify config logic
masonlet Jun 28, 2026
df086aa
refactor(commands): extract `Path` to string conversion helper
masonlet Jun 28, 2026
128a19d
refactor(single): use shared clone_repo helper
masonlet Jun 28, 2026
77f4f73
refactor(repository): extract git pull helper
masonlet Jun 28, 2026
61fe794
refactor: improve prerequisite checks
masonlet Jun 28, 2026
8bf86af
refactor(utils): simplify module and command execution
masonlet Jun 28, 2026
e775cdb
refactor(cli): consolidate interactive confirmation logic into confir…
masonlet Jun 28, 2026
636ec7a
refactor(run): extract subcomamnd handlers
masonlet Jun 28, 2026
6815556
refactor: relocate subcommand handlers and with_runner helper
masonlet Jun 28, 2026
31817c3
refactor(cli): extract interactive prompt helpers to reduce boilerplate
masonlet Jun 28, 2026
6edc773
refactor: move workspace functions to Workspace methods
masonlet Jun 28, 2026
e90456d
refactor: extract CONFIG_FILE_NAME into main and pass as PathBuf
masonlet Jun 28, 2026
599b23d
chore: move ask_bool_if, ask_required into prompts module
masonlet Jun 28, 2026
545e29c
chore: cargo fmt
masonlet Jun 28, 2026
d2a4357
refactor(cli): optimize and clean up build system and type parsing
masonlet Jun 28, 2026
14d9347
test(cli): refactor build detection tests to use a shared ctx helper
masonlet Jun 28, 2026
e32cf51
chore: cargo clip
masonlet Jun 28, 2026
2949b9e
refactor(tests): streamline build system detection tests
masonlet Jun 28, 2026
be9555f
refactor(tests): convert build type and system parsing tests to tables
masonlet Jun 28, 2026
9f43c91
refactor(tests): streamline configuration resolution tests
masonlet Jun 28, 2026
6a6559a
refactor(tests): streamline prompts test file boilerplate
masonlet Jun 28, 2026
99407b6
test(interactive): streamline interactive mode tests
masonlet Jun 28, 2026
afea7f8
refactor(tests): streamline runner tests
masonlet Jun 28, 2026
ab1efcb
refactor(tests): streamline repository tests
masonlet Jun 28, 2026
f742177
refactor(tests): clean up resolve tests with config builder
masonlet Jun 28, 2026
9f0e617
refactor(tests): streamline monorepo clone tests
masonlet Jun 28, 2026
8d1f4c3
refactor(tests): streamline mono config tests
masonlet Jun 28, 2026
c704dec
refactor(tests): streamline mono repo mode tests
masonlet Jun 28, 2026
2e96388
refactor(tests): streamline mono resolve tests
masonlet Jun 28, 2026
b4709b7
test(commands): streamline build_repo_list tests
masonlet Jun 28, 2026
064f099
refactor(tests): streamline mono wraps tests
masonlet Jun 28, 2026
81749ae
chore: cargo fmt
masonlet Jun 28, 2026
876e68f
refactor(tests): add shared ctx/io harness, migrate clone + repository
masonlet Jun 28, 2026
327f3de
refactor(tests): migrate wraps, config, resolve to with_io/with_io_dir
masonlet Jun 28, 2026
429a60c
refactor(tests): migrate mode tests to with_ctx/with_runner_ctx
masonlet Jun 28, 2026
6a56b91
refactor(tests): extract `with_tmp_dir` helper in workspace resolve t…
masonlet Jun 28, 2026
2d3c9b4
refactor(tests): extract shared workspace helper and deduplicate boil…
masonlet Jun 29, 2026
82c569d
refactor(tests): migrate prerequisite tests to with_ctx
masonlet Jun 29, 2026
3894a10
refactor(tests): use shared input io helpers in interactive and promp…
masonlet Jun 29, 2026
79a558c
refactor(tests): replace manual build test setup w/ with_runner_ctx
masonlet Jun 29, 2026
9370b04
refactor(tests): use shared io helpers in mono display tests
masonlet Jun 29, 2026
0e070ed
refactor(tests): use shared io helper in mode header test
masonlet Jun 29, 2026
2ad2936
refactor(tests): add shared IO output helper and streamline test harn…
masonlet Jun 29, 2026
60da53a
refactor(tests): streamline mono resolve mono meson test
masonlet Jun 29, 2026
66ef4d6
chore: whitespace
masonlet Jun 29, 2026
034c026
refactor(tests): streamline process runner tests with io harness
masonlet Jun 29, 2026
4e47b23
refactor(tessts): streamline interactive EOF test
masonlet Jun 29, 2026
d7a4147
refactor(tests): streamline workspace resolution tests
masonlet Jun 29, 2026
be51ac6
refactor(tests): migrate config test submodules to harness helpers
masonlet Jun 29, 2026
77bf972
refactor(tests): migrate profile test submodules to harness helpers
masonlet Jun 29, 2026
5c1e5d5
refactor(tests): clean up build detection tests
masonlet Jun 29, 2026
437853e
refactor(tests): convert single repo mode tests to unified runner
masonlet Jun 29, 2026
5cd86de
chore: clip
masonlet Jun 29, 2026
b889390
chore: bump v0.3.5
masonlet Jun 29, 2026
208812d
fix: conditional windows imports
masonlet Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "star-setup"
version = "0.3.4"
version = "0.3.5"
edition = "2021"
repository = "https://github.com/star-setup/core"
description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems"
Expand Down
6 changes: 3 additions & 3 deletions src/cli/build/detect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ fn pick_build_system(
has_cmake: bool,
has_meson: bool,
none_err: &str,
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<BuildSystem, String> {
match (has_cmake, has_meson) {
(true, false) => Ok(BuildSystem::Cmake),
Expand All @@ -26,7 +26,7 @@ fn pick_build_system(
/// Detects the build system in use by inspecting the given directory.
/// # Errors
/// Returns an error on EOF during prompt, or if no supported build system is found.
pub fn detect_build_system(dir: &Path, ctx: &mut RunCtx<'_>) -> Result<BuildSystem, String> {
pub fn detect_build_system(dir: &Path, ctx: &mut RunCtx<'_, '_>) -> Result<BuildSystem, String> {
crate::time!(ctx.io.timing, ctx.io.output, "Detect", {
let has_cmake = dir.join("CMakeLists.txt").exists();
let has_meson = dir.join("meson.build").exists();
Expand All @@ -39,7 +39,7 @@ pub fn detect_build_system(dir: &Path, ctx: &mut RunCtx<'_>) -> Result<BuildSyst
/// Returns an error if systems are inconsistent or none found, or EOF during prompt.
pub fn detect_mono_build_system(
dirs: &[PathBuf],
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<BuildSystem, String> {
writeln!(ctx.io.output, "Detecting build system\n").ok();
crate::time!(ctx.io.timing, ctx.io.output, "Detect", {
Expand Down
70 changes: 45 additions & 25 deletions src/cli/build/types.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
use serde::{Deserialize, Serialize};
use std::str::FromStr;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BuildSystem {
/// `CMake` build system (`CMakeLists.txt`).
Cmake,
/// Meson build system (`meson.build`).
Meson,
}

#[derive(Default, Clone, Serialize, Deserialize, PartialEq, Debug)]
impl FromStr for BuildSystem {
type Err = String;

/// Parses a build system string.
/// # Errors
/// Returns an error if the string does not match `cmake` or `meson`.
fn from_str(s: &str) -> Result<Self, Self::Err> {
let systems = [("cmake", Self::Cmake), ("meson", Self::Meson)];

for (name, variant) in systems {
if s.eq_ignore_ascii_case(name) {
return Ok(variant);
}
}

Err(format!("Unknown build system '{s}'. Valid: cmake, meson"))
}
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BuildType {
/// Debug build with no optimizations.
Expand All @@ -17,8 +38,10 @@ pub enum BuildType {
/// Optimized release build.
Release,
/// Release build with debug info.
#[serde(alias = "relwithdebinfo", alias = "debugoptimized")]
RelWithDebInfo,
/// Minimized binary size release build.
#[serde(alias = "minsizerel", alias = "minsize")]
MinSizeRel,
}

Expand All @@ -44,36 +67,33 @@ impl BuildType {
}
}

impl std::str::FromStr for BuildSystem {
type Err = String;

/// Parses a build system string.
/// # Errors
/// Returns an error if the string does not match `cmake` or `meson`.
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"cmake" => Ok(Self::Cmake),
"meson" => Ok(Self::Meson),
_ => Err(format!("Unknown build system '{s}'. Valid: cmake, meson")),
}
}
}

impl std::str::FromStr for BuildType {
impl FromStr for BuildType {
type Err = String;

/// Parses a build type string, accepting canonical and system-specific aliases.
/// # Errors
/// Returns an error if the string does not match any known build type.
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"debug" => Ok(Self::Debug),
"release" => Ok(Self::Release),
"rel-with-deb-info" | "relwithdebinfo" | "debugoptimized" => Ok(Self::RelWithDebInfo),
"min-size-rel" | "minsizerel" | "minsize" => Ok(Self::MinSizeRel),
_ => Err(format!(
"Unknown build type '{s}'. Canonical: debug, release, rel-with-deb-info, min-size-rel"
)),
let types: &[(&[&str], Self)] = &[
(&["debug"], Self::Debug),
(&["release"], Self::Release),
(
&["rel-with-deb-info", "relwithdebinfo", "debugoptimized"],
Self::RelWithDebInfo,
),
(&["min-size-rel", "minsizerel", "minsize"], Self::MinSizeRel),
];

for (aliases, variant) in types {
for alias in *aliases {
if s.eq_ignore_ascii_case(alias) {
return Ok(*variant);
}
}
}

Err(format!(
"Unknown build type '{s}'. Canonical: debug, release, rel-with-deb-info, min-size-rel"
))
}
}
40 changes: 21 additions & 19 deletions src/cli/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ pub fn resolve_bool(positive: bool, negative: bool, config: Option<bool>, defaul
if positive {
return true;
}
config.unwrap_or(default)
match config {
Some(val) => val,
None => default,
}
}

/// 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(mut args: Args, config: &SetupConfig) -> Result<ResolvedArgs, String> {
let config_name = args.config_name.as_deref().unwrap_or("default");
if let Some(name) = &args.config_name {
if !config.configs.contains_key(name.as_str()) {
return Err(format!("Configuration '{name}' not found"));
}
}

let default = config.configs.get(config_name);

if args.config_name.is_some() && default.is_none() {
return Err(format!("Configuration '{config_name}' not found"));
}

let ssh = resolve_bool(
args.connection.ssh,
args.connection.https,
Expand Down Expand Up @@ -68,12 +69,14 @@ pub fn resolve_with_config(mut args: Args, config: &SetupConfig) -> Result<Resol
default.map(|e| e.clean),
false,
);
if args.build.cmake_flags.is_empty() {
args.build.cmake_flags = default.map_or_else(Vec::new, |e| e.cmake_flags.clone());
}
if args.build.meson_flags.is_empty() {
args.build.meson_flags = default.map_or_else(Vec::new, |e| e.meson_flags.clone());
}

let cmake_flags = Some(args.build.cmake_flags)
.filter(|f| !f.is_empty())
.unwrap_or_else(|| default.map_or_else(Vec::new, |e| e.cmake_flags.clone()));

let meson_flags = Some(args.build.meson_flags)
.filter(|f| !f.is_empty())
.unwrap_or_else(|| default.map_or_else(Vec::new, |e| e.meson_flags.clone()));

let repos = args.mono.repos.take();
let profile = args.mono.profile.take();
Expand All @@ -85,10 +88,9 @@ pub fn resolve_with_config(mut args: Args, config: &SetupConfig) -> Result<Resol
connection: ResolvedConnectionFlags { ssh, verbose },
diagnostic: ResolvedDiagnosticFlags { timing, dry_run },
build: ResolvedBuildFlags {
build_type: if let Some(s) = args.build.build_type {
s.parse::<BuildType>()?
} else {
default.map(|e| e.build_type.clone()).unwrap_or_default()
build_type: match args.build.build_type {
Some(s) => s.parse::<BuildType>()?,
None => default.map(|e| e.build_type).unwrap_or_default(),
},
build_dir: args
.build
Expand All @@ -98,8 +100,8 @@ pub fn resolve_with_config(mut args: Args, config: &SetupConfig) -> Result<Resol
build_system: args.build.build_system,
no_build,
clean,
cmake_flags: args.build.cmake_flags,
meson_flags: args.build.meson_flags,
cmake_flags,
meson_flags,
},
mono: ResolvedMonoFlags {
mono_repo,
Expand Down
23 changes: 12 additions & 11 deletions src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ use crate::{
};
use std::path::Path;

fn to_str(path: &Path) -> Result<&str, String> {
path
.to_str()
.ok_or_else(|| format!("Invalid path: {}", path.display()))
}

/// Runs `CMake` configuration and optionally builds the project in `build_path`.
/// # Errors
/// Returns an error if any `CMake` command fails.
pub fn cmake_build(
args: &ResolvedArgs,
build_path: &Path,
mono: bool,
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<(), String> {
let build_type_flag = format!("-DCMAKE_BUILD_TYPE={}", args.build.build_type.to_cmake());
let mut cmake_cmd = if mono {
Expand Down Expand Up @@ -53,13 +59,13 @@ pub fn meson_build(
args: &ResolvedArgs,
build_path: &Path,
source_path: &Path,
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<(), String> {
let buildtype_flag = format!("--buildtype={}", args.build.build_type.to_meson());
let mut meson_cmd = vec!["meson", "setup"];
meson_cmd.push(&buildtype_flag);
meson_cmd.push(build_path.to_str().ok_or("Invalid build path")?);
meson_cmd.push(source_path.to_str().ok_or("Invalid source path")?);
meson_cmd.push(to_str(build_path)?);
meson_cmd.push(to_str(source_path)?);
meson_cmd.extend(args.build.meson_flags.iter().map(String::as_str));

crate::time!(ctx.io.timing, ctx.io.output, "Meson setup", {
Expand All @@ -69,12 +75,7 @@ pub fn meson_build(
writeln!(ctx.io.output, "Building project\n").ok();
crate::time!(ctx.io.timing, ctx.io.output, "Meson compile", {
ctx.runner.run(
&[
"meson",
"compile",
"-C",
build_path.to_str().ok_or("Invalid build path")?,
],
&["meson", "compile", "-C", to_str(build_path)?],
None,
&mut ctx.io,
)?;
Expand All @@ -92,7 +93,7 @@ pub fn build_project(
source_path: &Path,
build_system: BuildSystem,
mono: bool,
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<(), String> {
match build_system {
BuildSystem::Cmake => cmake_build(args, build_path, mono, ctx),
Expand Down
94 changes: 94 additions & 0 deletions src/commands/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use crate::{
cli::{ConfigAction, ProfileAction, WorkspaceAction},
config::{
add_config, create_default_config, list_configs, remove_config, ConfigEntry, SetupConfig,
},
ctx::{with_runner, IoCtx},
profile::{add_profile, list_profiles, remove_profile},
workspace::resolve_workspace,
};
use std::{error::Error, path::PathBuf};

/// Handles configuration-related subcommands.
/// # Errors
/// Returns an error if configuration initializing, addition, or removal fails.
pub fn handle_config_cmd(
action: ConfigAction,
config: &mut SetupConfig,
config_path: PathBuf,
yes: bool,
io: &mut IoCtx,
) -> Result<(), Box<dyn Error>> {
match action {
ConfigAction::Init => create_default_config(config_path, yes, io)?,
ConfigAction::List => list_configs(config, io),
ConfigAction::Remove { name } => remove_config(config, &name, yes, io)?,
ConfigAction::Add {
name,
connection,
build,
mono,
diagnostic,
} => {
let entry = ConfigEntry::from_flags(&connection, &build, &mono, &diagnostic);
add_config(config, &name, entry, yes, io)?;
}
}
Ok(())
}

/// Handles profile-related subcommands.
/// # Errors
/// 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,
yes: bool,
io: &mut IoCtx,
) -> Result<(), Box<dyn Error>> {
match action {
ProfileAction::List => list_profiles(config, io),
ProfileAction::Remove { name } => remove_profile(config, &name, yes, io)?,
ProfileAction::Add { name, repos } => {
let vals = std::iter::once(name).chain(repos).collect::<Vec<_>>();
add_profile(config, &vals, yes, io)?;
}
}
Ok(())
}

/// Handles workspace-related subcommands.
/// # Errors
/// Returns an error if resolving, updating, cleaning, or fetching status for the workspace fails.
pub fn handle_workspace_cmd(action: WorkspaceAction, io: IoCtx) -> Result<(), Box<dyn Error>> {
match action {
WorkspaceAction::Update {
path,
mono_dir,
build_dir,
} => {
let ws = resolve_workspace(path.as_deref(), mono_dir.as_deref(), build_dir.as_deref())?;
with_runner(io, |ctx| ws.update(ctx).map_err(Into::into))?;
}
WorkspaceAction::Status {
path,
mono_dir,
build_dir,
fetch,
} => {
let ws = resolve_workspace(path.as_deref(), mono_dir.as_deref(), build_dir.as_deref())?;
let mut status_io = io;
status_io.dry_run = false;
with_runner(status_io, |ctx| ws.status(fetch, ctx).map_err(Into::into))?;
}
WorkspaceAction::Clean {
path,
mono_dir,
build_dir,
} => {
let ws = resolve_workspace(path.as_deref(), mono_dir.as_deref(), build_dir.as_deref())?;
with_runner(io, |ctx| ws.clean(ctx).map_err(Into::into))?;
}
}
Ok(())
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ pub mod single;
pub use single::single_repo_mode;
pub mod setup;
pub use setup::{configure_and_build, extract_repo_input, prepare_build_dir};
pub mod handlers;
pub use handlers::{handle_config_cmd, handle_profile_cmd, handle_workspace_cmd};
2 changes: 1 addition & 1 deletion src/commands/mono/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub fn clone_mono_repos(
repos: &[String],
repos_path: &std::path::Path,
ssh: bool,
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<(), String> {
writeln!(ctx.io.output, "Cloning repositories").ok();
crate::time!(ctx.io.timing, ctx.io.output, "Clone", {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/mono/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub fn mono_repo_mode(
args: &ResolvedArgs,
config: &SetupConfig,
base_dir: &Path,
ctx: &mut RunCtx<'_>,
ctx: &mut RunCtx<'_, '_>,
) -> Result<(), String> {
let total = std::time::Instant::now();

Expand Down
Loading
Loading