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