diff --git a/crates/forest/src/cli.rs b/crates/forest/src/cli.rs index fca2088..479fb43 100644 --- a/crates/forest/src/cli.rs +++ b/crates/forest/src/cli.rs @@ -116,17 +116,27 @@ pub async fn execute() -> anyhow::Result<()> { let mut member_contexts = Vec::new(); - for (member_path, member) in workspace_members { + for (member_path, member) in &workspace_members { match member { - WorkspaceProject::Plan(plan) => { + 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) + .reconcile( + project, + member_path, + Some(workspace_members.as_ref()), + Some(&project_path), + ) .await? { - let plan_file = tokio::fs::read_to_string(&plan_file_path).await?; + let plan_file = tokio::fs::read_to_string(&plan_file_path) + .await + .context(format!( + "failed to read file at: {}", + project_path.to_string_lossy() + ))?; let plan_doc: KdlDocument = plan_file.parse()?; let plan: Plan = plan_doc.try_into()?; @@ -137,7 +147,10 @@ pub async fn execute() -> anyhow::Result<()> { None }; - let context = Context { project, plan }; + let context = Context { + project: project.clone(), + plan, + }; member_contexts.push((member_path, context)); } } @@ -177,12 +190,8 @@ pub async fn execute() -> anyhow::Result<()> { .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?; + run::Run::execute_command_if_exists(all_cmd, member_path, &context) + .await?; } } _ => { @@ -252,7 +261,7 @@ pub async fn execute() -> anyhow::Result<()> { tracing::trace!("found a project name: {}", project.name); let plan = if let Some(plan_file_path) = PlanReconciler::new() - .reconcile(&project, &project_path) + .reconcile(&project, &project_path, None, None) .await? { let plan_file = tokio::fs::read_to_string(&plan_file_path).await?; diff --git a/crates/forest/src/model.rs b/crates/forest/src/model.rs index b7a6a3e..e81f018 100644 --- a/crates/forest/src/model.rs +++ b/crates/forest/src/model.rs @@ -1,6 +1,5 @@ use std::{collections::BTreeMap, fmt::Debug, path::PathBuf}; -use colored_json::Paint; use kdl::{KdlDocument, KdlNode, KdlValue}; use serde::Serialize; @@ -57,6 +56,7 @@ impl TryFrom for Plan { pub enum ProjectPlan { Local { path: PathBuf }, Git { url: String, path: Option }, + Workspace { name: String }, NoPlan, } @@ -96,6 +96,17 @@ impl TryFrom<&KdlNode> for ProjectPlan { }); } + if let Some(workspace) = children.get_arg("workspace") { + return Ok(Self::Workspace { + name: workspace + .as_string() + .map(|w| w.to_string()) + .ok_or(anyhow::anyhow!( + "workspace requires a project name in the same project" + ))?, + }); + } + Ok(Self::NoPlan) } } diff --git a/crates/forest/src/plan_reconciler.rs b/crates/forest/src/plan_reconciler.rs index 0a1294f..5ddb146 100644 --- a/crates/forest/src/plan_reconciler.rs +++ b/crates/forest/src/plan_reconciler.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use anyhow::Context; -use crate::model::{ForestFile, Project}; +use crate::model::{ForestFile, Project, Workspace, WorkspaceProject}; pub mod git; pub mod local; @@ -21,6 +21,8 @@ impl PlanReconciler { &self, project: &Project, destination: &Path, + workspace_members: Option<&Vec<(PathBuf, WorkspaceProject)>>, + workspace_root: Option<&Path>, ) -> anyhow::Result> { tracing::info!("reconciling project"); if project.plan.is_none() { @@ -55,6 +57,21 @@ impl PlanReconciler { crate::model::ProjectPlan::Git { url, path } => { git::reconcile(url, path, &plan_dir).await?; } + crate::model::ProjectPlan::Workspace { name } => { + let workspace_root = workspace_root.expect("to have workspace root available"); + + if let Some(workspace_members) = workspace_members { + for (member_path, member) in workspace_members { + if let WorkspaceProject::Plan(plan) = member { + if &plan.name == name { + tracing::debug!("found workspace project: {}", name); + local::reconcile(&workspace_root.join(member_path), &plan_dir) + .await?; + } + } + } + } + } crate::model::ProjectPlan::NoPlan => { tracing::debug!("no plan, returning"); return Ok(None); diff --git a/crates/forest/src/script/shell.rs b/crates/forest/src/script/shell.rs index b17c95d..60ba247 100644 --- a/crates/forest/src/script/shell.rs +++ b/crates/forest/src/script/shell.rs @@ -29,7 +29,7 @@ impl ShellExecutor { } let mut cmd = tokio::process::Command::new(&script_path); - let cmd = cmd.current_dir(path); + let cmd = cmd.current_dir(&self.root.project_path); cmd.stdin(Stdio::inherit()); cmd.stdout(Stdio::inherit()); cmd.stderr(Stdio::inherit()); @@ -53,6 +53,7 @@ impl ShellExecutor { fn get_path(&self) -> PathBuf { match self.ty { + //ShellType::Plan => self.root.project_path.join(".forest").join("plan"), ShellType::Plan => self.root.project_path.join(".forest").join("plan"), ShellType::Project => self.root.project_path.clone(), } diff --git a/examples/workspace/plan/a/forest.kdl b/examples/workspace/plan/a/forest.kdl index cfca6f9..23f73d6 100644 --- a/examples/workspace/plan/a/forest.kdl +++ b/examples/workspace/plan/a/forest.kdl @@ -1,3 +1,7 @@ plan { name a + + scripts { + hello_plan type=shell {} + } } diff --git a/examples/workspace/plan/a/scripts/hello_plan.sh b/examples/workspace/plan/a/scripts/hello_plan.sh new file mode 100755 index 0000000..5693af4 --- /dev/null +++ b/examples/workspace/plan/a/scripts/hello_plan.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +set -e + +echo "hello from plan" + +echo "i am here: $PWD" diff --git a/examples/workspace/projects/a/forest.kdl b/examples/workspace/projects/a/forest.kdl index 46c4bf6..2182f1f 100644 --- a/examples/workspace/projects/a/forest.kdl +++ b/examples/workspace/projects/a/forest.kdl @@ -1,6 +1,10 @@ project { name a + plan { + workspace a + } + scripts { hello type=shell {} }