use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use bot_forge::config::ConfigDocument;
use bot_forge::constants::BUILTIN_CATALOG;
use bot_forge::state::RegistryDocument;
use sha2::{Digest, Sha256};
fn main() {
let arguments = env::args().skip(1).collect::<Vec<_>>();
let check = match parse_arguments(&arguments) {
Ok(check) => check,
Err(message) => {
eprintln!("{message}");
std::process::exit(2);
}
};
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
ConfigDocument::parse(&format!("catalog = \"rust-dev\"\n{BUILTIN_CATALOG}"))
.expect("builtin catalog must be valid");
let mut schema = serde_json::to_value(schemars::schema_for!(ConfigDocument)).unwrap();
let schema_object = schema
.as_object_mut()
.expect("config schema must be an object");
schema_object.insert("title".to_string(), "bot-forge configuration".into());
schema_object.insert("required".to_string(), serde_json::json!(["catalog"]));
schema_object["properties"]["catalog"] = serde_json::json!({
"const": "rust-dev",
"type": "string"
});
let schema = serde_json::to_string_pretty(&schema).unwrap() + "\n";
let mut registry_schema =
serde_json::to_value(schemars::schema_for!(RegistryDocument)).unwrap();
let registry_object = registry_schema
.as_object_mut()
.expect("registry schema must be an object");
registry_object.insert(
"$id".to_string(),
"https://bot-forge.local/schema/registry.json".into(),
);
registry_object.insert("title".to_string(), "bot-forge registry".into());
let registry_schema = serde_json::to_string_pretty(®istry_schema).unwrap() + "\n";
let catalog_digest = format!(
"{} catalogs/rust-dev.toml\n",
Sha256::digest(BUILTIN_CATALOG.as_bytes())
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<String>()
);
let artifacts = [
(root.join("schema/config.json"), schema),
(root.join("schema/registry.json"), registry_schema),
(root.join("catalogs/SHA256SUMS"), catalog_digest),
];
let mut drift = Vec::new();
for (path, contents) in artifacts {
if check {
if fs::read_to_string(&path).ok().as_deref() != Some(&contents) {
drift.push(path);
}
} else {
write(&path, &contents);
}
}
if !drift.is_empty() {
eprintln!("generated config artifacts are stale:");
for path in drift {
eprintln!(" {}", path.display());
}
std::process::exit(1);
}
}
fn parse_arguments(arguments: &[String]) -> Result<bool, &'static str> {
match arguments {
[] => Ok(false),
[argument] if argument == "--check" => Ok(true),
_ => Err("usage: generate-config-artifacts [--check]"),
}
}
fn write(path: &Path, contents: &str) {
fs::write(path, contents).unwrap_or_else(|error| panic!("write {}: {error}", path.display()));
}
#[cfg(test)]
mod tests {
use crate::parse_arguments;
#[test]
fn arguments_are_strict() {
assert_eq!(parse_arguments(&[]), Ok(false));
assert_eq!(parse_arguments(&["--check".into()]), Ok(true));
assert!(parse_arguments(&["unknown".into()]).is_err());
assert!(parse_arguments(&["--check".into(), "extra".into()]).is_err());
}
}