Compare commits

..

1 Commits

Author SHA1 Message Date
7883cfa908 chore(deps): update all dependencies 2025-03-04 01:43:36 +00:00
17 changed files with 97 additions and 615 deletions

2
.env
View File

@@ -3,5 +3,3 @@ FOREST_S3_BUCKET=forest
FOREST_S3_REGION=eu-west-1
FOREST_S3_USER=forestadmin
FOREST_S3_PASSWORD=forestadmin
FOREST_LOG_LEVEL=forest=trace

View File

@@ -1,41 +0,0 @@
name: Build Forest
on:
- push
- pull_request
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
build:
env:
RUSTFLAGS: -D warnings
timeout_minutes: 30
steps:
- name: Build application
uses: rustlang/rust:nightly
run:
- export SQLX_OFFLINE=true
- cargo build --release
- name: Run tests
uses: rustlang/rust:nightly
run:
- cargo test
- name: Check code formatting
uses: rustlang/rust:nightly
run:
- cargo fmt -- --check
continue_on_error: true
- name: Run clippy lints
uses: rustlang/rust:nightly
run:
- rustup component add clippy
- cargo clippy -- -D warnings
continue_on_error: true

38
Cargo.lock generated
View File

@@ -78,9 +78,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.98"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "autocfg"
@@ -138,9 +138,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.37"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
dependencies = [
"clap_builder",
"clap_derive",
@@ -148,9 +148,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.37"
version = "4.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
dependencies = [
"anstream",
"anstyle",
@@ -160,9 +160,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.32"
version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed"
dependencies = [
"heck",
"proc-macro2",
@@ -576,9 +576,9 @@ dependencies = [
[[package]]
name = "minijinja"
version = "2.9.0"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98642a6dfca91122779a307b77cd07a4aa951fbe32232aaf5bad9febc66be754"
checksum = "6e36f1329330bb1614c94b78632b9ce45dd7d761f3304a1bed07b2990a7c5097"
dependencies = [
"serde",
]
@@ -883,18 +883,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
@@ -1071,9 +1071,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes",
@@ -1209,9 +1209,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.16.0"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
dependencies = [
"getrandom",
]
@@ -1275,7 +1275,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]

View File

@@ -1,13 +1,12 @@
use std::{net::SocketAddr, path::PathBuf};
use anyhow::Context as AnyContext;
use clap::{FromArgMatches, Parser, Subcommand, crate_authors, crate_description, crate_version};
use colored_json::ToColoredJson;
use kdl::KdlDocument;
use rusty_s3::{Bucket, Credentials, S3Action};
use crate::{
model::{Context, ForestFile, Plan, Project, WorkspaceProject},
model::{Context, Plan, Project},
plan_reconciler::PlanReconciler,
state::SharedState,
};
@@ -48,7 +47,6 @@ 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")
@@ -57,8 +55,9 @@ 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)
}
@@ -79,263 +78,86 @@ pub async fn execute() -> anyhow::Result<()> {
}
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
let doc: KdlDocument = project_file.parse()?;
let project: ForestFile = doc.try_into()?;
let project_doc: KdlDocument = project_file.parse()?;
match project {
ForestFile::Workspace(workspace) => {
tracing::trace!("running as workspace");
let project: Project = project_doc.try_into()?;
tracing::trace!("found a project name: {}", project.name);
// 1. For each member load the 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 mut workspace_members = Vec::new();
let plan: Plan = plan_doc.try_into()?;
tracing::trace!("found a plan name: {}", project.name);
for member in workspace.members {
let workspace_member_path = project_path.join(&member.path);
Some(plan)
} else {
None
};
let project_file_path = workspace_member_path.join("forest.kdl");
if !project_file_path.exists() {
anyhow::bail!(
"no 'forest.kdl' file was found at: {}",
workspace_member_path.display().to_string()
);
}
let context = Context { project, plan };
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
let doc: KdlDocument = project_file.parse()?;
let project: WorkspaceProject = doc.try_into().context(format!(
"workspace member: {} failed to parse",
&member.path
))?;
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);
workspace_members.push((workspace_member_path, project));
}
let root = run::Run::augment_command(root, &context);
// TODO: 1a (optional). Resolve dependencies
// 2. Reconcile plans
root.get_matches()
} else {
matches
};
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,
member_path,
Some(workspace_members.as_ref()),
Some(&project_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()?;
tracing::trace!("found a plan name: {}", project.name);
Some(plan)
} else {
None
};
let context = Context {
project: project.clone(),
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");
// }
}
},
}
match matches
.subcommand()
.expect("forest requires a command to be passed")
{
("run", args) => {
run::Run::execute(args, &project_path, &context).await?;
}
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, None, None)
.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 run_cmd = run::Run::augment_command(&context);
root.subcommand(run_cmd).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");
}
}
},
_ => 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(())

View File

@@ -1,4 +1,4 @@
use std::path::Path;
use std::{collections::BTreeMap, path::Path};
use crate::{model::Context, script::ScriptExecutor};
@@ -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(ctx: &Context) -> clap::Command {
pub fn augment_command(root: clap::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,37 +37,7 @@ impl Run {
}
}
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
root.subcommand(run_cmd)
}
pub async fn execute(
@@ -103,64 +73,4 @@ 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(())
}
}

View File

@@ -1,5 +1,6 @@
use std::{collections::BTreeMap, fmt::Debug, path::PathBuf};
use colored_json::Paint;
use kdl::{KdlDocument, KdlNode, KdlValue};
use serde::Serialize;
@@ -12,9 +13,7 @@ pub struct Context {
#[derive(Debug, Clone, Serialize)]
pub struct Plan {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub templates: Option<Templates>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scripts: Option<Scripts>,
}
@@ -56,7 +55,6 @@ impl TryFrom<KdlDocument> for Plan {
pub enum ProjectPlan {
Local { path: PathBuf },
Git { url: String, path: Option<PathBuf> },
Workspace { name: String },
NoPlan,
}
@@ -96,17 +94,6 @@ 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)
}
}
@@ -177,12 +164,6 @@ pub struct Global {
items: BTreeMap<String, GlobalVariable>,
}
impl Global {
fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl From<&Global> for minijinja::Value {
fn from(value: &Global) -> Self {
Self::from_serialize(&value.items)
@@ -331,16 +312,10 @@ impl TryFrom<&KdlNode> for Scripts {
#[derive(Debug, Clone, Serialize)]
pub struct Project {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plan: Option<ProjectPlan>,
#[serde(skip_serializing_if = "Global::is_empty")]
pub global: Global,
#[serde(skip_serializing_if = "Option::is_none")]
pub templates: Option<Templates>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scripts: Option<Scripts>,
}
@@ -397,111 +372,3 @@ impl TryFrom<KdlDocument> for Project {
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct WorkspaceMember {
pub path: String,
}
impl TryFrom<&kdl::KdlNode> for WorkspaceMember {
type Error = anyhow::Error;
fn try_from(value: &kdl::KdlNode) -> Result<Self, Self::Error> {
Ok(Self {
path: value
.entries()
.first()
.ok_or(anyhow::anyhow!(
"is supposed to have a path `member ./some-path`"
))?
.value()
.as_string()
.ok_or(anyhow::anyhow!("value is required to be a string"))?
.to_string(),
})
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Workspace {
pub members: Vec<WorkspaceMember>,
}
impl TryFrom<KdlDocument> for Workspace {
type Error = anyhow::Error;
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
let workspace = value
.get("workspace")
.expect("to have a workspace at this point")
.children()
.ok_or(anyhow::anyhow!("workspace to be a section"))?;
Ok(Self {
members: workspace
.get("members")
.ok_or(anyhow::anyhow!(
"a members section is required for a workspace"
))?
.children()
.ok_or(anyhow::anyhow!("a members is required to have children"))?
.nodes()
.iter()
.map(|m| m.try_into())
.collect::<anyhow::Result<Vec<_>>>()?,
})
}
}
#[derive(Debug, Clone, Serialize)]
pub enum ForestFile {
Workspace(Workspace),
Project(Project),
}
impl TryFrom<KdlDocument> for ForestFile {
type Error = anyhow::Error;
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
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")
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum WorkspaceProject {
Plan(Plan),
Project(Project),
}
impl TryFrom<KdlDocument> for WorkspaceProject {
type Error = anyhow::Error;
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
if value.get("plan").is_some() && value.get("project").is_some() {
anyhow::bail!("a forest.kdl file cannot contain both a plan and project")
}
if value.get("project").is_some() {
return Ok(Self::Project(value.try_into()?));
}
if value.get("plan").is_some() {
return Ok(Self::Plan(value.try_into()?));
}
anyhow::bail!("a forest.kdl file must be either a project, workspace or plan")
}
}

View File

@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use anyhow::Context;
use crate::model::{Project, WorkspaceProject};
use crate::model::Project;
pub mod git;
pub mod local;
@@ -21,8 +21,6 @@ impl PlanReconciler {
&self,
project: &Project,
destination: &Path,
workspace_members: Option<&Vec<(PathBuf, WorkspaceProject)>>,
workspace_root: Option<&Path>,
) -> anyhow::Result<Option<PathBuf>> {
tracing::info!("reconciling project");
if project.plan.is_none() {
@@ -57,21 +55,6 @@ 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);

View File

@@ -29,7 +29,7 @@ impl ShellExecutor {
}
let mut cmd = tokio::process::Command::new(&script_path);
let cmd = cmd.current_dir(&self.root.project_path);
let cmd = cmd.current_dir(path);
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
@@ -53,7 +53,6 @@ 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(),
}

View File

@@ -1,9 +0,0 @@
workspace {
members {
member "projects/a"
member "projects/b"
member "plan/a"
member "plan/b"
// member "components/*"
}
}

View File

@@ -1,7 +0,0 @@
plan {
name a
scripts {
hello_plan type=shell {}
}
}

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env zsh
set -e
echo "hello from plan"
echo "i am here: $PWD"

View File

@@ -1,3 +0,0 @@
plan {
name b
}

View File

@@ -1,11 +0,0 @@
project {
name a
plan {
workspace a
}
scripts {
hello type=shell {}
}
}

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env zsh
set -e
echo "hello from a"
echo "i am here: $PWD"

View File

@@ -1,7 +0,0 @@
project {
name b
scripts {
hello type=shell {}
}
}

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env zsh
set -e
echo "hello from b"