use colored::Colorize;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)]
pub struct GitHubOpts {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub on_push_branches: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub on_pull_requests_branches: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_image: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub preflight_steps: Vec<serde_pretty_yaml::Value>,
#[serde(default = "crate::utils::yes", skip_serializing_if = "crate::utils::is_true")]
pub enable_cache: bool,
}
impl Ord for GitHubOpts {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.base_image.cmp(&other.base_image)
}
}
#[allow(clippy::non_canonical_partial_ord_impl)]
impl PartialOrd for GitHubOpts {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl GitHubOpts {
pub fn compile_preflight(&self) -> anyhow::Result<String> {
let preflight = serde_pretty_yaml::to_string_pretty(&self.preflight_steps)?
.trim_end()
.split('\n')
.map(|line| format!(" {line}"))
.collect::<Vec<_>>()
.join("\n");
Ok(preflight)
}
pub fn new_from_prompt() -> anyhow::Result<Self> {
let on_push_branches = crate::utils::tags_custom_type(
"Enter branches to trigger on push (comma-separated, or leave empty):",
None,
)
.prompt()?;
let on_pull_requests_branches = crate::utils::tags_custom_type(
"Enter branches to trigger on pull requests (comma-separated, or leave empty):",
None,
)
.prompt()?;
let base_image = inquire_reorder::Text::new("Enter GitHub Actions runner image:")
.with_initial_value("ubuntu-latest")
.prompt_skippable()?;
let base_image = base_image.filter(|v| v != "ubuntu-latest");
let enable_cache = inquire_reorder::Confirm::new("Enable caching?")
.with_default(true)
.prompt()?;
Ok(Self {
on_push_branches,
on_pull_requests_branches,
base_image,
preflight_steps: vec![],
enable_cache,
})
}
pub async fn edit_from_prompt(&mut self) -> anyhow::Result<()> {
loop {
println!(
"Current GitHub Actions config: runner={}, push branches={:?}, PR branches={:?}, cache={}.",
self.base_image.as_deref().unwrap_or("ubuntu-latest").green(),
self.on_push_branches,
self.on_pull_requests_branches,
if self.enable_cache {
"enabled".green()
} else {
"disabled".green()
},
);
let opts = vec![
"Edit push trigger branches",
"Edit pull request trigger branches",
"Edit runner image",
"Edit preflight steps",
"Toggle cache",
];
let select = inquire_reorder::Select::new("Select action (hit `esc` when done):", opts).prompt_skippable()?;
let Some(select) = select else { return Ok(()) };
match select {
"Edit push trigger branches" => {
let joined = self.on_push_branches.join(", ");
self.on_push_branches = crate::utils::tags_custom_type(
"Enter branches to trigger on push (comma-separated):",
if joined.is_empty() { None } else { Some(joined.as_str()) },
)
.prompt()?;
}
"Edit pull request trigger branches" => {
let joined = self.on_pull_requests_branches.join(", ");
self.on_pull_requests_branches = crate::utils::tags_custom_type(
"Enter branches to trigger on pull requests (comma-separated):",
if joined.is_empty() { None } else { Some(joined.as_str()) },
)
.prompt()?;
}
"Edit runner image" => {
let image = inquire_reorder::Text::new("Enter GitHub Actions runner image:")
.with_initial_value(self.base_image.as_deref().unwrap_or("ubuntu-latest"))
.prompt_skippable()?;
self.base_image = image.filter(|v| v != "ubuntu-latest");
}
"Edit preflight steps" => {
let current = if self.preflight_steps.is_empty() {
String::new()
} else {
serde_pretty_yaml::to_string_pretty(&self.preflight_steps)
.unwrap_or_default()
.trim()
.to_string()
};
let new_steps = crate::tui::utils::edit_with_nano(
"Edit preflight steps in GitHub Actions YAML format.\nEach step should be a YAML mapping with `name`, `uses`, `run`, etc.\nWrite steps below this comment.",
current,
Some("yaml"),
)
.await?;
if new_steps.is_empty() {
self.preflight_steps = vec![];
} else {
match serde_pretty_yaml::from_str::<Vec<serde_pretty_yaml::Value>>(&new_steps) {
Ok(steps) => self.preflight_steps = steps,
Err(e) => println!("Failed to parse YAML steps: {e}. Keeping previous value."),
}
}
}
"Toggle cache" => {
self.enable_cache = inquire_reorder::Confirm::new("Enable caching?")
.with_default(self.enable_cache)
.prompt()?;
}
_ => unreachable!(),
}
}
}
}