feat: can execute all subcommands from workspace
This commit is contained in:
parent
28a1d09974
commit
1fda414e05
@ -48,6 +48,7 @@ fn get_root(include_run: bool) -> clap::Command {
|
||||
.author(crate_authors!())
|
||||
.version(crate_version!())
|
||||
.about(crate_description!())
|
||||
.ignore_errors(include_run)
|
||||
.arg(
|
||||
clap::Arg::new("project_path")
|
||||
.long("project-path")
|
||||
@ -56,9 +57,8 @@ fn get_root(include_run: bool) -> clap::Command {
|
||||
);
|
||||
|
||||
if include_run {
|
||||
root_cmd = root_cmd.subcommand(clap::Command::new("run").allow_external_subcommands(true));
|
||||
root_cmd = root_cmd.subcommand(clap::Command::new("run").allow_external_subcommands(true))
|
||||
}
|
||||
|
||||
Commands::augment_subcommands(root_cmd)
|
||||
}
|
||||
|
||||
@ -108,16 +108,145 @@ pub async fn execute() -> anyhow::Result<()> {
|
||||
&member.path
|
||||
))?;
|
||||
|
||||
workspace_members.push(project);
|
||||
workspace_members.push((workspace_member_path, project));
|
||||
}
|
||||
|
||||
let output = serde_json::to_string_pretty(&workspace_members)?;
|
||||
println!("{}", output.to_colored_json_auto().unwrap_or(output));
|
||||
|
||||
// TODO: 1a (optional). Resolve dependencies
|
||||
// 2. Reconcile plans
|
||||
|
||||
let mut member_contexts = Vec::new();
|
||||
|
||||
for (member_path, member) in workspace_members {
|
||||
match member {
|
||||
WorkspaceProject::Plan(plan) => {
|
||||
tracing::warn!("skipping reconcile for plans for now")
|
||||
}
|
||||
WorkspaceProject::Project(project) => {
|
||||
let plan = if let Some(plan_file_path) = PlanReconciler::new()
|
||||
.reconcile(&project, &project_path)
|
||||
.await?
|
||||
{
|
||||
let plan_file = tokio::fs::read_to_string(&plan_file_path).await?;
|
||||
let plan_doc: KdlDocument = plan_file.parse()?;
|
||||
|
||||
let plan: Plan = plan_doc.try_into()?;
|
||||
tracing::trace!("found a plan name: {}", project.name);
|
||||
|
||||
Some(plan)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let context = Context { project, plan };
|
||||
member_contexts.push((member_path, context));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::debug!("run is called, building extra commands, rerunning the parser");
|
||||
let mut run_cmd = clap::Command::new("run").subcommand_required(true);
|
||||
|
||||
// 3. Provide context and aggregated commands for projects
|
||||
for (_, context) in &member_contexts {
|
||||
let commands = run::Run::augment_workspace_command(context, &context.project.name);
|
||||
run_cmd = run_cmd.subcommands(commands);
|
||||
}
|
||||
|
||||
run_cmd =
|
||||
run_cmd.subcommand(clap::Command::new("all").allow_external_subcommands(true));
|
||||
|
||||
let mut root = get_root(false).subcommand(run_cmd);
|
||||
let matches = root.get_matches_mut();
|
||||
|
||||
if matches.subcommand().is_none() {
|
||||
root.print_help()?;
|
||||
anyhow::bail!("failed to find command");
|
||||
}
|
||||
|
||||
match matches
|
||||
.subcommand()
|
||||
.expect("forest requires a command to be passed")
|
||||
{
|
||||
("run", args) => {
|
||||
let (run_args, args) = args.subcommand().expect("run must have subcommands");
|
||||
|
||||
match run_args {
|
||||
"all" => {
|
||||
let (all_cmd, _args) = args
|
||||
.subcommand()
|
||||
.expect("to be able to get a subcommand (todo: might not work)");
|
||||
|
||||
for (member_path, context) in member_contexts {
|
||||
run::Run::execute_command_if_exists(
|
||||
all_cmd,
|
||||
&member_path,
|
||||
&context,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let (project_name, command) = run_args
|
||||
.split_once("::")
|
||||
.expect("commands to always be pairs for workspaces");
|
||||
|
||||
let mut found_context = false;
|
||||
for (member_path, context) in &member_contexts {
|
||||
if project_name == context.project.name {
|
||||
run::Run::execute_command(command, member_path, context)
|
||||
.await?;
|
||||
|
||||
found_context = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_context {
|
||||
anyhow::bail!("no matching context was found")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => match Commands::from_arg_matches(&matches).unwrap() {
|
||||
Commands::Init {} => {
|
||||
tracing::info!("initializing project");
|
||||
}
|
||||
Commands::Info {} => {
|
||||
let output = serde_json::to_string_pretty(&member_contexts)?;
|
||||
println!("{}", output.to_colored_json_auto().unwrap_or(output));
|
||||
}
|
||||
Commands::Template(template) => {
|
||||
//template.execute(&project_path, &context).await?;
|
||||
}
|
||||
Commands::Serve {
|
||||
s3_endpoint,
|
||||
s3_bucket,
|
||||
s3_region,
|
||||
s3_user,
|
||||
s3_password,
|
||||
..
|
||||
} => {
|
||||
tracing::info!("Starting server");
|
||||
let creds = Credentials::new(s3_user, s3_password);
|
||||
let bucket = Bucket::new(
|
||||
url::Url::parse(&s3_endpoint)?,
|
||||
rusty_s3::UrlStyle::Path,
|
||||
s3_bucket,
|
||||
s3_region,
|
||||
)?;
|
||||
let put_object = bucket.put_object(Some(&creds), "some-object");
|
||||
let _url = put_object.sign(std::time::Duration::from_secs(30));
|
||||
let _state = SharedState::new().await?;
|
||||
}
|
||||
Commands::Clean {} => {
|
||||
todo!();
|
||||
// let forest_path = project_path.join(".forest");
|
||||
// if forest_path.exists() {
|
||||
// tokio::fs::remove_dir_all(forest_path).await?;
|
||||
// tracing::info!("removed .forest");
|
||||
// }
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
ForestFile::Project(project) => {
|
||||
tracing::trace!("found a project name: {}", project.name);
|
||||
@ -143,9 +272,8 @@ pub async fn execute() -> anyhow::Result<()> {
|
||||
tracing::debug!("run is called, building extra commands, rerunning the parser");
|
||||
let root = get_root(false);
|
||||
|
||||
let root = run::Run::augment_command(root, &context);
|
||||
|
||||
root.get_matches()
|
||||
let run_cmd = run::Run::augment_command(&context);
|
||||
root.subcommand(run_cmd).get_matches()
|
||||
} else {
|
||||
matches
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ use crate::{model::Context, script::ScriptExecutor};
|
||||
// create a new sub command that encapsulates all the run complexities
|
||||
pub struct Run {}
|
||||
impl Run {
|
||||
pub fn augment_command(root: clap::Command, ctx: &Context) -> clap::Command {
|
||||
pub fn augment_command(ctx: &Context) -> clap::Command {
|
||||
let mut run_cmd = clap::Command::new("run")
|
||||
.subcommand_required(true)
|
||||
.about("runs any kind of script from either the project or plan");
|
||||
@ -37,7 +37,37 @@ impl Run {
|
||||
}
|
||||
}
|
||||
|
||||
root.subcommand(run_cmd)
|
||||
run_cmd
|
||||
}
|
||||
|
||||
pub fn augment_workspace_command(ctx: &Context, prefix: &str) -> Vec<clap::Command> {
|
||||
let mut commands = Vec::new();
|
||||
if let Some(scripts) = &ctx.project.scripts {
|
||||
for name in scripts.items.keys() {
|
||||
let cmd = clap::Command::new(format!("{prefix}::{name}"));
|
||||
commands.push(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(plan) = &ctx.plan {
|
||||
if let Some(scripts) = &plan.scripts {
|
||||
let existing_cmds = commands
|
||||
.iter()
|
||||
.map(|s| format!("{prefix}::{}", s.get_name()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for name in scripts.items.keys() {
|
||||
if existing_cmds.contains(name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cmd = clap::Command::new(format!("{prefix}::{name}"));
|
||||
commands.push(cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commands
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
@ -73,4 +103,64 @@ impl Run {
|
||||
|
||||
anyhow::bail!("no scripts were found for command: {}", name)
|
||||
}
|
||||
|
||||
pub async fn execute_command(
|
||||
command: &str,
|
||||
project_path: &Path,
|
||||
ctx: &Context,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(scripts_ctx) = &ctx.project.scripts {
|
||||
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||
.run(script_ctx, command)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(plan) = &ctx.plan {
|
||||
if let Some(scripts_ctx) = &plan.scripts {
|
||||
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||
.run(script_ctx, command)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anyhow::bail!("no scripts were found for command: {}", command)
|
||||
}
|
||||
|
||||
pub async fn execute_command_if_exists(
|
||||
command: &str,
|
||||
project_path: &Path,
|
||||
ctx: &Context,
|
||||
) -> anyhow::Result<()> {
|
||||
if let Some(scripts_ctx) = &ctx.project.scripts {
|
||||
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||
.run(script_ctx, command)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(plan) = &ctx.plan {
|
||||
if let Some(scripts_ctx) = &plan.scripts {
|
||||
if let Some(script_ctx) = scripts_ctx.items.get(command) {
|
||||
ScriptExecutor::new(project_path.into(), ctx.clone())
|
||||
.run(script_ctx, command)
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
project {
|
||||
name a
|
||||
|
||||
scripts {
|
||||
hello type=shell {}
|
||||
}
|
||||
}
|
||||
|
7
examples/workspace/projects/a/scripts/hello.sh
Executable file
7
examples/workspace/projects/a/scripts/hello.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
set -e
|
||||
|
||||
echo "hello from a"
|
||||
|
||||
echo "i am here: $PWD"
|
@ -1,3 +1,7 @@
|
||||
project {
|
||||
name b
|
||||
|
||||
scripts {
|
||||
hello type=shell {}
|
||||
}
|
||||
}
|
||||
|
5
examples/workspace/projects/b/scripts/hello.sh
Executable file
5
examples/workspace/projects/b/scripts/hello.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
set -e
|
||||
|
||||
echo "hello from b"
|
Loading…
x
Reference in New Issue
Block a user