77 lines
2.7 KiB
Rust
77 lines
2.7 KiB
Rust
//! Headless schema-check binary - loads content (from a Gitea repo URL
|
|||
|
|
//! or a local directory) and validates it exactly the way
|
||
|
|
//! `content::watch_for_reload`/`main.rs`'s boot path do, with no NATS,
|
||
|
|
//! OIDC, web server, or JetStream connection involved. Built once by
|
||
|
|
//! portal's own deploy workflow and downloaded as a release artifact by
|
||
|
|
//! `questions`' own CI, rather than compiled there - keeps that repo's
|
||
|
|
//! CI coupling to "run a static binary," not "build a Rust workspace."
|
||
|
|
#![cfg(feature = "ssr")]
|
||
|
|
|
||
|
|
use portal::content;
|
||
|
|
|
||
|
|
#[tokio::main]
|
||
|
|
async fn main() -> anyhow::Result<()> {
|
||
|
|
let mut args = std::env::args().skip(1);
|
||
|
|
let mut repo: Option<String> = None;
|
||
|
|
let mut branch = "main".to_string();
|
||
|
|
let mut subdir = "questions".to_string();
|
||
|
|
let mut path: Option<String> = None;
|
||
|
|
|
||
|
|
while let Some(arg) = args.next() {
|
||
|
|
match arg.as_str() {
|
||
|
|
"--repo" => repo = args.next(),
|
||
|
|
"--branch" => branch = args.next().unwrap_or(branch),
|
||
|
|
"--subdir" => subdir = args.next().unwrap_or(subdir),
|
||
|
|
"--path" => path = args.next(),
|
||
|
|
other => {
|
||
|
|
eprintln!("unknown argument: {other}");
|
||
|
|
std::process::exit(2);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let questions = if let Some(dir) = path {
|
||
|
|
load_from_dir(&dir)?
|
||
|
|
} else if let Some(repo_url) = repo {
|
||
|
|
content::load_questions_from_gitea(&repo_url, &branch, &subdir).await?
|
||
|
|
} else {
|
||
|
|
eprintln!(
|
||
|
|
"usage: question-lint --repo <gitea-url> [--branch main] [--subdir questions] | --path <local-dir>"
|
||
|
|
);
|
||
|
|
std::process::exit(2);
|
||
|
|
};
|
||
|
|
|
||
|
|
match content::validate_questions(&questions) {
|
||
|
|
Ok(()) => {
|
||
|
|
println!("OK: {} question(s) valid", questions.len());
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
Err(e) => {
|
||
|
|
eprintln!("FAIL: {e}");
|
||
|
|
std::process::exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// The offline counterpart to `content::load_questions_from_gitea` -
|
||
|
|
/// same "every `*.yaml` file becomes a `Question` keyed by its own
|
||
|
|
/// `id`" shape, just reading a local checkout instead of Gitea's API,
|
||
|
|
/// for linting a branch that hasn't been pushed yet.
|
||
|
|
fn load_from_dir(
|
||
|
|
dir: &str,
|
||
|
|
) -> anyhow::Result<std::collections::HashMap<String, content::Question>> {
|
||
|
|
let mut out = std::collections::HashMap::new();
|
||
|
|
for entry in std::fs::read_dir(dir)? {
|
||
|
|
let entry = entry?;
|
||
|
|
let path = entry.path();
|
||
|
|
if path.extension().and_then(|e| e.to_str()) != Some("yaml") {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
let raw = std::fs::read_to_string(&path)?;
|
||
|
|
let question: content::Question = serde_yaml::from_str(&raw)
|
||
|
|
.map_err(|e| anyhow::anyhow!("parsing {}: {e}", path.display()))?;
|
||
|
|
out.insert(question.id.clone(), question);
|
||
|
|
}
|
||
|
|
Ok(out)
|
||
|
|
}
|