diff --git a/Cargo.toml b/Cargo.toml index 509f766..a466c0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "star-setup" -version = "0.4.8" +version = "0.4.9" edition = "2021" repository = "https://github.com/star-setup/core" description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems" diff --git a/src/build/cmake/build.rs b/src/build/cmake/build.rs new file mode 100644 index 0000000..4d04968 --- /dev/null +++ b/src/build/cmake/build.rs @@ -0,0 +1,39 @@ +use crate::{build::common::run_timed, ctx::RunCtx, resolve::ResolvedArgs}; +use std::path::Path; + +/// 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<'_, '_>, +) -> Result<(), String> { + let build_type_flag = format!("-DCMAKE_BUILD_TYPE={}", args.build.build_type.to_cmake()); + let mut cmake_cmd = if mono { + vec!["cmake", "-DBUILD_LOCAL=ON", &build_type_flag, ".."] + } else { + vec!["cmake", "..", &build_type_flag] + }; + cmake_cmd.extend(args.build.cmake_flags.iter().map(String::as_str)); + + run_timed(&cmake_cmd, Some(build_path), "CMake configure", ctx)?; + + if !args.build.no_build { + writeln!(ctx.io.output, "Building project").ok(); + run_timed( + &[ + "cmake", + "--build", + ".", + "--config", + args.build.build_type.to_cmake(), + ], + Some(build_path), + "CMake build", + ctx, + )?; + } + Ok(()) +} diff --git a/src/build/cmake/config.rs b/src/build/cmake/config.rs new file mode 100644 index 0000000..319ef13 --- /dev/null +++ b/src/build/cmake/config.rs @@ -0,0 +1,49 @@ +use crate::{ + build::common::write_mono_repo_config, + ctx::{IoCtx, RunFlags}, +}; +use std::path::Path; + +/// Generates a root `CMakeLists.txt` wiring all repositories as subdirectories. +/// # Errors +/// Returns an error if the `CMakeLists.txt` file cannot be written to `mono_dir` +pub fn create_mono_repo_cmakelists( + mono_dir: &Path, + repos: &[String], + io: &mut IoCtx<'_>, + flags: RunFlags, +) -> Result<(), String> { + writeln!(io.output, " Creating CMake configuration").ok(); + write_mono_repo_config( + mono_dir, + repos, + io, + flags, + "CMakeLists.txt", + |modules| modules.join("\n "), + |modules_cmake| { + format!( + "cmake_minimum_required(VERSION 3.23) + +project(star_setup LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 20) + +set(MONO_REPO_MODULES + {modules_cmake} +) + +foreach(module IN LISTS MONO_REPO_MODULES) + if(EXISTS \"${{CMAKE_CURRENT_SOURCE_DIR}}/repos/${{module}}/CMakeLists.txt\") + add_subdirectory(repos/${{module}}) + else() + message(WARNING \"Module ${{module}} not found or missing CMakeLists.txt\") + endif() +endforeach() + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER \"External\") +" + ) + }, + ) +} diff --git a/src/build/cmake/mod.rs b/src/build/cmake/mod.rs new file mode 100644 index 0000000..5821416 --- /dev/null +++ b/src/build/cmake/mod.rs @@ -0,0 +1,4 @@ +pub mod build; +pub use build::cmake_build; +pub mod config; +pub use config::create_mono_repo_cmakelists; diff --git a/src/build/common.rs b/src/build/common.rs new file mode 100644 index 0000000..1c7e3d1 --- /dev/null +++ b/src/build/common.rs @@ -0,0 +1,58 @@ +use crate::{ + ctx::{IoCtx, RunCtx, RunFlags}, + repository::repo_dir_name, + utils::{dry_run_or_do, report_summary}, +}; +use std::{fs::write, path::Path}; + +/// Runs `cmd`, timing it if `ctx.flags.timing` is set. +/// # Errors +/// Returns an error if the command fails. +pub fn run_timed( + cmd: &[&str], + cwd: Option<&Path>, + label: &str, + ctx: &mut RunCtx<'_, '_>, +) -> Result<(), String> { + crate::time!(ctx.flags.timing, ctx.io.output, label, { + ctx.runner.run(cmd, cwd, ctx.flags, ctx.io.output)?; + }); + Ok(()) +} + +/// Shared helper to generate, write, and log monorepo build configuration files. +/// # Errors +/// Returns an error if the config file cannot be written to `mono_dir`. +pub fn write_mono_repo_config( + mono_dir: &Path, + repos: &[String], + io: &mut IoCtx<'_>, + flags: RunFlags, + filename: &str, + format_modules: impl Fn(&[String]) -> String, + render_template: impl Fn(&str) -> String, +) -> Result<(), String> { + let module_names: Vec = repos.iter().map(|r| repo_dir_name(r)).collect(); + let modules_str = format_modules(&module_names); + let content = render_template(&modules_str); + let file_path = mono_dir.join(filename); + + dry_run_or_do( + "create file", + "Creating", + &file_path, + io, + flags, + &format!("Generate {filename}"), + || write(&file_path, content).map_err(|e| e.to_string()), + )?; + + report_summary( + io, + flags, + &format!("create root {filename} at {}\n", mono_dir.display()), + &format!("Created root {filename} at {}\n", mono_dir.display()), + ); + + Ok(()) +} diff --git a/src/cli/build/detect.rs b/src/build/detect.rs similarity index 97% rename from src/cli/build/detect.rs rename to src/build/detect.rs index 9493c5b..ae04f61 100644 --- a/src/cli/build/detect.rs +++ b/src/build/detect.rs @@ -1,4 +1,4 @@ -use crate::{cli::BuildSystem, ctx::RunCtx, prompts::ask_choice}; +use crate::{build::BuildSystem, ctx::RunCtx, prompts::ask_choice}; use std::path::{Path, PathBuf}; fn pick_build_system( diff --git a/src/build/dispatch.rs b/src/build/dispatch.rs new file mode 100644 index 0000000..cd379fd --- /dev/null +++ b/src/build/dispatch.rs @@ -0,0 +1,74 @@ +use crate::{ + build::{ + cmake_build, create_mono_repo_cmakelists, create_mono_repo_mesonbuild, + create_mono_repo_package_json, hoist_wraps, meson_build, npm_build, BuildSystem, + BuildSystem::Cmake, BuildSystem::Meson, BuildSystem::Npm, + }, + ctx::RunCtx, + repository::repo_dir_name, + resolve::ResolvedArgs, +}; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +/// Detects and dispatches to the appropriate build system. +/// # Errors +/// Returns an error if detection or the build system command fails. +pub fn build_project( + args: &ResolvedArgs, + build_path: &Path, + source_path: &Path, + build_system: BuildSystem, + mono: bool, + ctx: &mut RunCtx<'_, '_>, +) -> Result<(), String> { + let result = match build_system { + BuildSystem::Cmake => cmake_build(args, build_path, mono, ctx), + BuildSystem::Meson => meson_build(args, build_path, source_path, ctx), + BuildSystem::Npm => npm_build(args, source_path, mono, ctx), + }; + writeln!(ctx.io.output).ok(); + result +} + +/// Generates root build configuration files for the mono-repo. +/// # Errors +/// Returns an error if config file generation or wrap hoisting fails. +pub fn generate_mono_config( + build_system: BuildSystem, + mono_repo_path: &std::path::Path, + repos_path: &std::path::Path, + repo_dirs: &[PathBuf], + repos: &[String], + ctx: &mut RunCtx<'_, '_>, +) -> Result>, String> { + writeln!(ctx.io.output, " Creating mono-repo configuration").ok(); + match build_system { + Cmake => { + create_mono_repo_cmakelists(mono_repo_path, repos, &mut ctx.io, ctx.flags)?; + Ok(None) + } + Meson => { + let map = hoist_wraps(repos_path, repo_dirs, &mut ctx.io, ctx.flags)?; + let subproject_names: Vec = repos + .iter() + .map(|r| { + let dir = repo_dir_name(r); + map + .iter() + .find(|(_, v)| *v == &dir) + .map(|(k, _)| k.clone()) + .unwrap_or(dir) + }) + .collect(); + create_mono_repo_mesonbuild(mono_repo_path, &subproject_names, &mut ctx.io, ctx.flags)?; + Ok(Some(map)) + } + Npm => { + create_mono_repo_package_json(mono_repo_path, repos_path, repos, &mut ctx.io, ctx.flags)?; + Ok(None) + } + } +} diff --git a/src/build/meson/build.rs b/src/build/meson/build.rs new file mode 100644 index 0000000..5b4cf85 --- /dev/null +++ b/src/build/meson/build.rs @@ -0,0 +1,37 @@ +use crate::{build::common::run_timed, ctx::RunCtx, resolve::ResolvedArgs}; +use std::path::Path; + +fn to_str(path: &Path) -> Result<&str, String> { + path + .to_str() + .ok_or_else(|| format!("Invalid path: {}", path.display())) +} + +/// Runs Meson configuration and optionally builds the project in `build_path`. +/// # Errors +/// Returns an error if any Meson command fails. +pub fn meson_build( + args: &ResolvedArgs, + build_path: &Path, + source_path: &Path, + 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(to_str(build_path)?); + meson_cmd.push(to_str(source_path)?); + meson_cmd.extend(args.build.meson_flags.iter().map(String::as_str)); + + run_timed(&meson_cmd, None, "Meson setup", ctx)?; + if !args.build.no_build { + writeln!(ctx.io.output, "Building project").ok(); + run_timed( + &["meson", "compile", "-C", to_str(build_path)?], + None, + "Meson compile", + ctx, + )?; + } + Ok(()) +} diff --git a/src/build/meson/config.rs b/src/build/meson/config.rs new file mode 100644 index 0000000..1f5ca94 --- /dev/null +++ b/src/build/meson/config.rs @@ -0,0 +1,48 @@ +use crate::{ + build::common::write_mono_repo_config, + ctx::{IoCtx, RunFlags}, +}; +use std::path::Path; + +/// Generates a root `meson.build` wiring all repositories as subprojects. +/// # Errors +/// Returns an error if the `meson.build` file cannot be written to `mono_dir`. +pub fn create_mono_repo_mesonbuild( + mono_dir: &Path, + repos: &[String], + io: &mut IoCtx<'_>, + flags: RunFlags, +) -> Result<(), String> { + writeln!(io.output, " Creating Meson configuration").ok(); + write_mono_repo_config( + mono_dir, + repos, + io, + flags, + "meson.build", + |modules| { + modules + .iter() + .map(|m| format!(" '{m}'")) + .collect::>() + .join(",\n") + }, + |modules_meson| { + format!( + "project('star_setup', 'cpp', + default_options: ['cpp_std=c++20'], + subproject_dir: 'repos' +) + +modules = [ +{modules_meson}, +] + +foreach module : modules + subproject(module) +endforeach +" + ) + }, + ) +} diff --git a/src/build/meson/mod.rs b/src/build/meson/mod.rs new file mode 100644 index 0000000..682d484 --- /dev/null +++ b/src/build/meson/mod.rs @@ -0,0 +1,6 @@ +pub mod build; +pub use build::meson_build; +pub mod config; +pub use config::create_mono_repo_mesonbuild; +pub mod wraps; +pub use wraps::{hoist_wraps, parse_project_name, parse_provide_pairs}; diff --git a/src/commands/mono/wraps.rs b/src/build/meson/wraps.rs similarity index 100% rename from src/commands/mono/wraps.rs rename to src/build/meson/wraps.rs diff --git a/src/build/mod.rs b/src/build/mod.rs new file mode 100644 index 0000000..d058bee --- /dev/null +++ b/src/build/mod.rs @@ -0,0 +1,19 @@ +pub mod types; +pub use types::{BuildSystem, BuildType}; +pub mod detect; +pub use detect::{detect_build_system, detect_mono_build_system}; +pub mod dispatch; +pub use dispatch::{build_project, generate_mono_config}; +pub mod common; + +pub mod cmake; +pub use cmake::{cmake_build, create_mono_repo_cmakelists}; +pub mod meson; +pub use meson::{ + create_mono_repo_mesonbuild, hoist_wraps, meson_build, parse_project_name, parse_provide_pairs, +}; +pub mod npm; +pub use npm::{ + create_mono_repo_package_json, generate_watch_scripts, maybe_open_dev_server, npm_build, + open_dev_server, open_watch_scripts, read_package_json, resolve_dev_command, +}; diff --git a/src/build/npm/build.rs b/src/build/npm/build.rs new file mode 100644 index 0000000..eaef506 --- /dev/null +++ b/src/build/npm/build.rs @@ -0,0 +1,35 @@ +use crate::{ + build::{common::run_timed, read_package_json}, + ctx::RunCtx, + resolve::ResolvedArgs, +}; +use std::path::Path; + +/// Runs `npm install` and optionally builds the project in `source_path`. +/// # Errors +/// Returns an error if any npm command fails. +pub fn npm_build( + args: &ResolvedArgs, + source_path: &Path, + is_mono: bool, + ctx: &mut RunCtx<'_, '_>, +) -> Result<(), String> { + writeln!(ctx.io.output, "Installing dependencies").ok(); + run_timed(&["npm", "install"], Some(source_path), "npm install", ctx)?; + if !args.build.no_build && !is_mono { + let has_build = read_package_json(source_path, "skipping build", &mut ctx.io, ctx.flags) + .is_some_and(|j| j.get("scripts").and_then(|s| s.get("build")).is_some()); + if has_build { + writeln!(ctx.io.output, "Building project").ok(); + run_timed( + &["npm", "run", "build"], + Some(source_path), + "npm build", + ctx, + )?; + } else if ctx.flags.verbose { + writeln!(ctx.io.output, " No build script found, skipping build").ok(); + } + } + Ok(()) +} diff --git a/src/build/npm/config.rs b/src/build/npm/config.rs new file mode 100644 index 0000000..8ab4101 --- /dev/null +++ b/src/build/npm/config.rs @@ -0,0 +1,71 @@ +use crate::{ + build::read_package_json, + ctx::{IoCtx, RunFlags}, + repository::repo_dir_name, + utils::{dry_run_or_do, report_summary}, +}; +use std::{fs::write, path::Path}; + +/// Generates a root `package.json` wiring all repositories as npm workspaces. +/// # Errors +/// Returns an error if the `package.json` file cannot be written to `mono_dir`. +pub fn create_mono_repo_package_json( + mono_dir: &Path, + repos_path: &Path, + repos: &[String], + io: &mut IoCtx<'_>, + flags: RunFlags, +) -> Result<(), String> { + writeln!(io.output, " Creating npm workspace configuration").ok(); + + let module_names: Vec = repos.iter().map(|r| repo_dir_name(r)).collect(); + + let workspaces = module_names + .iter() + .map(|m| format!(" \"repos/{m}\"")) + .collect::>() + .join(",\n"); + + // Read package name from each lib repo (skip first — test repo) + let mut overrides = Vec::new(); + for (i, dir) in module_names.iter().enumerate() { + if i == 0 { + continue; + } + if let Some(json) = read_package_json(&repos_path.join(dir), "skipping override", io, flags) { + if let Some(name) = json.get("name").and_then(|n| n.as_str()) { + overrides.push(format!(" \"{name}\": \"*\"")); + } + } + } + + let overrides_block = if overrides.is_empty() { + String::new() + } else { + format!(",\n \"overrides\": {{\n{}\n }}", overrides.join(",\n")) + }; + + let content = format!( + "{{\n \"name\": \"star-setup-workspace\",\n \"private\": true,\n \"workspaces\": [\n{workspaces}\n ]{overrides_block}\n}}\n" + ); + + let file_path = mono_dir.join("package.json"); + dry_run_or_do( + "create file", + "Creating", + &file_path, + io, + flags, + "Generate package.json", + || write(&file_path, content).map_err(|e| e.to_string()), + )?; + + report_summary( + io, + flags, + &format!("create root package.json at {}\n", mono_dir.display()), + &format!("Created root package.json at {}\n", mono_dir.display()), + ); + + Ok(()) +} diff --git a/src/commands/dev.rs b/src/build/npm/dev.rs similarity index 98% rename from src/commands/dev.rs rename to src/build/npm/dev.rs index b801d41..99794ed 100644 --- a/src/commands/dev.rs +++ b/src/build/npm/dev.rs @@ -1,12 +1,12 @@ #[cfg(not(target_os = "windows"))] use crate::utils::process::resolve_exe_args; use crate::{ - cli::{ + build::{ + read_package_json, BuildSystem::{self, Npm}, - ResolvedArgs, }, - commands::read_package_json, ctx::{IoCtx, RunCtx, RunFlags}, + resolve::ResolvedArgs, }; use serde_json::Value; use std::{ diff --git a/src/build/npm/mod.rs b/src/build/npm/mod.rs new file mode 100644 index 0000000..62e37d7 --- /dev/null +++ b/src/build/npm/mod.rs @@ -0,0 +1,10 @@ +pub mod build; +pub use build::npm_build; +pub mod config; +pub use config::create_mono_repo_package_json; +pub mod watch; +pub use watch::{generate_watch_scripts, open_watch_scripts}; +pub mod dev; +pub use dev::{maybe_open_dev_server, open_dev_server, resolve_dev_command}; +pub mod package; +pub use package::read_package_json; diff --git a/src/commands/npm.rs b/src/build/npm/package.rs similarity index 100% rename from src/commands/npm.rs rename to src/build/npm/package.rs diff --git a/src/commands/mono/watch.rs b/src/build/npm/watch.rs similarity index 99% rename from src/commands/mono/watch.rs rename to src/build/npm/watch.rs index 432cb05..6f1f3be 100644 --- a/src/commands/mono/watch.rs +++ b/src/build/npm/watch.rs @@ -1,5 +1,5 @@ use crate::{ - commands::read_package_json, + build::read_package_json, ctx::{IoCtx, RunFlags}, repository::repo_dir_name, utils::{dry_run_or_do, report_summary}, diff --git a/src/cli/build/types.rs b/src/build/types.rs similarity index 100% rename from src/cli/build/types.rs rename to src/build/types.rs diff --git a/src/cli/args.rs b/src/cli/args.rs index a52987e..2e3616f 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -1,9 +1,10 @@ use crate::{ cli::{ - resolve_with_config, BuildFlags, ConfigCommand, ConnectionFlags, DiagnosticFlags, - MonoRepoFlags, ProfileCommand, ResolvedArgs, WorkspaceCommand, + BuildFlags, ConfigCommand, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ProfileCommand, + WorkspaceCommand, }, config::Config, + resolve::{resolve_with_config, ResolvedArgs}, }; use clap::{Parser, Subcommand}; diff --git a/src/cli/build/mod.rs b/src/cli/build/mod.rs deleted file mode 100644 index f5a51ff..0000000 --- a/src/cli/build/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod types; -pub use types::{BuildSystem, BuildType}; -pub mod detect; -pub use detect::{detect_build_system, detect_mono_build_system}; diff --git a/src/cli/flags.rs b/src/cli/flags.rs index 6b56e9e..c327ec7 100644 --- a/src/cli/flags.rs +++ b/src/cli/flags.rs @@ -1,4 +1,4 @@ -use crate::cli::BuildSystem; +use crate::build::BuildSystem; use clap::{ArgAction::Append, Args as ClapArgs}; #[allow(clippy::struct_excessive_bools)] diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 732c07b..4e6ac3b 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,13 +1,7 @@ pub mod args; pub use args::Args; -pub mod build; -pub use build::{detect_build_system, detect_mono_build_system, BuildSystem, BuildType}; pub mod flags; pub use flags::{BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags}; -pub mod resolve; -pub use resolve::{resolve_bool, resolve_with_config}; -pub mod resolved; -pub use resolved::{ResolvedArgs, ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags}; pub mod commands; pub use commands::{ ConfigAction, ConfigCommand, ProfileAction, ProfileCommand, WorkspaceAction, WorkspaceCommand, diff --git a/src/commands/build.rs b/src/commands/build.rs deleted file mode 100644 index d21bf2f..0000000 --- a/src/commands/build.rs +++ /dev/null @@ -1,142 +0,0 @@ -use crate::{ - cli::{BuildSystem, ResolvedArgs}, - commands::read_package_json, - ctx::RunCtx, -}; -use std::path::Path; - -/// Runs `cmd`, timing it if `ctx.flags.timing` is set. -/// # Errors -/// Returns an error if the command fails. -fn run_timed( - cmd: &[&str], - cwd: Option<&Path>, - label: &str, - ctx: &mut RunCtx<'_, '_>, -) -> Result<(), String> { - crate::time!(ctx.flags.timing, ctx.io.output, label, { - ctx.runner.run(cmd, cwd, ctx.flags, ctx.io.output)?; - }); - Ok(()) -} - -/// 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<'_, '_>, -) -> Result<(), String> { - let build_type_flag = format!("-DCMAKE_BUILD_TYPE={}", args.build.build_type.to_cmake()); - let mut cmake_cmd = if mono { - vec!["cmake", "-DBUILD_LOCAL=ON", &build_type_flag, ".."] - } else { - vec!["cmake", "..", &build_type_flag] - }; - cmake_cmd.extend(args.build.cmake_flags.iter().map(String::as_str)); - - run_timed(&cmake_cmd, Some(build_path), "CMake configure", ctx)?; - - if !args.build.no_build { - writeln!(ctx.io.output, "Building project").ok(); - run_timed( - &[ - "cmake", - "--build", - ".", - "--config", - args.build.build_type.to_cmake(), - ], - Some(build_path), - "CMake build", - ctx, - )?; - } - Ok(()) -} - -fn to_str(path: &Path) -> Result<&str, String> { - path - .to_str() - .ok_or_else(|| format!("Invalid path: {}", path.display())) -} - -/// Runs Meson configuration and optionally builds the project in `build_path`. -/// # Errors -/// Returns an error if any Meson command fails. -pub fn meson_build( - args: &ResolvedArgs, - build_path: &Path, - source_path: &Path, - 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(to_str(build_path)?); - meson_cmd.push(to_str(source_path)?); - meson_cmd.extend(args.build.meson_flags.iter().map(String::as_str)); - - run_timed(&meson_cmd, None, "Meson setup", ctx)?; - if !args.build.no_build { - writeln!(ctx.io.output, "Building project").ok(); - run_timed( - &["meson", "compile", "-C", to_str(build_path)?], - None, - "Meson compile", - ctx, - )?; - } - Ok(()) -} - -/// Runs `npm install` and optionally builds the project in `source_path`. -/// # Errors -/// Returns an error if any npm command fails. -pub fn npm_build( - args: &ResolvedArgs, - source_path: &Path, - is_mono: bool, - ctx: &mut RunCtx<'_, '_>, -) -> Result<(), String> { - writeln!(ctx.io.output, "Installing dependencies").ok(); - run_timed(&["npm", "install"], Some(source_path), "npm install", ctx)?; - if !args.build.no_build && !is_mono { - let has_build = read_package_json(source_path, "skipping build", &mut ctx.io, ctx.flags) - .is_some_and(|j| j.get("scripts").and_then(|s| s.get("build")).is_some()); - if has_build { - writeln!(ctx.io.output, "Building project").ok(); - run_timed( - &["npm", "run", "build"], - Some(source_path), - "npm build", - ctx, - )?; - } else if ctx.flags.verbose { - writeln!(ctx.io.output, " No build script found, skipping build").ok(); - } - } - Ok(()) -} - -/// Detects and dispatches to the appropriate build system. -/// # Errors -/// Returns an error if detection or the build system command fails. -pub fn build_project( - args: &ResolvedArgs, - build_path: &Path, - source_path: &Path, - build_system: BuildSystem, - mono: bool, - ctx: &mut RunCtx<'_, '_>, -) -> Result<(), String> { - let result = match build_system { - BuildSystem::Cmake => cmake_build(args, build_path, mono, ctx), - BuildSystem::Meson => meson_build(args, build_path, source_path, ctx), - BuildSystem::Npm => npm_build(args, source_path, mono, ctx), - }; - writeln!(ctx.io.output).ok(); - result -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9a86894..214c396 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,13 +1,8 @@ -pub mod build; -pub use build::{build_project, cmake_build, meson_build, npm_build}; pub mod header; pub use header::{print_mode_header, ModeHeader}; pub mod mono; pub use mono::{ - build_repo_list, create_mono_repo_cmakelists, create_mono_repo_mesonbuild, - create_mono_repo_package_json, generate_mono_config, generate_watch_scripts, hoist_wraps, - mono_repo_mode, resolve_repos_for_mono, resolve_setup_paths, resolve_test_repo, - wraps::{parse_project_name, parse_provide_pairs}, + build_repo_list, mono_repo_mode, resolve_repos_for_mono, resolve_setup_paths, resolve_test_repo, }; pub mod single; pub use single::single_repo_mode; @@ -15,7 +10,3 @@ pub mod setup; pub use setup::{extract_repo_input, prepare_build_dir}; pub mod handlers; pub use handlers::{handle_config_cmd, handle_profile_cmd, handle_workspace_cmd}; -pub mod npm; -pub use npm::read_package_json; -pub mod dev; -pub use dev::{maybe_open_dev_server, open_dev_server, resolve_dev_command}; diff --git a/src/commands/mono/config.rs b/src/commands/mono/config.rs deleted file mode 100644 index b62807a..0000000 --- a/src/commands/mono/config.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::{ - commands::read_package_json, - ctx::{IoCtx, RunFlags}, - repository::repo_dir_name, - utils::{dry_run_or_do, report_summary}, -}; -use std::{fs::write, path::Path}; - -/// Shared helper to generate, write, and log monorepo build configuration files. -fn write_mono_repo_config( - mono_dir: &Path, - repos: &[String], - io: &mut IoCtx<'_>, - flags: RunFlags, - filename: &str, - format_modules: impl Fn(&[String]) -> String, - render_template: impl Fn(&str) -> String, -) -> Result<(), String> { - let module_names: Vec = repos.iter().map(|r| repo_dir_name(r)).collect(); - let modules_str = format_modules(&module_names); - let content = render_template(&modules_str); - let file_path = mono_dir.join(filename); - - dry_run_or_do( - "create file", - "Creating", - &file_path, - io, - flags, - &format!("Generate {filename}"), - || write(&file_path, content).map_err(|e| e.to_string()), - )?; - - report_summary( - io, - flags, - &format!("create root {filename} at {}\n", mono_dir.display()), - &format!("Created root {filename} at {}\n", mono_dir.display()), - ); - - Ok(()) -} - -/// Generates a root `CMakeLists.txt` wiring all repositories as subdirectories. -/// # Errors -/// Returns an error if the `CMakeLists.txt` file cannot be written to `mono_dir` -pub fn create_mono_repo_cmakelists( - mono_dir: &Path, - repos: &[String], - io: &mut IoCtx<'_>, - flags: RunFlags, -) -> Result<(), String> { - writeln!(io.output, " Creating CMake configuration").ok(); - write_mono_repo_config( - mono_dir, - repos, - io, - flags, - "CMakeLists.txt", - |modules| modules.join("\n "), - |modules_cmake| { - format!( - "cmake_minimum_required(VERSION 3.23) - -project(star_setup LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 20) - -set(MONO_REPO_MODULES - {modules_cmake} -) - -foreach(module IN LISTS MONO_REPO_MODULES) - if(EXISTS \"${{CMAKE_CURRENT_SOURCE_DIR}}/repos/${{module}}/CMakeLists.txt\") - add_subdirectory(repos/${{module}}) - else() - message(WARNING \"Module ${{module}} not found or missing CMakeLists.txt\") - endif() -endforeach() - -set_property(GLOBAL PROPERTY USE_FOLDERS ON) -set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER \"External\") -" - ) - }, - ) -} - -/// Generates a root `meson.build` wiring all repositories as subprojects. -/// # Errors -/// Returns an error if the `meson.build` file cannot be written to `mono_dir`. -pub fn create_mono_repo_mesonbuild( - mono_dir: &Path, - repos: &[String], - io: &mut IoCtx<'_>, - flags: RunFlags, -) -> Result<(), String> { - writeln!(io.output, " Creating Meson configuration").ok(); - write_mono_repo_config( - mono_dir, - repos, - io, - flags, - "meson.build", - |modules| { - modules - .iter() - .map(|m| format!(" '{m}'")) - .collect::>() - .join(",\n") - }, - |modules_meson| { - format!( - "project('star_setup', 'cpp', - default_options: ['cpp_std=c++20'], - subproject_dir: 'repos' -) - -modules = [ -{modules_meson}, -] - -foreach module : modules - subproject(module) -endforeach -" - ) - }, - ) -} - -/// Generates a root `package.json` wiring all repositories as npm workspaces. -/// # Errors -/// Returns an error if the `package.json` file cannot be written to `mono_dir`. -pub fn create_mono_repo_package_json( - mono_dir: &Path, - repos_path: &Path, - repos: &[String], - io: &mut IoCtx<'_>, - flags: RunFlags, -) -> Result<(), String> { - writeln!(io.output, " Creating npm workspace configuration").ok(); - - let module_names: Vec = repos.iter().map(|r| repo_dir_name(r)).collect(); - - let workspaces = module_names - .iter() - .map(|m| format!(" \"repos/{m}\"")) - .collect::>() - .join(",\n"); - - // Read package name from each lib repo (skip first — test repo) - let mut overrides = Vec::new(); - for (i, dir) in module_names.iter().enumerate() { - if i == 0 { - continue; - } - if let Some(json) = read_package_json(&repos_path.join(dir), "skipping override", io, flags) { - if let Some(name) = json.get("name").and_then(|n| n.as_str()) { - overrides.push(format!(" \"{name}\": \"*\"")); - } - } - } - - let overrides_block = if overrides.is_empty() { - String::new() - } else { - format!(",\n \"overrides\": {{\n{}\n }}", overrides.join(",\n")) - }; - - let content = format!( - "{{\n \"name\": \"star-setup-workspace\",\n \"private\": true,\n \"workspaces\": [\n{workspaces}\n ]{overrides_block}\n}}\n" - ); - - let file_path = mono_dir.join("package.json"); - dry_run_or_do( - "create file", - "Creating", - &file_path, - io, - flags, - "Generate package.json", - || write(&file_path, content).map_err(|e| e.to_string()), - )?; - - report_summary( - io, - flags, - &format!("create root package.json at {}\n", mono_dir.display()), - &format!("Created root package.json at {}\n", mono_dir.display()), - ); - - Ok(()) -} diff --git a/src/commands/mono/display.rs b/src/commands/mono/display.rs index 1b1b9ce..b3b1ded 100644 --- a/src/commands/mono/display.rs +++ b/src/commands/mono/display.rs @@ -1,5 +1,5 @@ use crate::{ - cli::{BuildSystem, BuildSystem::Npm}, + build::{BuildSystem, BuildSystem::Npm}, ctx::{IoCtx, RunFlags}, repository::repo_dir_name, }; diff --git a/src/commands/mono/mod.rs b/src/commands/mono/mod.rs index 3f72cc7..f94ace5 100644 --- a/src/commands/mono/mod.rs +++ b/src/commands/mono/mod.rs @@ -1,7 +1,3 @@ -pub mod config; -pub use config::{ - create_mono_repo_cmakelists, create_mono_repo_mesonbuild, create_mono_repo_package_json, -}; pub mod display; pub use display::{print_setup_complete, resolve_setup_paths}; pub mod mode; @@ -10,9 +6,5 @@ pub mod resolve; 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; -pub use setup::{build_repo_list, generate_mono_config}; -pub mod watch; -pub use watch::{generate_watch_scripts, open_watch_scripts}; +pub use setup::build_repo_list; diff --git a/src/commands/mono/mode.rs b/src/commands/mono/mode.rs index c67eeb1..f163593 100644 --- a/src/commands/mono/mode.rs +++ b/src/commands/mono/mode.rs @@ -1,10 +1,13 @@ use crate::{ - cli::{detect_mono_build_system, BuildSystem::Npm, ResolvedArgs}, + build::{ + build_project, detect_mono_build_system, generate_mono_config, generate_watch_scripts, + maybe_open_dev_server, open_watch_scripts, BuildSystem::Npm, + }, commands::{ - build_project, build_repo_list, maybe_open_dev_server, + build_repo_list, mono::{ display::{resolve_setup_paths, SetupPaths}, - generate_mono_config, generate_watch_scripts, open_watch_scripts, print_setup_complete, + print_setup_complete, resolve::{resolve_profile, resolve_test_repo_for_mono}, }, prepare_build_dir, print_mode_header, resolve_repos_for_mono, ModeHeader, @@ -12,6 +15,7 @@ use crate::{ config::Config, ctx::RunCtx, repository::{clone_repos, repo_dir_name}, + resolve::ResolvedArgs, utils::{detect_or_dry_run, dry_run_or_do}, }; use std::{ @@ -30,8 +34,8 @@ pub fn mono_repo_mode( ctx: &mut RunCtx<'_, '_>, ) -> Result<(), String> { let total = Instant::now(); - let profile = resolve_profile(args, config, &mut ctx.io)?; - let test_repo = resolve_test_repo_for_mono(args, profile)?; + let profile = resolve_profile(args, config); + let test_repo = resolve_test_repo_for_mono(args)?; let deps = resolve_repos_for_mono(args, profile); let repos = build_repo_list(&test_repo, &deps); diff --git a/src/commands/mono/resolve.rs b/src/commands/mono/resolve.rs index af3a62d..c827db9 100644 --- a/src/commands/mono/resolve.rs +++ b/src/commands/mono/resolve.rs @@ -1,9 +1,4 @@ -use crate::{ - cli::ResolvedArgs, - config::Config, - ctx::IoCtx, - profile::{list_profiles, Profile}, -}; +use crate::{config::Config, profile::Profile, resolve::ResolvedArgs}; /// Normalizes a repository input to `username/repo` format. /// # Errors @@ -30,34 +25,22 @@ pub fn resolve_test_repo(repo_input: &str) -> Result { } /// Resolves the named profile for mono-repo mode, if one was requested. -/// # Errors -/// Returns an error if `--profile` names a profile that doesn't exist. -pub fn resolve_profile<'a>( - args: &ResolvedArgs, - config: &'a Config, - io: &mut IoCtx<'_>, -) -> Result, String> { - match &args.mono.profile { - Some(name) => config.profiles.get(name).map(Some).ok_or_else(|| { - list_profiles(config, io); - format!("Profile '{name}' not found") - }), - None => Ok(None), - } +#[must_use] +pub fn resolve_profile<'a>(args: &ResolvedArgs, config: &'a Config) -> Option<&'a Profile> { + args + .mono + .profile + .as_deref() + .and_then(|name| config.profiles.get(name)) } -/// Resolves the test repo for mono-repo mode: CLI positional wins, else the profile's. +/// Resolves the test repo for mono-repo mode from the already-resolved repo. /// # 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 { +/// Returns an error if no repository is available. +pub fn resolve_test_repo_for_mono(args: &ResolvedArgs) -> 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()), + None => Err("No repository specified".to_string()), } } diff --git a/src/commands/mono/setup.rs b/src/commands/mono/setup.rs index d77b548..2a48601 100644 --- a/src/commands/mono/setup.rs +++ b/src/commands/mono/setup.rs @@ -1,60 +1,5 @@ -use crate::{ - cli::{ - BuildSystem, - BuildSystem::{Cmake, Meson, Npm}, - }, - commands::{ - create_mono_repo_cmakelists, create_mono_repo_mesonbuild, hoist_wraps, - mono::create_mono_repo_package_json, - }, - ctx::RunCtx, - repository::repo_dir_name, -}; -use std::{ - collections::{HashMap, HashSet}, - iter::once, - path::PathBuf, -}; - -/// Generates root build configuration files for the mono-repo. -/// # Errors -/// Returns an error if config file generation or wrap hoisting fails. -pub fn generate_mono_config( - build_system: BuildSystem, - mono_repo_path: &std::path::Path, - repos_path: &std::path::Path, - repo_dirs: &[PathBuf], - repos: &[String], - ctx: &mut RunCtx<'_, '_>, -) -> Result>, String> { - writeln!(ctx.io.output, " Creating mono-repo configuration").ok(); - match build_system { - Cmake => { - create_mono_repo_cmakelists(mono_repo_path, repos, &mut ctx.io, ctx.flags)?; - Ok(None) - } - Meson => { - let map = hoist_wraps(repos_path, repo_dirs, &mut ctx.io, ctx.flags)?; - let subproject_names: Vec = repos - .iter() - .map(|r| { - let dir = repo_dir_name(r); - map - .iter() - .find(|(_, v)| *v == &dir) - .map(|(k, _)| k.clone()) - .unwrap_or(dir) - }) - .collect(); - create_mono_repo_mesonbuild(mono_repo_path, &subproject_names, &mut ctx.io, ctx.flags)?; - Ok(Some(map)) - } - Npm => { - create_mono_repo_package_json(mono_repo_path, repos_path, repos, &mut ctx.io, ctx.flags)?; - Ok(None) - } - } -} +use crate::repository::repo_dir_name; +use std::{collections::HashSet, iter::once}; /// Builds the full ordered list of repositories, deduplicating by directory name. #[must_use] diff --git a/src/commands/setup.rs b/src/commands/setup.rs index 5484d00..618245a 100644 --- a/src/commands/setup.rs +++ b/src/commands/setup.rs @@ -1,4 +1,4 @@ -use crate::{cli::ResolvedArgs, ctx::RunCtx, utils::dry_run_or_do}; +use crate::{ctx::RunCtx, resolve::ResolvedArgs, utils::dry_run_or_do}; use std::{ fs::{create_dir_all, remove_dir_all}, path::Path, diff --git a/src/commands/single.rs b/src/commands/single.rs index 89c5845..993c467 100644 --- a/src/commands/single.rs +++ b/src/commands/single.rs @@ -1,15 +1,13 @@ use crate::{ - cli::{detect_build_system, BuildSystem::Npm, ResolvedArgs}, - commands::{ - build_project, extract_repo_input, maybe_open_dev_server, prepare_build_dir, print_mode_header, - ModeHeader, - }, + build::{build_project, detect_build_system, maybe_open_dev_server, BuildSystem::Npm}, + commands::{extract_repo_input, prepare_build_dir, print_mode_header, ModeHeader}, ctx::RunCtx, prompts::confirm, repository::{ clone_repo, repo_dir_name, ExistsAction::{Skip, Update}, }, + resolve::ResolvedArgs, utils::detect_or_dry_run, }; use std::{path::Path, time::Instant}; diff --git a/src/config/types.rs b/src/config/types.rs index decc891..a543fc3 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1,6 +1,8 @@ use crate::{ - cli::{BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs}, + build::BuildType, + cli::{BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags}, profile::Profile, + resolve::ResolvedArgs, }; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, path::PathBuf}; diff --git a/src/ctx.rs b/src/ctx.rs index 13798ef..e94a79b 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -13,7 +13,7 @@ pub struct IoCtx<'a> { } /// Behavioral execution flags. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct RunFlags { pub verbose: bool, pub timing: bool, diff --git a/src/interactive.rs b/src/interactive.rs index e269919..a9579a6 100644 --- a/src/interactive.rs +++ b/src/interactive.rs @@ -1,7 +1,8 @@ use crate::{ - cli::{BuildType, ResolvedArgs}, + build::BuildType, ctx::IoCtx, prompts::{ask, ask_bool_if, ask_default, ask_required}, + resolve::ResolvedArgs, }; /// Interactive CLI mode — prompts for any unset arguments. diff --git a/src/lib.rs b/src/lib.rs index 21f144a..efecffa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #![warn(clippy::all, clippy::pedantic)] +pub mod build; pub mod cli; pub mod commands; pub mod config; @@ -8,6 +9,7 @@ pub mod interactive; pub mod profile; pub mod prompts; pub mod repository; +pub mod resolve; pub mod run; pub mod utils; pub mod workspace; diff --git a/src/resolve/mod.rs b/src/resolve/mod.rs new file mode 100644 index 0000000..fb91a5a --- /dev/null +++ b/src/resolve/mod.rs @@ -0,0 +1,4 @@ +pub mod resolved; +pub use resolved::{ResolvedArgs, ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags}; +pub mod resolver; +pub use resolver::{resolve_bool, resolve_with_config}; diff --git a/src/cli/resolved.rs b/src/resolve/resolved.rs similarity index 91% rename from src/cli/resolved.rs rename to src/resolve/resolved.rs index 090c294..676fb6f 100644 --- a/src/cli/resolved.rs +++ b/src/resolve/resolved.rs @@ -1,14 +1,16 @@ use crate::{ - cli::{BuildSystem, BuildType}, + build::{BuildSystem, BuildType}, ctx::RunFlags, }; /// Resolved connection flags after applying config and CLI overrides. +#[derive(Debug)] pub struct ResolvedConnectionFlags { pub ssh: bool, } /// Resolved build flags after applying config and CLI overrides. +#[derive(Debug)] #[allow(clippy::struct_excessive_bools)] pub struct ResolvedBuildFlags { pub build_type: BuildType, @@ -25,6 +27,7 @@ pub struct ResolvedBuildFlags { } /// Resolved mono-repo flags after applying config and CLI overrides. +#[derive(Debug)] pub struct ResolvedMonoFlags { pub mono_repo: bool, pub mono_dir: String, @@ -33,6 +36,7 @@ pub struct ResolvedMonoFlags { } /// Fully resolved arguments ready for command execution. +#[derive(Debug)] pub struct ResolvedArgs { pub repo: Option, pub yes: bool, diff --git a/src/cli/resolve.rs b/src/resolve/resolver.rs similarity index 79% rename from src/cli/resolve.rs rename to src/resolve/resolver.rs index 67c925d..2befc2a 100644 --- a/src/cli/resolve.rs +++ b/src/resolve/resolver.rs @@ -1,10 +1,9 @@ use crate::{ - cli::{ - Args, BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs, - ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags, - }, + build::BuildType, + cli::{Args, BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags}, config::{Config, ConfigEntry}, ctx::RunFlags, + resolve::{ResolvedArgs, ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags}, }; /// Resolves a boolean flag from CLI positive/negative flags, config value, and a default. @@ -112,6 +111,29 @@ fn resolve_mono_flags(mono: MonoRepoFlags, default: Option<&ConfigEntry>) -> Res } } +/// Fills the repo from the named profile's `test_repo` when no positional repo +/// was given, so all downstream code sees one resolved repo value. +/// # Errors +/// Returns an error if `profile` names a profile that does not exist. +fn resolve_repo( + repo: Option, + profile: Option<&str>, + config: &Config, +) -> Result, String> { + let prof = match profile { + Some(name) => Some(config.profiles.get(name).ok_or_else(|| { + let mut names: Vec<&str> = config.profiles.keys().map(String::as_str).collect(); + names.sort_unstable(); + format!( + "Profile '{name}' not found. Available: {}", + names.join(", ") + ) + })?), + None => None, + }; + Ok(repo.or_else(|| prof.and_then(|p| p.test_repo.clone()))) +} + /// 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`. @@ -124,7 +146,7 @@ pub fn resolve_with_config(args: Args, config: &Config) -> Result Result<(), Box> { let mut raw = Args::parse(); let yes = raw.yes; let command = raw.command.take(); - let mut config = load_config( + let config = load_config( &config_locations(config_path.as_path()), raw.diagnostic.verbose, raw.diagnostic.timing, &mut stdout, ); - let mut args = resolve_with_config(raw, &config).map_err(Box::::from)?; - let mut flags = args.diagnostic; + let args = resolve_with_config(raw, &config).map_err(Box::::from)?; - let mut io = IoCtx { + let io = IoCtx { input: &mut stdin, output: &mut stdout, }; + execute(args, config, command, yes, config_path, io, is_terminal) +} + +/// Dispatches to a management subcommand, or runs the clone/build flow. +/// # Errors +/// Returns an error if arguments can't be resolved, +/// a required tool is missing, +/// or the selected mode fails. +fn execute( + mut args: ResolvedArgs, + mut config: Config, + command: Option, + yes: bool, + config_path: PathBuf, + mut io: IoCtx<'_>, + is_terminal: bool, +) -> Result<(), Box> { + let mut flags = args.diagnostic; + if let Some(cmd) = command { match cmd { - Config(c) => { + Command::Config(c) => { handle_config_cmd(c.action, &mut config, config_path, yes, &mut io, flags)?; } - Profile(p) => { + Command::Profile(p) => { handle_profile_cmd(p.action, &mut config, yes, &mut io, flags)?; } - Workspace(w) => handle_workspace_cmd(&w.action, io, flags)?, + Command::Workspace(w) => handle_workspace_cmd(&w.action, io, flags)?, } return Ok(()); } - if args.repo.is_none() { + let has_repo = args.repo.is_some() + || args.mono.profile.as_deref().is_some_and(|p| { + config + .profiles + .get(p) + .is_some_and(|p| p.test_repo.is_some()) + }); + + if !has_repo { if is_terminal { interactive_mode(&mut args, &mut io)?; flags = args.diagnostic; diff --git a/src/utils/dry_run.rs b/src/utils/dry_run.rs index 07fcc85..264847a 100644 --- a/src/utils/dry_run.rs +++ b/src/utils/dry_run.rs @@ -1,5 +1,5 @@ use crate::{ - cli::BuildSystem, + build::BuildSystem, ctx::{IoCtx, RunCtx, RunFlags}, }; use std::path::Path; diff --git a/tests/build.rs b/tests/build.rs new file mode 100644 index 0000000..57f6044 --- /dev/null +++ b/tests/build.rs @@ -0,0 +1,12 @@ +#[path = "build/build.rs"] +mod build; +#[path = "common/mod.rs"] +mod common; +#[path = "build/config.rs"] +mod config; +#[path = "build/detect.rs"] +mod detect; +#[path = "build/npm.rs"] +mod npm; +#[path = "build/types.rs"] +mod types; diff --git a/tests/commands/build.rs b/tests/build/build.rs similarity index 97% rename from tests/commands/build.rs rename to tests/build/build.rs index dd012cc..ebd47ea 100644 --- a/tests/commands/build.rs +++ b/tests/build/build.rs @@ -1,8 +1,5 @@ use crate::common::{default_resolved_with_no_build, with_ctx, with_ctx_runner, MockRunner}; -use star_setup::{ - cli::BuildSystem, - commands::{build_project, cmake_build, meson_build, npm_build}, -}; +use star_setup::build::{build_project, cmake_build, meson_build, npm_build, BuildSystem}; use std::fs::write; /* ===== BUILD_PROJECT ===== */ diff --git a/tests/commands/mono/config.rs b/tests/build/config.rs similarity index 99% rename from tests/commands/mono/config.rs rename to tests/build/config.rs index 85efbc5..e2fcce1 100644 --- a/tests/commands/mono/config.rs +++ b/tests/build/config.rs @@ -1,5 +1,5 @@ use crate::common::{make_flags, with_io_dir}; -use star_setup::commands::{ +use star_setup::build::{ create_mono_repo_cmakelists, create_mono_repo_mesonbuild, create_mono_repo_package_json, }; use std::fs::{create_dir_all, read_to_string, write}; diff --git a/tests/cli/build/detect.rs b/tests/build/detect.rs similarity index 98% rename from tests/cli/build/detect.rs rename to tests/build/detect.rs index 50b323f..0092474 100644 --- a/tests/cli/build/detect.rs +++ b/tests/build/detect.rs @@ -1,6 +1,6 @@ use crate::common::with_ctx_input; use star_setup::{ - cli::{detect_build_system, detect_mono_build_system, BuildSystem}, + build::{detect_build_system, detect_mono_build_system, BuildSystem}, ctx::ProcessRunner, }; use std::{fs::write, path::Path}; diff --git a/tests/build/npm.rs b/tests/build/npm.rs new file mode 100644 index 0000000..f4e3f97 --- /dev/null +++ b/tests/build/npm.rs @@ -0,0 +1,6 @@ +#[path = "npm/dev.rs"] +mod dev; +#[path = "npm/npm.rs"] +mod package; +#[path = "npm/watch.rs"] +mod watch; diff --git a/tests/commands/dev.rs b/tests/build/npm/dev.rs similarity index 97% rename from tests/commands/dev.rs rename to tests/build/npm/dev.rs index 5eac89b..66f616b 100644 --- a/tests/commands/dev.rs +++ b/tests/build/npm/dev.rs @@ -1,5 +1,5 @@ use crate::common::with_io_output; -use star_setup::{commands::resolve_dev_command, ctx::RunFlags}; +use star_setup::{build::resolve_dev_command, ctx::RunFlags}; use std::fs::{create_dir_all, write}; use tempfile::TempDir; diff --git a/tests/commands/mono/npm.rs b/tests/build/npm/npm.rs similarity index 96% rename from tests/commands/mono/npm.rs rename to tests/build/npm/npm.rs index 9dd71b0..87969d1 100644 --- a/tests/commands/mono/npm.rs +++ b/tests/build/npm/npm.rs @@ -1,5 +1,5 @@ use crate::common::with_io_output; -use star_setup::{commands::read_package_json, ctx::RunFlags}; +use star_setup::{build::read_package_json, ctx::RunFlags}; use std::fs::{create_dir_all, write}; use tempfile::TempDir; diff --git a/tests/commands/mono/watch.rs b/tests/build/npm/watch.rs similarity index 98% rename from tests/commands/mono/watch.rs rename to tests/build/npm/watch.rs index a66bd5d..3644897 100644 --- a/tests/commands/mono/watch.rs +++ b/tests/build/npm/watch.rs @@ -1,5 +1,5 @@ use crate::common::{make_flags, with_io_dir, with_io_output}; -use star_setup::{commands::generate_watch_scripts, ctx::RunFlags}; +use star_setup::{build::generate_watch_scripts, ctx::RunFlags}; use std::{ fs::{create_dir_all, read_to_string, write}, path::Path, diff --git a/tests/commands/mono/wraps.rs b/tests/build/npm/wraps.rs similarity index 100% rename from tests/commands/mono/wraps.rs rename to tests/build/npm/wraps.rs diff --git a/tests/cli/build/types.rs b/tests/build/types.rs similarity index 97% rename from tests/cli/build/types.rs rename to tests/build/types.rs index e722134..25848d7 100644 --- a/tests/cli/build/types.rs +++ b/tests/build/types.rs @@ -1,4 +1,4 @@ -use star_setup::cli::{BuildSystem, BuildType}; +use star_setup::build::{BuildSystem, BuildType}; use std::str::FromStr; #[test] diff --git a/tests/cli.rs b/tests/cli.rs deleted file mode 100644 index 6003801..0000000 --- a/tests/cli.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[path = "common/mod.rs"] -mod common; - -#[path = "cli/build.rs"] -mod build; -#[path = "cli/resolve.rs"] -mod resolve; diff --git a/tests/cli/build.rs b/tests/cli/build.rs deleted file mode 100644 index c1b7b60..0000000 --- a/tests/cli/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[path = "build/detect.rs"] -mod detect; -#[path = "build/types.rs"] -mod types; diff --git a/tests/commands.rs b/tests/commands.rs index a6d3359..e6ff547 100644 --- a/tests/commands.rs +++ b/tests/commands.rs @@ -1,10 +1,6 @@ #[path = "common/mod.rs"] mod common; -#[path = "commands/build.rs"] -mod build; -#[path = "commands/dev.rs"] -mod dev; #[path = "commands/display.rs"] mod display; #[path = "commands/header.rs"] diff --git a/tests/commands/mono.rs b/tests/commands/mono.rs index 90c9a89..40c2482 100644 --- a/tests/commands/mono.rs +++ b/tests/commands/mono.rs @@ -1,14 +1,6 @@ -#[path = "mono/config.rs"] -mod config; #[path = "mono/mode.rs"] mod mode; -#[path = "mono/npm.rs"] -mod npm; #[path = "mono/resolve.rs"] mod resolve; #[path = "mono/setup.rs"] mod setup; -#[path = "mono/watch.rs"] -mod watch; -#[path = "mono/wraps.rs"] -mod wraps; diff --git a/tests/commands/mono/mode.rs b/tests/commands/mono/mode.rs index 8aac778..2093986 100644 --- a/tests/commands/mono/mode.rs +++ b/tests/commands/mono/mode.rs @@ -1,5 +1,5 @@ use crate::common::{default_resolved_mono, with_ctx, with_ctx_runner, MockRunner}; -use star_setup::{cli::BuildSystem, commands::mono_repo_mode, config::Config, ctx::DryRunRunner}; +use star_setup::{build::BuildSystem, commands::mono_repo_mode, config::Config, ctx::DryRunRunner}; use std::{ fs::{create_dir_all, read_dir, write}, path::Path, diff --git a/tests/commands/mono/resolve.rs b/tests/commands/mono/resolve.rs index d7cf9c9..b476619 100644 --- a/tests/commands/mono/resolve.rs +++ b/tests/commands/mono/resolve.rs @@ -1,11 +1,7 @@ -use crate::common::{default_resolved, with_ctx, with_io, MockRunner}; +use crate::common::{default_resolved, with_ctx, MockRunner}; use star_setup::{ - cli::BuildSystem, - commands::{ - generate_mono_config, - mono::{resolve_profile, resolve_test_repo_for_mono}, - resolve_repos_for_mono, resolve_test_repo, - }, + build::{generate_mono_config, BuildSystem}, + commands::{mono::resolve_test_repo_for_mono, resolve_repos_for_mono, resolve_test_repo}, config::Config, profile::Profile, }; @@ -64,11 +60,7 @@ fn test_resolve_test_repo_for_mono_errors_when_profile_empty() { .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 profile = resolve_profile(&args, &config, io).unwrap(); - assert!(resolve_test_repo_for_mono(&args, profile).is_err()); - }); + assert!(resolve_test_repo_for_mono(&args).is_err()); } #[test] @@ -81,36 +73,11 @@ fn test_resolve_test_repo_for_mono_prefers_positional_over_profile() { deps: vec![], }, ); - let args = default_resolved(); // args.repo already Some("user/repo") - with_io(|io| { - 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_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 args = default_resolved(); + assert_eq!( + resolve_test_repo_for_mono(&args), + Ok("user/repo".to_string()) ); - let mut args = default_resolved(); - args.repo = None; - args.mono.profile = Some("myprofile".to_string()); - with_io(|io| { - let profile = resolve_profile(&args, &config, io).unwrap(); - assert_eq!( - resolve_test_repo_for_mono(&args, profile), - Ok("user/app".to_string()) - ); - }); } #[test] @@ -136,19 +103,6 @@ fn test_resolve_repos_for_mono_with_explicit_repos() { ); } -#[test] -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_profile(&args, &config, io); - assert!(result.is_err()); - assert!(result.unwrap_err().contains("not found")); - }); -} - /* ===== GENERATE_MONO_CONFIG ===== */ #[test] fn test_generate_mono_config_meson() { diff --git a/tests/commands/single.rs b/tests/commands/single.rs index 3cd55b9..d0f493d 100644 --- a/tests/commands/single.rs +++ b/tests/commands/single.rs @@ -1,5 +1,5 @@ use crate::common::{default_resolved, with_ctx_input, MockRunner}; -use star_setup::{cli::BuildSystem, commands::single_repo_mode, ctx::DryRunRunner}; +use star_setup::{build::BuildSystem, commands::single_repo_mode, ctx::DryRunRunner}; use std::fs::{create_dir_all, read_dir, write}; fn make_repo_fixture(base: &std::path::Path) { diff --git a/tests/common/args.rs b/tests/common/args.rs index 5c6756b..ab2457f 100644 --- a/tests/common/args.rs +++ b/tests/common/args.rs @@ -1,11 +1,9 @@ #![allow(dead_code)] use star_setup::{ - cli::{ - resolve_with_config, Args, BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, - ResolvedArgs, - }, + cli::{Args, BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags}, config::Config, + resolve::{resolve_with_config, ResolvedArgs}, }; pub fn default_args() -> Args { diff --git a/tests/config/display.rs b/tests/config/display.rs index 234f999..84d71a2 100644 --- a/tests/config/display.rs +++ b/tests/config/display.rs @@ -1,5 +1,5 @@ use star_setup::{ - cli::BuildType, + build::BuildType, config::{format_entry, ConfigEntry}, }; diff --git a/tests/config/io.rs b/tests/config/io.rs index a73d143..99c9aed 100644 --- a/tests/config/io.rs +++ b/tests/config/io.rs @@ -1,6 +1,6 @@ use crate::common::with_io_output; use star_setup::{ - cli::BuildType, + build::BuildType, config::{insert_config, load_config, save_config, Config, ConfigEntry}, }; use std::{fs::write, path::PathBuf}; diff --git a/tests/config/types.rs b/tests/config/types.rs index 0a66545..003c8f2 100644 --- a/tests/config/types.rs +++ b/tests/config/types.rs @@ -1,6 +1,7 @@ use crate::common::default_resolved; use star_setup::{ - cli::{BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags}, + build::BuildType, + cli::{BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags}, config::ConfigEntry, }; diff --git a/tests/resolve.rs b/tests/resolve.rs new file mode 100644 index 0000000..6147017 --- /dev/null +++ b/tests/resolve.rs @@ -0,0 +1,4 @@ +#[path = "common/mod.rs"] +mod common; +#[path = "resolve/resolve.rs"] +mod resolve; diff --git a/tests/cli/resolve.rs b/tests/resolve/resolve.rs similarity index 83% rename from tests/cli/resolve.rs rename to tests/resolve/resolve.rs index 7e8a74e..5a95e4f 100644 --- a/tests/cli/resolve.rs +++ b/tests/resolve/resolve.rs @@ -1,7 +1,8 @@ use crate::common::default_args; use star_setup::{ - cli::{resolve_bool, resolve_with_config, BuildType}, + build::BuildType, config::{Config, ConfigEntry}, + resolve::{resolve_bool, resolve_with_config}, }; /// Helper to quickly build a `SetupConfig` with a populated profile entry. @@ -149,7 +150,11 @@ fn test_resolve_with_config_mono_repo_from_repos() { #[test] fn test_resolve_with_config_mono_repo_from_profile() { - let config = Config::new(); + let mut config = Config::new(); + config.profiles.insert( + "myprofile".to_string(), + star_setup::profile::Profile::default(), + ); let mut args = default_args(); args.mono.profile = Some("myprofile".to_string()); @@ -284,3 +289,46 @@ fn test_resolve_with_config_cli_positives_override_no_watch_no_dev_config() { assert!(!resolved.build.no_watch); assert!(!resolved.build.no_dev); } + +#[test] +fn test_resolve_fills_repo_from_profile_test_repo() { + let mut config = Config::new(); + config.profiles.insert( + "p".to_string(), + star_setup::profile::Profile { + test_repo: Some("user/app".to_string()), + deps: vec![], + }, + ); + let mut args = default_args(); + args.mono.profile = Some("p".to_string()); + let resolved = resolve_with_config(args, &config).unwrap(); + assert_eq!(resolved.repo, Some("user/app".to_string())); +} + +#[test] +fn test_resolve_positional_beats_profile_test_repo() { + let mut config = Config::new(); + config.profiles.insert( + "p".to_string(), + star_setup::profile::Profile { + test_repo: Some("user/other".to_string()), + deps: vec![], + }, + ); + let mut args = default_args(); + args.repo = Some("user/repo".to_string()); + args.mono.profile = Some("p".to_string()); + assert_eq!( + resolve_with_config(args, &config).unwrap().repo, + Some("user/repo".to_string()) + ); +} + +#[test] +fn test_resolve_unknown_profile_errors() { + let mut args = default_args(); + args.mono.profile = Some("nope".to_string()); + let err = resolve_with_config(args, &Config::new()).unwrap_err(); + assert!(err.contains("not found")); +}