#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
pub mod apply;
mod collect;
pub mod data;
mod doctor;
pub mod lint;
mod lock;
pub mod sidecars;
use clap::{Parser, Subcommand};
use cratefield_core::Harness;
use std::path::PathBuf;
use std::process::ExitCode;
#[derive(Parser)]
#[command(name = "fz", about = "Factory Zero venture CLI")]
struct Cli {
#[arg(long, global = true, value_name = "JSON")]
sidecars: Option<String>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Migrations {
#[command(subcommand)]
command: MigrationsCommand,
},
Doctor {
#[arg(long, default_value = "migrations")]
out: PathBuf,
#[arg(long, value_name = "REASON")]
allow_no_captcha: Option<String>,
},
Modules,
Data {
#[command(subcommand)]
command: DataCommand,
},
}
#[derive(Subcommand)]
enum DataCommand {
Export {
#[arg(long, value_name = "PATH")]
db: PathBuf,
#[arg(long)]
without_sidecar_tables: bool,
#[arg(long, value_name = "FILE")]
out: PathBuf,
#[arg(long)]
plan: bool,
},
Import {
#[arg(long, value_name = "URL")]
url: String,
#[arg(value_name = "FILE")]
file: PathBuf,
#[arg(long)]
append: bool,
#[arg(long)]
plan: bool,
},
}
#[derive(Subcommand)]
enum MigrationsCommand {
Collect {
#[arg(long, default_value = "sqlite")]
dialect: String,
#[arg(long, default_value = "migrations")]
out: PathBuf,
},
Apply {
#[arg(long, default_value = "postgres")]
dialect: String,
#[arg(long, value_name = "URL")]
url: String,
},
}
#[must_use = "call process::exit with the returned ExitCode"]
pub fn run(build: impl Fn() -> Harness, args: impl IntoIterator<Item = String>) -> ExitCode {
let mut full_argv: Vec<String> = Vec::with_capacity(8);
full_argv.push("fz".to_string());
full_argv.extend(args);
let cli = Cli::parse_from(full_argv);
let harness = build();
let sidecars = crate::sidecars::from_cli_or_env(cli.sidecars.as_deref());
let result = match cli.command {
Command::Migrations {
command: MigrationsCommand::Collect { dialect, out },
} => collect::collect(&harness, &dialect, &out),
Command::Migrations {
command: MigrationsCommand::Apply { dialect, url },
} => apply::apply(&harness, &dialect, &url),
Command::Doctor {
out,
allow_no_captcha,
} => doctor::doctor(
&harness,
&out,
allow_no_captcha.as_deref(),
sidecars.as_deref(),
),
Command::Modules => {
print_modules(&harness);
Ok(())
}
Command::Data {
command:
DataCommand::Export {
db,
out,
plan,
without_sidecar_tables,
},
} => data::export(
&harness,
&db,
&out,
plan,
without_sidecar_tables,
sidecars.as_deref(),
),
Command::Data {
command:
DataCommand::Import {
url,
file,
append,
plan,
},
} => data::import(&harness, &file, &url, append, plan),
};
match result {
Ok(()) => ExitCode::SUCCESS,
Err(message) => {
eprintln!("fz: {message}");
ExitCode::FAILURE
}
}
}
pub fn main_for(build: fn() -> Harness) -> ! {
let code = run(build, std::env::args().skip(1));
std::process::exit(match code {
ExitCode::SUCCESS => 0,
_ => 1,
});
}
fn print_modules(harness: &Harness) {
for module in harness.modules() {
println!(
"{} {} /v1/{} emits=[{}] tables=[{}]",
module.name(),
module.version(),
module.name(),
module.emits().join(", "),
module.tables().join(", "),
);
}
}