use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ForgeKind {
#[default]
Auto,
#[serde(alias = "GitHub")]
Github,
#[serde(alias = "GitLab")]
Gitlab,
#[serde(alias = "Gitea", alias = "forgejo", alias = "Forgejo")]
Gitea,
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct HooksConfig {
#[serde(alias = "preBump")]
pub pre_bump: Option<String>,
#[serde(alias = "postBump")]
pub post_bump: Option<String>,
#[serde(alias = "preCommit")]
pub pre_commit: Option<String>,
#[serde(alias = "postCommit")]
pub post_commit: Option<String>,
#[serde(alias = "preTag")]
pub pre_tag: Option<String>,
#[serde(alias = "postTag")]
pub post_tag: Option<String>,
#[serde(alias = "prePublish")]
pub pre_publish: Option<String>,
#[serde(alias = "postPublish")]
pub post_publish: Option<String>,
#[serde(alias = "preRelease")]
pub pre_release: Option<String>,
#[serde(alias = "onSuccess")]
pub on_success: Option<String>,
#[serde(alias = "onError")]
pub on_error: Option<String>,
#[serde(default, alias = "onFailure")]
pub on_failure: Option<OnFailure>,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum OnFailure {
#[default]
Abort,
Continue,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub enum OrphanedTagStrategy {
#[default]
Warn,
TreeHash,
Message,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BranchChannelConfig {
pub name: String,
#[serde(default)]
pub channel: ChannelValue,
#[serde(default, alias = "prereleaseIdentifier")]
pub prerelease_identifier: PrereleaseIdentifier,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ChannelValue {
Stable(bool),
Named(String),
}
impl Default for ChannelValue {
fn default() -> Self {
ChannelValue::Stable(false)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "kebab-case")]
pub enum PrereleaseIdentifier {
#[default]
Increment,
Timestamp,
ShortHash,
TimestampHash,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ReleaseCommitMode {
#[default]
Commit,
Pr,
None,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ReleaseCommitScope {
#[default]
Grouped,
PerPackage,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
pub struct RegistryConfig {
#[serde(default)]
pub url: Option<String>,
#[serde(alias = "tokenEnv")]
pub token_env: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum PublisherConfig {
Cargo {
#[serde(default)]
registry: Option<String>,
#[serde(default, rename = "allowDirty")]
allow_dirty: bool,
#[serde(default, rename = "noVerify")]
no_verify: bool,
#[serde(default)]
args: Vec<String>,
},
Npm {
#[serde(default)]
registry: Option<String>,
#[serde(default)]
tag: Option<String>,
#[serde(default)]
access: Option<String>,
#[serde(default)]
args: Vec<String>,
},
Docker {
image: String,
#[serde(default = "default_docker_tags")]
tags: Vec<String>,
#[serde(default)]
platforms: Vec<String>,
#[serde(default = "default_docker_context")]
context: String,
#[serde(default = "default_dockerfile")]
dockerfile: String,
#[serde(default)]
sign: DockerSign,
#[serde(default)]
args: Vec<String>,
},
Helm {
#[serde(default = "default_helm_chart_path")]
chart: String,
registry: String,
#[serde(default)]
args: Vec<String>,
},
GithubReleaseAsset {
path: String,
#[serde(default, rename = "displayName")]
display_name: Option<String>,
#[serde(default)]
args: Vec<String>,
},
Webhook {
url: String,
#[serde(default)]
body: Option<serde_json::Value>,
#[serde(default)]
headers: std::collections::BTreeMap<String, String>,
},
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum DockerSign {
#[default]
None,
Sigstore,
}
fn default_docker_tags() -> Vec<String> {
vec!["{version}".to_string()]
}
fn default_docker_context() -> String {
".".to_string()
}
fn default_dockerfile() -> String {
"Dockerfile".to_string()
}
fn default_helm_chart_path() -> String {
".".to_string()
}
impl PublisherConfig {
pub fn kind_name(&self) -> &'static str {
match self {
PublisherConfig::Cargo { .. } => "cargo",
PublisherConfig::Npm { .. } => "npm",
PublisherConfig::Docker { .. } => "docker",
PublisherConfig::Helm { .. } => "helm",
PublisherConfig::GithubReleaseAsset { .. } => "github-release-asset",
PublisherConfig::Webhook { .. } => "webhook",
}
}
pub fn describe(&self, package_name: &str, new_version: &str) -> String {
match self {
PublisherConfig::Cargo { registry, .. } => {
let reg = registry.as_deref().unwrap_or("crates-io");
format!("cargo publish {package_name}@{new_version} → {reg}")
}
PublisherConfig::Npm { registry, tag, .. } => {
let reg = registry.as_deref().unwrap_or("npmjs.org");
let tag = tag.as_deref().unwrap_or("latest");
format!("npm publish {package_name}@{new_version} → {reg} (tag={tag})")
}
PublisherConfig::Docker {
image,
tags,
platforms,
sign,
..
} => {
let resolved: Vec<String> = tags
.iter()
.map(|t| t.replace("{version}", new_version))
.collect();
let platforms_s = if platforms.is_empty() {
"linux/amd64".to_string()
} else {
platforms.join(",")
};
let sign_s = match sign {
DockerSign::None => "",
DockerSign::Sigstore => " +sigstore",
};
format!(
"docker push {image}:[{}] platforms={platforms_s}{sign_s}",
resolved.join(", ")
)
}
PublisherConfig::Helm {
chart, registry, ..
} => {
format!("helm push {chart} {new_version} → {registry}")
}
PublisherConfig::GithubReleaseAsset {
path, display_name, ..
} => {
let shown = display_name.as_deref().unwrap_or(path);
format!("upload {shown} → GitHub Release {new_version}")
}
PublisherConfig::Webhook { url, .. } => {
format!("POST {url} ({package_name}@{new_version})")
}
}
}
}