use std::path::PathBuf;
use std::time::Instant;
use anyhow::Result;
use clap::Parser;
use colored::Colorize;
use gaji::builder::WorkflowBuilder;
use gaji::cache::Cache;
use gaji::cli::{Cli, Commands};
use gaji::config::Config;
use gaji::generator::TypeGenerator;
use gaji::init::{self, InitOptions};
use gaji::parser;
use gaji::watcher;
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Init {
force,
skip_examples,
migrate,
interactive,
} => {
cmd_init(force, skip_examples, migrate, interactive).await?;
}
Commands::Dev { dir, watch } => {
cmd_dev(&dir, watch).await?;
}
Commands::Build {
input,
output,
dry_run,
} => {
cmd_build(&input, &output, dry_run).await?;
}
Commands::Add { action } => {
cmd_add(&action).await?;
}
Commands::Clean { cache } => {
cmd_clean(cache).await?;
}
}
Ok(())
}
async fn cmd_init(
force: bool,
skip_examples: bool,
migrate: bool,
interactive: bool,
) -> Result<()> {
let root = std::env::current_dir()?;
let options = InitOptions {
force,
skip_examples,
migrate,
interactive,
};
init::init_project(&root, options).await
}
async fn cmd_dev(dir: &str, watch: bool) -> Result<()> {
println!("{} Starting development mode...\n", "๐".green());
let config = Config::load()?;
let token = config.resolve_token();
let api_url = config.resolve_api_url();
let path = PathBuf::from(dir);
if path.exists() {
let results = parser::analyze_directory(&path).await?;
let mut all_refs = std::collections::HashSet::new();
for refs in results.values() {
all_refs.extend(refs.clone());
}
if !all_refs.is_empty() {
println!(
"{} Found {} action reference(s), generating types...",
"๐".cyan(),
all_refs.len()
);
let gen_start = Instant::now();
let cache = Cache::load_or_create()?;
let generator = TypeGenerator::with_cache_ttl(
cache,
PathBuf::from("generated"),
token,
api_url,
config.build.cache_ttl_days,
);
generator.generate_types_for_refs(&all_refs).await?;
println!(
"{} Types generated in {:.2}s!\n",
"โจ".green(),
gen_start.elapsed().as_secs_f64()
);
}
}
if watch {
watcher::watch_directory(&path).await?;
} else {
println!("{} Done. Run with --watch to keep watching.", "โ".green());
}
Ok(())
}
async fn cmd_build(input: &str, output: &str, dry_run: bool) -> Result<()> {
let start = Instant::now();
if dry_run {
println!("{} Dry run: previewing workflows...\n", "๐จ".cyan());
} else {
println!("{} Building workflows...\n", "๐จ".cyan());
}
let builder = WorkflowBuilder::new(PathBuf::from(input), PathBuf::from(output), dry_run);
let built = builder.build_all().await?;
let elapsed = start.elapsed();
if built.is_empty() {
println!("{} No workflows built", "โ ๏ธ".yellow());
} else {
println!(
"\n{} Built {} workflow(s) in {:.2}s",
"โ
".green(),
built.len(),
elapsed.as_secs_f64()
);
}
Ok(())
}
async fn cmd_add(action: &str) -> Result<()> {
let start = Instant::now();
println!("{} Adding action: {}\n", "๐ฆ".cyan(), action);
let config = Config::load()?;
let token = config.resolve_token();
let api_url = config.resolve_api_url();
let cache = Cache::load_or_create()?;
let generator = TypeGenerator::with_cache_ttl(
cache,
PathBuf::from("generated"),
token,
api_url,
config.build.cache_ttl_days,
);
let mut refs = std::collections::HashSet::new();
refs.insert(action.to_string());
match generator.generate_types_for_refs(&refs).await {
Ok(files) => {
for file in files {
println!("{} Generated {}", "โ
".green(), file.display());
}
println!(
"\n{} Done in {:.2}s",
"โจ".green(),
start.elapsed().as_secs_f64()
);
}
Err(e) => {
eprintln!("{} Failed to generate types: {}", "โ".red(), e);
return Err(e);
}
}
Ok(())
}
async fn cmd_clean(clean_cache: bool) -> Result<()> {
println!("{} Cleaning generated files...\n", "๐งน".cyan());
if PathBuf::from("generated").exists() {
tokio::fs::remove_dir_all("generated").await?;
println!("{} Removed generated/", "โ".green());
}
if clean_cache {
let cache = Cache::load_or_create()?;
cache.clear()?;
println!("{} Cleared cache", "โ".green());
}
println!("\n{} Clean complete!", "โจ".green());
Ok(())
}