feat: refactor frontend configuration
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
@@ -4,6 +4,8 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cuddle-please-frontend.workspace = true
|
||||
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
@@ -8,11 +8,10 @@ use std::{
|
||||
use ::semver::Version;
|
||||
use anyhow::Context;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use cuddle_please_frontend::{gatheres::ConfigArgs, PleaseConfig, PleaseConfigBuilder};
|
||||
|
||||
use crate::{
|
||||
cliff::{self, changelog_parser},
|
||||
environment::get_from_environment,
|
||||
git_client::VcsClient,
|
||||
gitea_client::GiteaClient,
|
||||
ui::{ConsoleUi, DynUi},
|
||||
@@ -27,6 +26,9 @@ pub struct Command {
|
||||
#[command(flatten)]
|
||||
global: GlobalArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
config: ConfigArgs,
|
||||
|
||||
#[command(subcommand)]
|
||||
commands: Option<Commands>,
|
||||
|
||||
@@ -49,32 +51,13 @@ struct GlobalArgs {
|
||||
)]
|
||||
token: Option<String>,
|
||||
|
||||
/// Which repository to publish against. If not supplied remote url will be inferred from environment or fail if not present.
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
api_url: Option<String>,
|
||||
|
||||
/// repo is the name of repository you want to release for
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
repo: Option<String>,
|
||||
|
||||
/// owner is the name of user from which the repository belongs <user>/<repo>
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
owner: Option<String>,
|
||||
|
||||
/// which source directory to use, if not set `std::env::current_dir` is used instead.
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
source: Option<PathBuf>,
|
||||
|
||||
/// which branch is being run from
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
branch: Option<String>,
|
||||
|
||||
/// whether to run in dry run mode (i.e. no pushes or releases)
|
||||
#[arg(long, global = true, help_heading = "Global")]
|
||||
dry_run: bool,
|
||||
|
||||
/// Inject configuration from stdin
|
||||
#[arg(
|
||||
env = "CUDDLE_PLEASE_CONFIG_STDIN",
|
||||
long,
|
||||
global = true,
|
||||
help_heading = "Global",
|
||||
@@ -138,31 +121,23 @@ impl Command {
|
||||
s
|
||||
}
|
||||
|
||||
fn get_config(
|
||||
&self,
|
||||
current_dir: &Path,
|
||||
stdin: Option<String>,
|
||||
) -> anyhow::Result<PleaseConfig> {
|
||||
let mut config = get_config(current_dir, stdin)?;
|
||||
|
||||
self.get_from_environment(&mut config)?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> {
|
||||
// 1. Parse the current directory
|
||||
let current_dir = get_current_path(current_dir, self.global.source.clone())?;
|
||||
let stdin = if self.global.config_stdin {
|
||||
// 0. Get config
|
||||
let mut builder = &mut PleaseConfigBuilder::new();
|
||||
|
||||
if self.global.config_stdin {
|
||||
if let Some(stdin_fn) = self.stdin.clone() {
|
||||
let output = (stdin_fn.lock().unwrap().deref())();
|
||||
Some(output.unwrap())
|
||||
} else {
|
||||
None
|
||||
builder = builder.with_stdin(output?);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
// 1. Parse the current directory
|
||||
let current_dir = get_current_path(current_dir, self.config.source.clone())?;
|
||||
let config = builder
|
||||
.with_config_file(¤t_dir)
|
||||
.with_execution_env(std::env::vars())
|
||||
.with_cli(self.config.clone())
|
||||
.build()?;
|
||||
|
||||
match &self.commands {
|
||||
Some(Commands::Release {}) => {
|
||||
@@ -170,30 +145,24 @@ impl Command {
|
||||
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
|
||||
// 2a. if not existing use default.
|
||||
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
|
||||
let _config = self.get_config(¤t_dir, stdin)?;
|
||||
|
||||
let owner = self.global.owner.as_ref().expect("owner to be set");
|
||||
let repo = self.global.repo.as_ref().expect("repo to be set");
|
||||
let branch = self.global.branch.as_ref().expect("branch to be set");
|
||||
|
||||
let git_client = self.get_git(¤t_dir)?;
|
||||
let git_client = self.get_git(config.get_source())?;
|
||||
|
||||
// 3. Create gitea client and do a health check
|
||||
let gitea_client = self.get_gitea_client();
|
||||
gitea_client
|
||||
.connect(owner, repo)
|
||||
.connect(config.get_owner(), config.get_repository())
|
||||
.context("failed to connect to gitea repository")?;
|
||||
// 4. Fetch git tags for the current repository
|
||||
let tags = gitea_client.get_tags(owner, repo)?;
|
||||
let tags = gitea_client.get_tags(config.get_owner(), config.get_repository())?;
|
||||
|
||||
let significant_tag = get_most_significant_version(tags.iter().collect());
|
||||
|
||||
// 5. Fetch git commits since last git tag
|
||||
let commits = gitea_client.get_commits_since(
|
||||
owner,
|
||||
repo,
|
||||
config.get_owner(),
|
||||
config.get_repository(),
|
||||
significant_tag.map(|st| st.commit.sha.clone()),
|
||||
branch,
|
||||
config.get_branch(),
|
||||
)?;
|
||||
|
||||
// 7. Create a versioning client
|
||||
@@ -219,12 +188,7 @@ impl Command {
|
||||
let builder =
|
||||
cliff::ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build();
|
||||
|
||||
let changelog_placement = self
|
||||
.global
|
||||
.source
|
||||
.as_ref()
|
||||
.map(|s| s.join("CHANGELOG.md"))
|
||||
.unwrap_or(PathBuf::from("CHANGELOG.md"));
|
||||
let changelog_placement = config.get_source().join("CHANGELOG.md");
|
||||
|
||||
let changelog = match std::fs::read_to_string(&changelog_placement).ok() {
|
||||
Some(existing_changelog) => builder.prepend(existing_changelog)?,
|
||||
@@ -239,8 +203,8 @@ impl Command {
|
||||
if first_commit.contains("chore(release): ") {
|
||||
if !self.global.dry_run {
|
||||
gitea_client.create_release(
|
||||
owner,
|
||||
repo,
|
||||
config.get_owner(),
|
||||
config.get_repository(),
|
||||
next_version.to_string(),
|
||||
changelog_last_changes.unwrap(),
|
||||
!next_version.pre.is_empty(),
|
||||
@@ -261,12 +225,14 @@ impl Command {
|
||||
|
||||
git_client.commit_and_push(next_version.to_string(), self.global.dry_run)?;
|
||||
|
||||
let _pr_number = match gitea_client.get_pull_request(owner, repo)? {
|
||||
let _pr_number = match gitea_client
|
||||
.get_pull_request(config.get_owner(), config.get_repository())?
|
||||
{
|
||||
Some(existing_pr) => {
|
||||
if !self.global.dry_run {
|
||||
gitea_client.update_pull_request(
|
||||
owner,
|
||||
repo,
|
||||
config.get_owner(),
|
||||
config.get_repository(),
|
||||
next_version.to_string(),
|
||||
changelog_last_changes.unwrap(),
|
||||
existing_pr,
|
||||
@@ -279,11 +245,11 @@ impl Command {
|
||||
None => {
|
||||
if !self.global.dry_run {
|
||||
gitea_client.create_pull_request(
|
||||
owner,
|
||||
repo,
|
||||
config.get_owner(),
|
||||
config.get_repository(),
|
||||
next_version.to_string(),
|
||||
changelog,
|
||||
self.global.branch.clone().unwrap(),
|
||||
config.get_branch(),
|
||||
)?
|
||||
} else {
|
||||
tracing::debug!("creating pull request (dry_run)");
|
||||
@@ -296,13 +262,12 @@ impl Command {
|
||||
Some(Commands::Config { command }) => match command {
|
||||
ConfigCommand::List { .. } => {
|
||||
tracing::debug!("running command: config list");
|
||||
let _config = self.get_config(current_dir.as_path(), stdin)?;
|
||||
|
||||
self.ui.write_str_ln("cuddle-config");
|
||||
}
|
||||
},
|
||||
Some(Commands::Gitea { command }) => {
|
||||
let git_url = url::Url::parse(&self.global.api_url.unwrap())?;
|
||||
let git_url = url::Url::parse(config.get_api_url())?;
|
||||
|
||||
let mut url = String::new();
|
||||
url.push_str(git_url.scheme());
|
||||
@@ -315,13 +280,13 @@ impl Command {
|
||||
let client = GiteaClient::new(url, self.global.token);
|
||||
match command {
|
||||
GiteaCommand::Connect {} => {
|
||||
client.connect(self.global.owner.unwrap(), self.global.repo.unwrap())?;
|
||||
client.connect(config.get_owner(), config.get_repository())?;
|
||||
self.ui.write_str_ln("connected succesfully go gitea");
|
||||
}
|
||||
GiteaCommand::Tags { command } => match command {
|
||||
Some(GiteaTagsCommand::MostSignificant {}) => {
|
||||
let tags = client
|
||||
.get_tags(self.global.owner.unwrap(), self.global.repo.unwrap())?;
|
||||
let tags =
|
||||
client.get_tags(config.get_owner(), config.get_repository())?;
|
||||
|
||||
match get_most_significant_version(tags.iter().collect()) {
|
||||
Some(tag) => {
|
||||
@@ -336,8 +301,8 @@ impl Command {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let tags = client
|
||||
.get_tags(self.global.owner.unwrap(), self.global.repo.unwrap())?;
|
||||
let tags =
|
||||
client.get_tags(config.get_owner(), config.get_repository())?;
|
||||
self.ui.write_str_ln("got tags from gitea");
|
||||
for tag in tags {
|
||||
self.ui.write_str_ln(&format!("- {}", tag.name))
|
||||
@@ -346,8 +311,8 @@ impl Command {
|
||||
},
|
||||
GiteaCommand::SinceCommit { sha, branch } => {
|
||||
let commits = client.get_commits_since(
|
||||
self.global.owner.unwrap(),
|
||||
self.global.repo.unwrap(),
|
||||
config.get_owner(),
|
||||
config.get_repository(),
|
||||
Some(sha),
|
||||
branch,
|
||||
)?;
|
||||
@@ -357,10 +322,8 @@ impl Command {
|
||||
}
|
||||
}
|
||||
GiteaCommand::CheckPr {} => {
|
||||
let pr = client.get_pull_request(
|
||||
self.global.owner.unwrap(),
|
||||
self.global.repo.unwrap(),
|
||||
)?;
|
||||
let pr =
|
||||
client.get_pull_request(config.get_owner(), config.get_repository())?;
|
||||
|
||||
match pr {
|
||||
Some(index) => {
|
||||
@@ -398,16 +361,9 @@ impl Command {
|
||||
VcsClient::new_git(current_dir)
|
||||
}
|
||||
|
||||
fn get_from_environment(&self, config: &mut PleaseConfig) -> anyhow::Result<()> {
|
||||
let input_config = get_from_environment();
|
||||
config.merge_mut(input_config);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_gitea_client(&self) -> GiteaClient {
|
||||
GiteaClient::new(
|
||||
self.global.api_url.clone().expect("api_url to be set"),
|
||||
self.config.api_url.clone().expect("api_url to be set"),
|
||||
self.global.token.clone(),
|
||||
)
|
||||
}
|
||||
@@ -469,7 +425,8 @@ fn get_current_path(
|
||||
.or_else(|| optional_current_dir.map(|p| p.to_path_buf())) // fall back on current env from environment
|
||||
.filter(|v| v.to_string_lossy() != "") // make sure we don't get empty values
|
||||
//.and_then(|p| p.canonicalize().ok()) // Make sure we get the absolute path
|
||||
.context("could not find current dir, pass --source as a replacement")?;
|
||||
//.context("could not find current dir, pass --source as a replacement")?;
|
||||
.unwrap_or(PathBuf::from("."));
|
||||
|
||||
if !path.exists() {
|
||||
anyhow::bail!("path doesn't exist {}", path.display());
|
||||
@@ -477,131 +434,3 @@ fn get_current_path(
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PleaseProjectConfig {
|
||||
pub owner: Option<String>,
|
||||
pub repository: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PleaseSettingsConfig {
|
||||
pub api_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct PleaseConfig {
|
||||
pub project: Option<PleaseProjectConfig>,
|
||||
pub settings: Option<PleaseSettingsConfig>,
|
||||
}
|
||||
|
||||
impl PleaseConfig {
|
||||
fn merge(self, _config: PleaseConfig) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
fn merge_mut(&mut self, _config: PleaseConfig) -> &mut Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CuddleEmbeddedPleaseConfig {
|
||||
please: PleaseConfig,
|
||||
}
|
||||
|
||||
impl From<CuddleEmbeddedPleaseConfig> for PleaseConfig {
|
||||
fn from(value: CuddleEmbeddedPleaseConfig) -> Self {
|
||||
value.please
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CuddlePleaseConfig {
|
||||
#[serde(flatten)]
|
||||
please: PleaseConfig,
|
||||
}
|
||||
impl From<CuddlePleaseConfig> for PleaseConfig {
|
||||
fn from(value: CuddlePleaseConfig) -> Self {
|
||||
value.please
|
||||
}
|
||||
}
|
||||
|
||||
const CUDDLE_FILE_NAME: &str = "cuddle";
|
||||
const CUDDLE_CONFIG_FILE_NAME: &str = "cuddle.please";
|
||||
const YAML_EXTENSION: &str = "yaml";
|
||||
|
||||
fn get_config(current_dir: &Path, stdin: Option<String>) -> anyhow::Result<PleaseConfig> {
|
||||
let current_cuddle_path = current_dir
|
||||
.clone()
|
||||
.join(format!("{CUDDLE_FILE_NAME}.{YAML_EXTENSION}"));
|
||||
let current_cuddle_config_path = current_dir
|
||||
.clone()
|
||||
.join(format!("{CUDDLE_CONFIG_FILE_NAME}.{YAML_EXTENSION}"));
|
||||
let mut please_config = PleaseConfig::default();
|
||||
|
||||
if let Some(config) = get_config_from_file::<CuddleEmbeddedPleaseConfig>(current_cuddle_path) {
|
||||
please_config = please_config.merge(config);
|
||||
}
|
||||
|
||||
if let Some(config) = get_config_from_file::<CuddlePleaseConfig>(current_cuddle_config_path) {
|
||||
please_config = please_config.merge(config);
|
||||
}
|
||||
|
||||
if let Some(input_config) = get_config_from_stdin::<CuddlePleaseConfig>(stdin.as_ref()) {
|
||||
please_config = please_config.merge(input_config);
|
||||
}
|
||||
|
||||
Ok(please_config)
|
||||
}
|
||||
|
||||
fn get_config_from_file<T>(current_cuddle_path: PathBuf) -> Option<PleaseConfig>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
T: Into<PleaseConfig>,
|
||||
{
|
||||
match std::fs::File::open(¤t_cuddle_path) {
|
||||
Ok(file) => match serde_yaml::from_reader::<_, T>(file) {
|
||||
Ok(config) => {
|
||||
return Some(config.into());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
"{} doesn't contain a valid please config: {}",
|
||||
¤t_cuddle_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::debug!(
|
||||
"did not find or was not allowed to read {}, error: {}",
|
||||
¤t_cuddle_path.display(),
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn get_config_from_stdin<'d, T>(stdin: Option<&'d String>) -> Option<PleaseConfig>
|
||||
where
|
||||
T: Deserialize<'d>,
|
||||
T: Into<PleaseConfig>,
|
||||
{
|
||||
match stdin {
|
||||
Some(content) => match serde_yaml::from_str::<'d, T>(content) {
|
||||
Ok(config) => {
|
||||
return Some(config.into());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("stdin doesn't contain a valid please config: {}", e);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("Stdin was not set continueing",);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
@@ -1 +0,0 @@
|
||||
|
@@ -1,38 +0,0 @@
|
||||
use crate::command::{PleaseConfig, PleaseProjectConfig};
|
||||
|
||||
pub mod drone;
|
||||
|
||||
pub fn get_from_environment() -> PleaseConfig {
|
||||
let env = detect_environment();
|
||||
|
||||
match env {
|
||||
ExecutionEnvironment::Local => PleaseConfig {
|
||||
project: None,
|
||||
settings: None,
|
||||
},
|
||||
ExecutionEnvironment::Drone => PleaseConfig {
|
||||
project: Some(PleaseProjectConfig {
|
||||
owner: Some(
|
||||
std::env::var("DRONE_REPO_OWNER").expect("DRONE_REPO_OWNER to be present"),
|
||||
),
|
||||
repository: Some(
|
||||
std::env::var("DRONE_REPO_NAME").expect("DRONE_REPO_NAME to be present"),
|
||||
),
|
||||
}),
|
||||
settings: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_environment() -> ExecutionEnvironment {
|
||||
if std::env::var("DRONE").is_ok() {
|
||||
return ExecutionEnvironment::Drone;
|
||||
}
|
||||
|
||||
ExecutionEnvironment::Local
|
||||
}
|
||||
|
||||
pub enum ExecutionEnvironment {
|
||||
Local,
|
||||
Drone,
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
pub mod cliff;
|
||||
pub mod command;
|
||||
pub mod environment;
|
||||
pub mod git_client;
|
||||
pub mod gitea_client;
|
||||
pub mod ui;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
pub mod cliff;
|
||||
pub mod command;
|
||||
pub mod environment;
|
||||
pub mod git_client;
|
||||
pub mod gitea_client;
|
||||
pub mod ui;
|
||||
|
6
crates/cuddle-please/testdata/cuddle-embed/cuddle.please.yaml
vendored
Normal file
6
crates/cuddle-please/testdata/cuddle-embed/cuddle.please.yaml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
project:
|
||||
owner: kjuulh
|
||||
repository: cuddle-please
|
||||
branch: main
|
||||
settings:
|
||||
api_url: https://some-example.gitea-instance
|
@@ -0,0 +1,7 @@
|
||||
please:
|
||||
project:
|
||||
owner: kjuulh
|
||||
repository: cuddle-please
|
||||
branch: main
|
||||
settings:
|
||||
api_url: https://some-example.gitea-instance
|
@@ -0,0 +1,6 @@
|
||||
project:
|
||||
owner: kjuulh
|
||||
repository: cuddle-please
|
||||
branch: main
|
||||
settings:
|
||||
api_url: https://some-example.gitea-instance
|
@@ -45,15 +45,19 @@ fn test_config_from_source_dir() {
|
||||
fn test_config_from_stdin() {
|
||||
let mut args = get_base_args();
|
||||
let ui = &BufferUi::default();
|
||||
let current_dir = get_test_data_path("cuddle-embed");
|
||||
args.push("--source");
|
||||
args.push(current_dir.to_str().unwrap());
|
||||
args.push("--config-stdin");
|
||||
let config = r#"
|
||||
project:
|
||||
owner: kjuulh
|
||||
repository: cuddle-please
|
||||
branch: main
|
||||
settings:
|
||||
api_url: https://some-example.gitea-instance
|
||||
"#;
|
||||
|
||||
Command::new_from_args_with_stdin(Some(ui), args, || Ok("please".into()))
|
||||
args.push("--config-stdin");
|
||||
Command::new_from_args_with_stdin(Some(ui), args, || Ok(config.into()))
|
||||
.execute(None)
|
||||
.unwrap();
|
||||
|
||||
assert_output(ui, "cuddle-config\n", "");
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user