1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3pub mod auth_catalog;
15pub mod backfill;
16#[cfg(feature = "catalog")]
17pub mod catalog;
18pub mod cli;
19pub mod commands;
20pub mod compose;
21pub mod config;
22pub mod conformance;
23pub mod dlq_replay;
24pub mod env_config;
25pub mod env_loader;
26pub mod error;
27pub mod executor;
28pub mod expand;
29pub mod init_template;
30pub mod interpolate;
31#[cfg(feature = "lineage")]
32pub mod lineage_glue;
33#[cfg(any(feature = "cli-tui", feature = "cli-progress"))]
36pub mod livemetrics;
37#[cfg(feature = "mcp")]
38pub mod mcp;
39pub mod merge;
40#[cfg(feature = "notify")]
41pub mod notify;
42pub mod obs;
43pub mod params;
44pub mod pipeline_test;
45#[cfg(feature = "cli-progress")]
46pub mod progress;
47pub mod registry;
48pub mod registry_index;
49pub mod replication;
50pub mod scaffold;
51#[cfg(feature = "schedule")]
52pub mod schedule;
53pub mod schema_compose;
54pub mod secrets;
55pub mod select;
56#[cfg(feature = "serve")]
57pub mod serve;
58pub mod sla;
59pub mod state;
60#[cfg(feature = "templates")]
61pub mod templates;
62pub mod topology;
63pub mod transforms;
64#[cfg(feature = "cli-tui")]
65pub mod tui;
66
67pub use error::{CliError, CliResult};
68
69use crate::cli::{Cli, Command};
70use crate::registry::PluginRegistry;
71
72pub fn run_main(registry: PluginRegistry) -> std::process::ExitCode {
93 use clap::Parser;
94 use std::process::ExitCode;
95
96 if let Err(err) = registry.install() {
97 commands::report(&err);
98 return ExitCode::from(1);
99 }
100
101 faucet_core::redact::install(Box::new(|s: &str| {
107 secrets::registry::redact(s).into_owned()
108 }));
109
110 clap_complete::env::CompleteEnv::with_factory(<Cli as clap::CommandFactory>::command)
115 .complete();
116
117 let cli = Cli::parse();
118 #[cfg(feature = "serve")]
119 let is_serve = matches!(cli.command, Command::Serve(_));
120 #[cfg(not(feature = "serve"))]
121 let is_serve = false;
122 #[cfg(feature = "cli-tui")]
125 let is_tui = matches!(&cli.command, Command::Run(a) if tui::is_tui_session(a.tui));
126 #[cfg(not(feature = "cli-tui"))]
127 let is_tui = false;
128 #[cfg(feature = "mcp")]
131 let is_mcp = matches!(cli.command, Command::Mcp(_));
132 #[cfg(not(feature = "mcp"))]
133 let is_mcp = false;
134 if !is_serve && !is_tui && !is_mcp {
137 install_tracing(&cli.log_level);
138 }
139 #[cfg(feature = "cli-tui")]
140 if is_tui {
141 tui::install_tui_tracing(&cli.log_level);
142 }
143 #[cfg(feature = "mcp")]
144 if is_mcp {
145 mcp::install_stderr_tracing(&cli.log_level);
146 }
147
148 let runtime = match tokio::runtime::Builder::new_multi_thread()
149 .enable_all()
150 .build()
151 {
152 Ok(rt) => rt,
153 Err(e) => {
154 eprintln!("error: failed to start async runtime: {e}");
155 return ExitCode::from(1);
156 }
157 };
158
159 runtime.block_on(async move {
160 match run_command(cli).await {
161 Ok(()) => ExitCode::SUCCESS,
162 Err(CliError::DoctorFailed { failed }) => ExitCode::from(failed.min(255) as u8),
165 Err(CliError::TestsFailed { failed }) => ExitCode::from(failed.min(255) as u8),
166 Err(CliError::BackfillFailed { failed }) => ExitCode::from(failed.min(255) as u8),
167 Err(err) => {
168 commands::report(&err);
169 ExitCode::from(1)
170 }
171 }
172 })
173}
174
175pub async fn run_command(cli: Cli) -> CliResult<()> {
180 #[cfg(feature = "serve")]
181 let serve_log_level = cli.log_level.clone();
182 match cli.command {
183 Command::Run(args) => commands::run::run(args).await,
184 Command::Backfill(args) => commands::backfill::run(args).await,
185 Command::Replicate(args) => commands::replicate::run(args).await,
186 Command::Discover(args) => commands::discover::run(args).await,
187 Command::Validate(args) => commands::validate::run(args).await,
188 Command::Schema(args) => commands::schema::run(args).await,
189 Command::List(args) => commands::list::run(args).await,
190 Command::Search(args) => commands::search::run(args).await,
191 Command::Conformance(args) => commands::conformance::run(args).await,
192 Command::Install(args) => commands::install::run(args).await,
193 Command::Preview(args) => commands::preview::run(args).await,
194 Command::Plan(args) => commands::plan::run(args).await,
195 #[cfg(feature = "cli-dev")]
196 Command::Dev(args) => commands::dev::run(args).await,
197 Command::Init(args) => commands::init::run(args).await,
198 Command::New(args) => commands::new::run(args).await,
199 Command::Doctor(args) => commands::doctor::run(args).await,
200 Command::Test(args) => commands::test::run(args).await,
201 Command::Dlq(args) => commands::dlq::run(args).await,
202 #[cfg(feature = "contract")]
203 Command::Contract(args) => commands::contract::run(args).await,
204 #[cfg(feature = "masking")]
205 Command::Masking(args) => commands::masking::run(args).await,
206 #[cfg(feature = "schedule")]
207 Command::Schedule(args) => commands::schedule::run(args).await,
208 #[cfg(feature = "serve")]
209 Command::Serve(args) => commands::serve::run(args, serve_log_level).await,
210 #[cfg(feature = "mcp")]
211 Command::Mcp(args) => commands::mcp::run(args).await,
212 #[cfg(feature = "notify")]
213 Command::Notify(args) => commands::notify::run(args).await,
214 #[cfg(feature = "catalog")]
215 Command::Catalog(args) => commands::catalog::run(args).await,
216 #[cfg(feature = "templates")]
217 Command::Template(args) => commands::template::run(args).await,
218 Command::Completions(args) => commands::completions::run(args.shell),
219 Command::Migrate(args) => commands::migrate::run(args).await,
220 Command::Fmt(args) => commands::fmt::run(args).await,
221 Command::Explain(args) => commands::explain::run(args).await,
222 #[cfg(feature = "catalog")]
223 Command::History(args) => commands::history::run(args).await,
224 }
225}
226
227#[cfg(feature = "observability")]
228fn install_tracing(level: &str) {
229 use crate::secrets::registry::RedactingMakeWriter;
230 use tracing_subscriber::EnvFilter;
231 let filter = EnvFilter::try_new(level).unwrap_or_else(|_| EnvFilter::new("info"));
232 let _ = tracing_subscriber::fmt()
233 .with_env_filter(filter)
234 .with_writer(RedactingMakeWriter)
235 .try_init();
236}
237
238#[cfg(not(feature = "observability"))]
241fn install_tracing(_level: &str) {}
242
243pub async fn run_from_yaml_str(yaml: &str) -> CliResult<executor::RunSummary> {
250 let mut value: serde_json::Value =
254 serde_yaml::from_str(yaml).map_err(|e| CliError::ParseConfig {
255 path: std::path::PathBuf::from("<yaml-string>"),
256 message: e.to_string(),
257 })?;
258 interpolate::interpolate_value(&mut value)?;
259 params::bind_document(&mut value, &Default::default(), params::BindMode::Strict)?;
263 let interpolated = serde_yaml::to_string(&value).map_err(|e| CliError::ParseConfig {
264 path: std::path::PathBuf::from("<yaml-string>"),
265 message: e.to_string(),
266 })?;
267 let mut cfg: config::PipelineConfig =
268 serde_yaml::from_str(&interpolated).map_err(|e| CliError::ParseConfig {
269 path: std::path::PathBuf::from("<yaml-string>"),
270 message: e.to_string(),
271 })?;
272 if cfg.version != 1 {
273 return Err(CliError::ParseConfig {
274 path: std::path::PathBuf::from("<yaml-string>"),
275 message: format!(
276 "unsupported pipeline version {}, only version 1 is recognised",
277 cfg.version
278 ),
279 });
280 }
281 crate::secrets::resolve_secrets(&mut cfg).await?;
282 let pipeline_name = cfg.name.clone().unwrap_or_else(|| "unnamed".to_string());
283 let auth = auth_catalog::build_auth_catalog(cfg.auth.as_ref())?;
284 let resilience = match &cfg.resilience {
285 Some(spec) => Some(spec.to_policy()?),
286 None => None,
287 };
288 #[cfg(feature = "catalog")]
289 let catalog = match cfg.catalog.as_ref() {
290 Some(spec) => Some(catalog::connect_from_spec(spec).await?),
291 None => None,
292 };
293 let nodes = expand::expand(&cfg)?;
294 executor::run_expanded(
295 nodes,
296 executor::ExecuteOptions {
297 pipeline_name,
298 execution: cfg.execution.clone(),
299 dry_run: false,
300 limit: None,
301 state_path_override: None,
302 shard: None,
303 auth,
304 clock: chrono::Utc::now().fixed_offset(),
305 cancel: None,
306 resilience,
307 sla: cfg.sla.clone(),
308 #[cfg(feature = "lineage")]
309 lineage: None,
310 #[cfg(feature = "lineage")]
311 lineage_cfg: None,
312 #[cfg(feature = "notify")]
313 notifier: None,
314 #[cfg(feature = "catalog")]
315 catalog,
316 },
317 )
318 .await
319}