1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize};
// ---------------------------------------------------------------------------
// HooksConfig
// ---------------------------------------------------------------------------
/// Top-level lifecycle hooks for `before` and `after` blocks.
/// Each block has `pre` and `post` lists of hook commands that run around the
/// entire pipeline (not individual stages).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default)]
pub struct HooksConfig {
/// Commands to run before the pipeline or stage starts. Matches GoReleaser
/// `before.hooks` canonically.
pub hooks: Option<Vec<HookEntry>>,
/// Commands to run after the pipeline or stage completes. Anodizer extension
/// (GoReleaser has no top-level `after:` block).
pub post: Option<Vec<HookEntry>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default)]
pub struct StructuredHook {
/// Command to run.
///
/// The entire string is interpreted by `sh -c`, so shell metacharacters
/// (`|`, `;`, `&&`, backticks, `$()`, redirects, globs) are honoured —
/// any templated values folded into `cmd` become part of the shell
/// command and are subject to word-splitting and metacharacter expansion.
/// Keep templated user-config values out of `cmd` when possible, or quote
/// them defensively (e.g. `'{{ .Env.FOO }}'`). Hooks already run with
/// `env_clear()` plus an allow-list, so secrets in `$ENV` are not
/// inherited unless explicitly listed in `env`.
pub cmd: String,
/// Working directory for the command (defaults to project root).
pub dir: Option<String>,
/// Environment variables for the command.
#[serde(default)]
pub env: Option<Vec<String>>,
/// When true, capture and log stdout/stderr of the command.
pub output: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum HookEntry {
Simple(String),
Structured(StructuredHook),
}
impl PartialEq<&str> for HookEntry {
fn eq(&self, other: &&str) -> bool {
match self {
HookEntry::Simple(s) => s.as_str() == *other,
HookEntry::Structured(h) => h.cmd.as_str() == *other,
}
}
}
impl<'de> Deserialize<'de> for HookEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = serde_json::Value::deserialize(deserializer)?;
match &value {
serde_json::Value::String(s) => Ok(HookEntry::Simple(s.clone())),
serde_json::Value::Object(_) => {
let hook: StructuredHook =
serde_json::from_value(value).map_err(serde::de::Error::custom)?;
Ok(HookEntry::Structured(hook))
}
_ => Err(serde::de::Error::custom(
"hook entry must be a string or an object with cmd/dir/env/output",
)),
}
}
}