Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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.3"
version = "0.3.4"
edition = "2021"
repository = "https://github.com/star-setup/core"
description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems"
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,30 @@ build-mono/
└── build/ # Build output
```

#### Workspace Mode
Manage an existing mono-repo workspace.

```bash
# Pull latest changes for all repos
star-setup workspace update

# Show status of all repos
star-setup workspace status

# Show status with ahead/behind remote
star-setup workspace status --fetch

# Remove build directory
star-setup workspace clean
```

Workspace flags:
| Flag | Description |
|------|-------------|
| `--path <DIR>` | Workspace root directory (default: current directory) |
| `--mono-dir <DIR>` | Workspace directory name (default: `build-mono`) |
| `--build-dir <DIR>` | Build directory name (default: `build`) |

### Profile Mode
Profiles represent a saved ecosystem of libraries commonly used together.
```bash
Expand Down
6 changes: 4 additions & 2 deletions src/cli/args.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
cli::{
commands::{ConfigCommand, ProfileCommand},
resolve_with_config, BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs,
resolve_with_config, BuildFlags, ConfigCommand, ConnectionFlags, DiagnosticFlags,
MonoRepoFlags, ProfileCommand, ResolvedArgs, WorkspaceCommand,
},
config::SetupConfig,
};
Expand All @@ -13,6 +13,8 @@ pub enum Command {
Config(ConfigCommand),
/// Manage saved profiles.
Profile(ProfileCommand),
/// Manage an existing workspace.
Workspace(WorkspaceCommand),
}

/// Top-level CLI arguments for star-setup.
Expand Down
45 changes: 45 additions & 0 deletions src/cli/commands.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::path::PathBuf;

use crate::cli::{BuildFlags, ConnectionFlags, DiagnosticFlags, MonoRepoFlags};
use clap::{Parser, Subcommand};

Expand Down Expand Up @@ -60,3 +62,46 @@ pub enum ProfileAction {
/// List all saved profiles.
List,
}

/// Workspace subcommand.
#[derive(Parser)]
pub struct WorkspaceCommand {
#[command(subcommand)]
pub action: WorkspaceAction,
}

/// Workspace subcommand actions.
#[derive(Subcommand)]
pub enum WorkspaceAction {
/// Pull latest changes for all repos in the workspace.
Update {
/// Workspace root directory (default: current directory).
#[arg(long)]
path: Option<PathBuf>,
/// Mono-repo workspace directory name (default: build-mono).
#[arg(long)]
mono_dir: Option<String>,
#[arg(long)]
build_dir: Option<String>,
},
/// Show status of all repos in the workspace.
Status {
#[arg(long)]
path: Option<PathBuf>,
#[arg(long)]
mono_dir: Option<String>,
#[arg(long)]
build_dir: Option<String>,
#[arg(long)]
fetch: bool,
},
/// Remove the build directory from the workspace.
Clean {
#[arg(long)]
path: Option<PathBuf>,
#[arg(long)]
mono_dir: Option<String>,
#[arg(long)]
build_dir: Option<String>,
},
}
4 changes: 3 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ pub use resolved::{
ResolvedMonoFlags,
};
pub mod commands;
pub use commands::{ConfigAction, ConfigCommand, ProfileAction, ProfileCommand};
pub use commands::{
ConfigAction, ConfigCommand, ProfileAction, ProfileCommand, WorkspaceAction, WorkspaceCommand,
};
21 changes: 17 additions & 4 deletions src/config/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::cli::{BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs};
use crate::cli::{
BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::PathBuf};

Expand Down Expand Up @@ -43,9 +45,20 @@ impl ConfigEntry {
) -> Self {
Self {
ssh: connection.ssh,
build_type: build.build_type.as_deref().unwrap_or("debug").parse().unwrap_or_default(),
build_dir: build.build_dir.clone().unwrap_or_else(|| "build".to_string()),
mono_dir: mono.mono_dir.clone().unwrap_or_else(|| "build-mono".to_string()),
build_type: build
.build_type
.as_deref()
.unwrap_or("debug")
.parse()
.unwrap_or_default(),
build_dir: build
.build_dir
.clone()
.unwrap_or_else(|| "build".to_string()),
mono_dir: mono
.mono_dir
.clone()
.unwrap_or_else(|| "build-mono".to_string()),
no_build: build.no_build,
clean: build.clean,
verbose: connection.verbose,
Expand Down
59 changes: 45 additions & 14 deletions src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ use std::{
path::Path,
};

/// IO context passed to functions that need input/output and behavioral flags.
pub struct IoCtx<'a> {
pub input: &'a mut dyn BufRead,
pub output: &'a mut dyn Write,
pub verbose: bool,
pub timing: bool,
pub dry_run: bool,
}

/// Trait for executing shell commands.
/// # Errors
/// Returns an error if the command fails to spawn or exits with a non-zero status.
Expand All @@ -21,12 +12,11 @@ pub trait Runner {
/// # Errors
/// Returns an error if the command fails to spawn or exits with a non-zero status.
fn run(&mut self, cmd: &[&str], cwd: Option<&Path>, io: &mut IoCtx<'_>) -> Result<(), String>;
}

/// Full execution context combining IO and a command runner.
pub struct RunCtx<'a> {
pub io: IoCtx<'a>,
pub runner: &'a mut dyn Runner,
/// Executes a shell command and captures stdout as a string.
/// # Errors
/// Returns an error if the command fails to spawn or exits with a non-zero status.
fn run_capture(&mut self, cmd: &[&str], cwd: Option<&Path>) -> Result<String, String>;
}

/// Runner that executes commands.
Expand All @@ -35,6 +25,28 @@ impl Runner for ProcessRunner {
fn run(&mut self, cmd: &[&str], cwd: Option<&Path>, io: &mut IoCtx<'_>) -> Result<(), String> {
run_command(cmd, cwd, io.verbose, io.output)
}

fn run_capture(&mut self, cmd: &[&str], cwd: Option<&Path>) -> Result<String, String> {
if cmd.is_empty() {
return Err("No command provided".to_string());
}
let mut command = std::process::Command::new(cmd[0]);
command.args(&cmd[1..]);
if let Some(dir) = cwd {
command.current_dir(dir);
}
let output = command
.output()
.map_err(|e| format!("Failed to run command: {e}"))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
} else {
Err(format!(
"Command failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
))
}
}
}

/// Runner that prints commands instead of executing them.
Expand All @@ -47,4 +59,23 @@ impl Runner for DryRunRunner {
}
Ok(())
}

fn run_capture(&mut self, _cmd: &[&str], _cwd: Option<&Path>) -> Result<String, String> {
Ok(String::new())
}
}

/// IO context passed to functions that need input/output and behavioral flags.
pub struct IoCtx<'a> {
pub input: &'a mut dyn BufRead,
pub output: &'a mut dyn Write,
pub verbose: bool,
pub timing: bool,
pub dry_run: bool,
}

/// Full execution context combining IO and a command runner.
pub struct RunCtx<'a> {
pub io: IoCtx<'a>,
pub runner: &'a mut dyn Runner,
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ pub mod prompts;
pub mod repository;
pub mod run;
pub mod utils;
pub mod workspace;
67 changes: 63 additions & 4 deletions src/run.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
use crate::{
cli::{Args, ConfigAction, ProfileAction, args::Command, resolve_with_config}, commands::{mono_repo_mode, single_repo_mode}, config::{
ConfigEntry, add_config, create_default_config, list_configs, load_config, remove_config,
}, ctx::{DryRunRunner, IoCtx, ProcessRunner, RunCtx, Runner}, interactive::interactive_mode, profile::{add_profile, list_profiles, remove_profile}, utils::check_prerequisites,
cli::{args::Command, resolve_with_config, Args, ConfigAction, ProfileAction, WorkspaceAction},
commands::{mono_repo_mode, single_repo_mode},
config::{
add_config, create_default_config, list_configs, load_config, remove_config, ConfigEntry,
},
ctx::{DryRunRunner, IoCtx, ProcessRunner, RunCtx, Runner},
interactive::interactive_mode,
profile::{add_profile, list_profiles, remove_profile},
utils::check_prerequisites,
workspace::{clean_workspace, resolve_workspace, status_workspace, update_workspace},
};
use clap::Parser;
use std::{
error::Error,
io::{self, IsTerminal},
path::{Path, PathBuf},
};
use clap::Parser;

const CONFIG_FILE_NAME: &str = ".star-setup.json";

/// Runs the setup process.
/// # Errors
/// Returns an error if the configuration file is missing or corrupted.
#[allow(clippy::too_many_lines)]
pub fn run() -> Result<(), Box<dyn Error>> {
let mut stdin = io::stdin().lock();
let mut stdout = io::stdout();
Expand Down Expand Up @@ -69,6 +77,57 @@ pub fn run() -> Result<(), Box<dyn Error>> {
add_profile(&mut config, &vals, raw.yes, &mut io)?;
}
},
Command::Workspace(cmd) => match cmd.action {
WorkspaceAction::Update {
path,
mono_dir,
build_dir,
} => {
let workspace =
resolve_workspace(path.as_deref(), mono_dir.as_deref(), build_dir.as_deref())?;
let mut dry = DryRunRunner;
let mut real = ProcessRunner;
let runner: &mut dyn Runner = if raw.diagnostic.dry_run {
&mut dry
} else {
&mut real
};
let mut ctx = RunCtx { io, runner };
update_workspace(&workspace, &mut ctx)?;
}
WorkspaceAction::Status {
path,
mono_dir,
build_dir,
fetch,
} => {
let workspace =
resolve_workspace(path.as_deref(), mono_dir.as_deref(), build_dir.as_deref())?;
let mut real = ProcessRunner;
let mut ctx = RunCtx {
io,
runner: &mut real,
};
status_workspace(&workspace, fetch, &mut ctx)?;
}
WorkspaceAction::Clean {
path,
mono_dir,
build_dir,
} => {
let workspace =
resolve_workspace(path.as_deref(), mono_dir.as_deref(), build_dir.as_deref())?;
let mut dry = DryRunRunner;
let mut real = ProcessRunner;
let runner: &mut dyn Runner = if raw.diagnostic.dry_run {
&mut dry
} else {
&mut real
};
let mut ctx = RunCtx { io, runner };
clean_workspace(&workspace, &mut ctx)?;
}
},
}
return Ok(());
}
Expand Down
39 changes: 39 additions & 0 deletions src/workspace/clean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::{ctx::RunCtx, workspace::resolve::Workspace};
use std::fs;

/// Removes the build directory from the workspace.
/// # Errors
/// Returns an error if the build directory cannot be removed.
pub fn clean_workspace(workspace: &Workspace, ctx: &mut RunCtx<'_>) -> Result<(), String> {
if !workspace.build_path.exists() {
writeln!(
ctx.io.output,
"Build directory does not exist: {}",
workspace.build_path.display()
)
.ok();
return Ok(());
}

writeln!(
ctx.io.output,
"Removing build directory: {}",
workspace.build_path.display()
)
.ok();

if ctx.io.dry_run {
writeln!(
ctx.io.output,
"Would remove directory: {}",
workspace.build_path.display()
)
.ok();
} else {
fs::remove_dir_all(&workspace.build_path)
.map_err(|e| format!("Failed to remove build directory: {e}"))?;
writeln!(ctx.io.output, "Done").ok();
}

Ok(())
}
8 changes: 8 additions & 0 deletions src/workspace/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub mod clean;
pub mod resolve;
pub mod status;
pub mod update;
pub use clean::clean_workspace;
pub use resolve::{resolve_workspace, Workspace};
pub use status::status_workspace;
pub use update::update_workspace;
Loading
Loading