diff --git a/.env b/.env index 16cab76..fba9db5 100644 --- a/.env +++ b/.env @@ -3,3 +3,5 @@ FOREST_S3_BUCKET=forest FOREST_S3_REGION=eu-west-1 FOREST_S3_USER=forestadmin FOREST_S3_PASSWORD=forestadmin + +FOREST_LOG_LEVEL=forest=trace diff --git a/crates/forest/src/cli.rs b/crates/forest/src/cli.rs index 87139f0..5a58f88 100644 --- a/crates/forest/src/cli.rs +++ b/crates/forest/src/cli.rs @@ -6,7 +6,7 @@ use kdl::KdlDocument; use rusty_s3::{Bucket, Credentials, S3Action}; use crate::{ - model::{Context, Plan, Project}, + model::{Context, ForestFile, Plan}, plan_reconciler::PlanReconciler, state::SharedState, }; @@ -80,84 +80,92 @@ pub async fn execute() -> anyhow::Result<()> { let project_file = tokio::fs::read_to_string(&project_file_path).await?; let project_doc: KdlDocument = project_file.parse()?; - let project: Project = project_doc.try_into()?; - tracing::trace!("found a project name: {}", project.name); + let project: ForestFile = project_doc.try_into()?; - 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 }; - - let matches = if matches.subcommand_matches("run").is_some() { - 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() - } else { - matches - }; - - match matches - .subcommand() - .expect("forest requires a command to be passed") - { - ("run", args) => { - run::Run::execute(args, &project_path, &context).await?; + match project { + ForestFile::Workspace(workspace) => { + tracing::trace!("running as workspace") } - _ => match Commands::from_arg_matches(&matches).unwrap() { - Commands::Init {} => { - tracing::info!("initializing project"); - tracing::trace!("found context: {:?}", context); - } - Commands::Info {} => { - let output = serde_json::to_string_pretty(&context)?; - 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 {} => { - 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); + + 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 }; + + let matches = if matches.subcommand_matches("run").is_some() { + 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() + } else { + matches + }; + + match matches + .subcommand() + .expect("forest requires a command to be passed") + { + ("run", args) => { + run::Run::execute(args, &project_path, &context).await?; } + _ => match Commands::from_arg_matches(&matches).unwrap() { + Commands::Init {} => { + tracing::info!("initializing project"); + tracing::trace!("found context: {:?}", context); + } + Commands::Info {} => { + let output = serde_json::to_string_pretty(&context)?; + 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 {} => { + let forest_path = project_path.join(".forest"); + if forest_path.exists() { + tokio::fs::remove_dir_all(forest_path).await?; + tracing::info!("removed .forest"); + } + } + }, } - }, + } } Ok(()) diff --git a/crates/forest/src/cli/run.rs b/crates/forest/src/cli/run.rs index 8df355e..b60ce5e 100644 --- a/crates/forest/src/cli/run.rs +++ b/crates/forest/src/cli/run.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, path::Path}; +use std::path::Path; use crate::{model::Context, script::ScriptExecutor}; diff --git a/crates/forest/src/model.rs b/crates/forest/src/model.rs index d73b3b9..9d1dcf9 100644 --- a/crates/forest/src/model.rs +++ b/crates/forest/src/model.rs @@ -372,3 +372,40 @@ impl TryFrom for Project { }) } } + +#[derive(Debug, Clone, Serialize)] +pub struct Workspace {} + +impl TryFrom for Workspace { + type Error = anyhow::Error; + + fn try_from(value: KdlDocument) -> Result { + Ok(Self {}) + } +} + +#[derive(Debug, Clone, Serialize)] +pub enum ForestFile { + Workspace(Workspace), + Project(Project), +} + +impl TryFrom for ForestFile { + type Error = anyhow::Error; + + fn try_from(value: KdlDocument) -> Result { + if value.get("workspace").is_some() && value.get("project").is_some() { + anyhow::bail!("a forest.kdl file cannot contain both a workspace and project") + } + + if value.get("project").is_some() { + return Ok(Self::Project(value.try_into()?)); + } + + if value.get("workspace").is_some() { + return Ok(Self::Workspace(value.try_into()?)); + } + + anyhow::bail!("a forest.kdl file must be either a project, workspace or plan") + } +} diff --git a/crates/forest/src/plan_reconciler.rs b/crates/forest/src/plan_reconciler.rs index c13a4d7..0a1294f 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::Project; +use crate::model::{ForestFile, Project}; pub mod git; pub mod local; diff --git a/examples/workspace/forest.kdl b/examples/workspace/forest.kdl new file mode 100644 index 0000000..1441b1b --- /dev/null +++ b/examples/workspace/forest.kdl @@ -0,0 +1,9 @@ +workspace { + members { + member "./projects/a" + member "./projects/b" + member "./plan/a" + member "./plan/b" + member "./components/*" + } +}