harmont_cli/cli/pipelines.rs
1use std::path::PathBuf;
2
3use anyhow::{Context, Result};
4use clap::Parser;
5use hm_dsl_engine::{detect, engine_for};
6
7#[derive(Debug, Clone, Parser)]
8pub struct PipelinesArgs {
9 /// Source root containing `.hm/` (defaults to cwd).
10 #[arg(short, long)]
11 pub dir: Option<PathBuf>,
12}
13
14/// Empty discovery envelope, emitted when a repo declares no pipelines. Mirrors
15/// the shape of `harmont.dump_registry_json()` so backend discovery parses it
16/// the same way (it reads only the `pipelines` array).
17const EMPTY_ENVELOPE: &str = r#"{"schema_version":"1","pipelines":[]}"#;
18
19/// Print the discovery envelope JSON (all pipelines) to stdout.
20///
21/// A repo with no `.hm/` directory (or one with no `.py`/`.ts` files)
22/// declares no pipelines and yields the empty envelope rather than an error —
23/// the backend fans discovery out across every repo in an installation, most of
24/// which carry no pipelines. Both Python and TypeScript pipelines emit the same
25/// discovery envelope; a repo declaring both languages resolves to Python via
26/// [`detect::detect_language_python_first`] (the fully-supported backend path),
27/// matching `hm render`.
28///
29/// # Errors
30///
31/// Returns an error if the engine can't start or the DSL runtime fails to
32/// evaluate the pipelines.
33pub async fn run(args: PipelinesArgs) -> Result<()> {
34 let repo_root = match args.dir {
35 Some(d) => d,
36 None => std::env::current_dir().context("cannot determine current directory")?,
37 };
38
39 if !detect::has_pipeline_files(&repo_root) {
40 print!("{EMPTY_ENVELOPE}");
41 return Ok(());
42 }
43
44 let lang =
45 detect::detect_language_python_first(&repo_root).context("detecting pipeline language")?;
46 let engine = engine_for(lang).context("initializing DSL engine")?;
47 let json = engine
48 .registry_json(&repo_root)
49 .await
50 .context("dumping pipeline registry")?;
51
52 // Machine-facing: raw envelope JSON on stdout, nothing else.
53 print!("{json}");
54 Ok(())
55}