diff --git a/Cargo.lock b/Cargo.lock index 838f373..60f6edf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -520,6 +520,19 @@ dependencies = [ "url", ] +[[package]] +name = "cuddle-please-release-strategy" +version = "0.1.0" +dependencies = [ + "anyhow", + "pretty_assertions", + "semver", + "serde", + "tempdir", + "tracing", + "tracing-test", +] + [[package]] name = "dagger-core" version = "0.2.11" diff --git a/Cargo.toml b/Cargo.toml index f50eef8..16bc1a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/cuddle-please-frontend", "crates/cuddle-please-commands", "crates/cuddle-please-misc", + "crates/cuddle-please-release-strategy", "ci" ] resolver = "2" @@ -13,6 +14,7 @@ cuddle-please = { path = "crates/cuddle-please", version = "0.1.0" } cuddle-please-frontend = { path = "crates/cuddle-please-frontend", version = "0.1.0" } cuddle-please-commands = { path = "crates/cuddle-please-commands", version = "0.1.0" } cuddle-please-misc = { path = "crates/cuddle-please-misc", version = "0.1.0" } +cuddle-please-release-strategy = { path = "crates/cuddle-please-release-strategy", version = "0.1.0" } anyhow = { version = "1.0.72" } tracing = { version = "0.1", features = ["log"] } diff --git a/cr b/cr new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cr @@ -0,0 +1 @@ + diff --git a/crates/cuddle-please-release-strategy/Cargo.toml b/crates/cuddle-please-release-strategy/Cargo.toml new file mode 100644 index 0000000..8869eac --- /dev/null +++ b/crates/cuddle-please-release-strategy/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "cuddle-please-release-strategy" +description = "A release-please inspired release manager tool, built on top of cuddle, but also useful standalone, cuddle-please supports, your ci of choice, as well as gitea, github" +repository = "https://git.front.kjuulh.io/kjuulh/cuddle-please" +version = "0.1.0" +edition = "2021" +readme = "../../README.md" +license-file = "../../LICENSE" +publishable = true + +[dependencies] +anyhow.workspace = true +tracing.workspace = true +serde.workspace = true +semver.workspace = true + +[dev-dependencies] +tracing-test = { workspace = true, features = ["no-env-filter"] } +pretty_assertions.workspace = true +tempdir.workspace = true + +[features] +rust-workspace = [] +rust-crate = [] +toml-edit = [] +json-edit = [] +yaml-edit = [] + +default = [ + "json-edit" +] diff --git a/crates/cuddle-please-release-strategy/src/json_edit.rs b/crates/cuddle-please-release-strategy/src/json_edit.rs new file mode 100644 index 0000000..b3a7d51 --- /dev/null +++ b/crates/cuddle-please-release-strategy/src/json_edit.rs @@ -0,0 +1,57 @@ +use std::path::Path; + +use anyhow::Context; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JsonEditOptions { + pub jq: String, +} + +impl JsonEditOptions { + pub fn execute(&self, path: &Path, next_version: impl AsRef) -> anyhow::Result<()> { + let next_version = next_version.as_ref(); + + let jq_query = self.jq.replace("%%version%%", next_version); + + if !path.exists() { + anyhow::bail!("could not find file at: {}", path.display()); + } + + if let Ok(metadata) = path.metadata() { + if !metadata.is_file() { + anyhow::bail!("{} is not a file", path.display()); + } + } + + let abs_path = path.canonicalize().context(anyhow::anyhow!( + "could not get absolute path from {}", + path.display() + ))?; + + let output = std::process::Command::new("jq") + .arg(format!("{}", jq_query)) + .arg( + abs_path + .to_str() + .ok_or(anyhow::anyhow!("path contains non utf-8 chars"))?, + ) + .output() + .context(anyhow::anyhow!("failed to run jq on file"))?; + + if !output.status.success() { + let err_content = std::str::from_utf8(output.stderr.as_slice())?; + anyhow::bail!("failed to run jq with output: {}", err_content); + } + + let edited_json_content = std::str::from_utf8(output.stdout.as_slice())?; + tracing::trace!( + new_content = edited_json_content, + file = &abs_path.display().to_string(), + "applied jq to file" + ); + std::fs::write(abs_path, edited_json_content)?; + + Ok(()) + } +} diff --git a/crates/cuddle-please-release-strategy/src/lib.rs b/crates/cuddle-please-release-strategy/src/lib.rs new file mode 100644 index 0000000..8ff1005 --- /dev/null +++ b/crates/cuddle-please-release-strategy/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "json-edit")] +mod json_edit; +mod strategy; + +#[cfg(feature = "json-edit")] +pub use json_edit::JsonEditOptions; diff --git a/crates/cuddle-please-release-strategy/src/strategy.rs b/crates/cuddle-please-release-strategy/src/strategy.rs new file mode 100644 index 0000000..1db872b --- /dev/null +++ b/crates/cuddle-please-release-strategy/src/strategy.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +pub struct UpdateOptions { + next_version: String, + global_changelog: String, +} + +pub type Projects = Vec; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Project { + path: Option, + r#type: ProjectType, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum ProjectType { + #[cfg(feature = "rust-workspace")] + #[serde(alias = "rust_workspace")] + RustWorkspace, + #[cfg(feature = "rust-crate")] + #[serde(alias = "json_edit")] + RustCrate, + #[cfg(feature = "toml-edit")] + #[serde(alias = "toml_edit")] + TomlEdit, + #[cfg(feature = "yaml-edit")] + #[serde(alias = "yaml_edit")] + YamlEdit, + #[cfg(feature = "json-edit")] + #[serde(alias = "json_edit")] + JsonEdit, +} + +impl Project { + pub fn new(path: Option, r#type: ProjectType) -> Self { + Self { path, r#type } + } + + pub fn execute(&self, options: &UpdateOptions) -> anyhow::Result<()> { + match self.r#type { + #[cfg(feature = "rust-workspace")] + ProjectType::RustWorkspace => todo!(), + #[cfg(feature = "rust-crate")] + ProjectType::RustCrate => todo!(), + #[cfg(feature = "toml-edit")] + ProjectType::TomlEdit => todo!(), + #[cfg(feature = "yaml-edit")] + ProjectType::YamlEdit => todo!(), + #[cfg(feature = "json-edit")] + ProjectType::JsonEdit => todo!(), + } + + Ok(()) + } +} diff --git a/crates/cuddle-please-release-strategy/tests/json_edit.rs b/crates/cuddle-please-release-strategy/tests/json_edit.rs new file mode 100644 index 0000000..a9a92b8 --- /dev/null +++ b/crates/cuddle-please-release-strategy/tests/json_edit.rs @@ -0,0 +1,50 @@ +use tracing_test::traced_test; + +#[test] +#[traced_test] +#[cfg(feature = "json-edit")] +pub fn test_can_update_version_in_jq() { + use cuddle_please_release_strategy::JsonEditOptions; + + let dir = tempdir::TempDir::new("can_update_version_in_jq").unwrap(); + let dir_path = dir.path(); + let json_file = dir_path.join("some-test.json"); + let initial_content = r#"{ + "some": { + "nested": [ + { + "structure": { + "version": "v1.0.1" + } + } + ] + } +} +"#; + + let expected = r#"{ + "some": { + "nested": [ + { + "structure": { + "version": "v1.0.2" + } + } + ] + } +} +"#; + + std::fs::write(&json_file, initial_content).unwrap(); + let actual_file = std::fs::read_to_string(&json_file).unwrap(); + pretty_assertions::assert_eq!(initial_content, actual_file); + + let edit_options = JsonEditOptions { + jq: r#".some.nested[].structure.version="%%version%%""#.into(), + }; + + edit_options.execute(&json_file, "v1.0.2").unwrap(); + + let actual_file = std::fs::read_to_string(&json_file).unwrap(); + pretty_assertions::assert_eq!(expected, &actual_file); +}