use mesofact_publisher::{
publish_dist, publish_pin, CloudflareCdnPurger, ConfigError, InMemoryPurger, InMemoryStore,
PublishConfig, PublishReport, S3Store,
};
use std::path::PathBuf;
use std::process::ExitCode;
#[derive(Debug, clap::Args)]
pub struct PublishArgs {
#[arg(default_value = "dist")]
dist: PathBuf,
#[arg(long)]
pin: Option<String>,
#[arg(long)]
in_memory: bool,
#[arg(long, default_value = "mesofact.config.toml")]
config: PathBuf,
#[arg(long)]
bucket: Option<String>,
#[arg(long)]
endpoint: Option<String>,
#[arg(long)]
zone: Option<String>,
}
pub async fn run(args: PublishArgs) -> ExitCode {
if args.in_memory {
return run_in_memory(args).await;
}
run_real(args).await
}
async fn run_in_memory(args: PublishArgs) -> ExitCode {
let store = InMemoryStore::new();
let purger = InMemoryPurger::new();
let result = dispatch(&args, &store, &purger).await;
report(result)
}
async fn run_real(args: PublishArgs) -> ExitCode {
let cfg = match PublishConfig::load(&args.config).await {
Ok(cfg) => cfg.with_overrides(args.bucket.clone(), args.endpoint.clone(), args.zone.clone()),
Err(ConfigError::NotFound(path)) => {
eprintln!(
"mesofact-publish: {path} not found. Pass --in-memory for a smoke run, \
or add a [publish] block to mesofact.config.toml."
);
return ExitCode::from(2);
}
Err(ConfigError::MissingPublish(path)) => {
eprintln!(
"mesofact-publish: no [publish] section in {path}. Declare bucket / endpoint / \
zone_id (and env-var names for credentials) to enable real-network publish."
);
return ExitCode::from(2);
}
Err(err) => {
eprintln!("mesofact-publish: config error: {err}");
return ExitCode::from(2);
}
};
let creds = match cfg.resolve_credentials() {
Ok(c) => c,
Err(err) => {
eprintln!("mesofact-publish: {err}");
return ExitCode::from(2);
}
};
let store = match S3Store::new(
&cfg.endpoint,
&cfg.bucket,
&cfg.region,
&creds.access_key_id,
&creds.secret_access_key,
) {
Ok(s) => s.with_base_prefix(cfg.prefix.clone().unwrap_or_default()),
Err(err) => {
eprintln!("mesofact-publish: S3 store init failed: {err}");
return ExitCode::from(1);
}
};
let purger = match CloudflareCdnPurger::new(&cfg.zone_id, &creds.cloudflare_api_token) {
Ok(p) => p,
Err(err) => {
eprintln!("mesofact-publish: Cloudflare purger init failed: {err}");
return ExitCode::from(1);
}
};
let result = dispatch(&args, &store, &purger).await;
report(result)
}
async fn dispatch(
args: &PublishArgs,
store: &dyn mesofact_publisher::ObjectStore,
purger: &dyn mesofact_publisher::CdnPurger,
) -> Result<PublishReport, mesofact_publisher::PublishError> {
if let Some(build_id) = args.pin.as_deref() {
publish_pin(build_id, store, purger).await
} else {
publish_dist(&args.dist, store, purger).await
}
}
fn report(result: Result<PublishReport, mesofact_publisher::PublishError>) -> ExitCode {
match result {
Ok(report) => {
println!("publish ok — build_id={}", report.build_id);
println!(" uploaded: {} key(s)", report.uploaded_keys.len());
println!(" skipped: {} key(s)", report.skipped_keys.len());
println!(" purged: {} tag(s)", report.purged_tags.len());
ExitCode::SUCCESS
}
Err(err) => {
eprintln!("mesofact-publish failed: {err}");
ExitCode::from(1)
}
}
}