| @@ -115,6 +115,7 @@ use clap::Parser; |
| use human_panic::setup_panic; |
| use miette::{IntoDiagnostic, Result}; |
| use tokio::fs::create_dir_all; |
| +use tracing::{info, warn}; |
| |
| use crate::{ |
| builders::{AnyBuilder, archive::ArchiveBuilderConfig}, |
| @@ -135,6 +136,119 @@ pub mod site; |
| /// Extracts current version information from different sources (ex: git tags, cargo metadata, etc.). |
| pub mod version_extractors; |
| |
| +/// Build the full website for every git tag, sorted from the lowest semver |
| +/// version to the highest. |
| +/// |
| +/// For each tag the function: |
| +/// 1. Runs `git checkout <tag>` to switch the working tree. |
| +/// 2. Loads `abbaye.toml` from the checked-out revision (falling back to the |
| +/// config that was active before the loop if the file is absent). |
| +/// 3. Calls [`site::build_site`] to produce the version page and update the |
| +/// root index and Atom feed. |
| +/// |
| +/// The original HEAD (branch or commit) is always restored after the loop, |
| +/// even when an error occurs. |
| +async fn build_all() -> Result<()> { |
| + // Load the current config to discover the version extractor settings. |
| + let base_config = config::load_config()?; |
| + |
| + // `git for-each-ref --sort=version:refname` returns tags in semver order, |
| + // lowest first, which is exactly the order we want. |
| + let all_versions = base_config.version_extractor.extract_all().await?; |
| + if all_versions.is_empty() { |
| + info!("No tagged versions found – nothing to build."); |
| + return Ok(()); |
| + } |
| + |
| + // Remember where we are so we can restore it when we're done. |
| + // Prefer the branch name (symbolic ref) so that checking it out |
| + // afterwards leaves the user on their branch rather than in a |
| + // detached-HEAD state. Fall back to the raw commit SHA when HEAD |
| + // is already detached. |
| + let symref_out = tokio::process::Command::new("git") |
| + .args(["symbolic-ref", "--short", "HEAD"]) |
| + .output() |
| + .await |
| + .into_diagnostic()?; |
| + let original_head = if symref_out.status.success() { |
| + // On a branch. |
| + String::from_utf8(symref_out.stdout) |
| + .into_diagnostic()? |
| + .trim() |
| + .to_owned() |
| + } else { |
| + // Detached HEAD — fall back to the commit SHA. |
| + let sha_out = tokio::process::Command::new("git") |
| + .args(["rev-parse", "HEAD"]) |
| + .output() |
| + .await |
| + .into_diagnostic()?; |
| + if !sha_out.status.success() { |
| + return Err(miette::miette!("Could not determine current HEAD")); |
| + } |
| + String::from_utf8(sha_out.stdout) |
| + .into_diagnostic()? |
| + .trim() |
| + .to_owned() |
| + }; |
| + |
| + let total = all_versions.len(); |
| + info!("Building {} version(s) …", total); |
| + |
| + // Run the build loop; capture the result so we can restore HEAD first. |
| + let loop_result = async { |
| + for (i, version_info) in all_versions.iter().enumerate() { |
| + let tag = base_config |
| + .version_extractor |
| + .tag_name(&version_info.version); |
| + |
| + info!("[{}/{}] Checking out {} …", i + 1, total, tag); |
| + |
| + let checkout = tokio::process::Command::new("git") |
| + .args(["checkout", &tag]) |
| + .output() |
| + .await |
| + .into_diagnostic()?; |
| + if !checkout.status.success() { |
| + let stderr = String::from_utf8_lossy(&checkout.stderr); |
| + return Err(miette::miette!("git checkout {tag} failed:\n{stderr}")); |
| + } |
| + |
| + // Reload `abbaye.toml` from the checked-out revision so the build |
| + // uses that version's own configuration (builders, readme path, |
| + // etc.). If the file does not exist in this revision, fall back |
| + // to the config we loaded before the loop. |
| + let version_config = config::load_config().unwrap_or_else(|_| base_config.clone()); |
| + |
| + info!( |
| + "[{}/{}] Building version {} …", |
| + i + 1, |
| + total, |
| + version_info.version |
| + ); |
| + |
| + site::build_site(version_config).await?; |
| + } |
| + Ok(()) |
| + } |
| + .await; |
| + |
| + // Always restore HEAD, regardless of whether the loop succeeded. |
| + let restore = tokio::process::Command::new("git") |
| + .args(["checkout", &original_head]) |
| + .output() |
| + .await |
| + .into_diagnostic()?; |
| + if !restore.status.success() { |
| + let stderr = String::from_utf8_lossy(&restore.stderr); |
| + warn!("Could not restore HEAD to {original_head}:\n{stderr}"); |
| + } |
| + |
| + loop_result?; |
| + info!("Done. Built {total} version(s)."); |
| + Ok(()) |
| +} |
| + |
| #[tokio::main] |
| async fn main() -> Result<()> { |
| setup_panic!(); |
| @@ -190,6 +304,9 @@ async fn main() -> Result<()> { |
| let config = config::load_config()?; |
| site::build_site(config).await?; |
| } |
| + cli::CliCommand::BuildAll => { |
| + build_all().await?; |
| + } |
| cli::CliCommand::DumpTheme => { |
| let theme_path = PathBuf::from(".abbaye").join("theme"); |
| create_dir_all(&theme_path).await.into_diagnostic()?; |