l3_cli 0.0.4

Lambda compute tooling
use crate::collect::collect_handlers;
use crate::LLLCommandRunError::LambdasNotFound;
use crate::{LLLCommandRun, LLLCommandRunResult};
use clap::{Args, Parser};
use l3_fn_build::runtime::node::NodeConfig;
use l3_fn_build::runtime::Runtime;
use l3_fn_build::{BuildMode, FnBuildSpec, FnEntrypoint, FnHandler, FnOutputConfig, FnRouting};
use serde_json::{json, Value};
use std::sync::Arc;
use std::{env, fs};

#[derive(Parser, Debug)]
pub struct BuildCommand {
    #[clap(
        long,
        default_value = "false",
        long_help = "Create a release build of Lambda functions"
    )]
    release: bool,
    #[command(flatten)]
    target: BuildTarget,
}

#[derive(Args, Debug)]
#[group(required = true, multiple = false)]
pub struct BuildTarget {
    #[clap(long)]
    all: bool,
    #[clap(long)]
    r#fn: Option<String>,
}

impl LLLCommandRun for BuildCommand {
    async fn run(&self) -> LLLCommandRunResult {
        let build_mode = if self.release {
            BuildMode::Release
        } else {
            BuildMode::Debug
        };

        if self.target.all {
            build_all_fns(build_mode).await
        } else {
            match &self.target.r#fn {
                None => panic!(),
                Some(r#fn) => build_fn(r#fn, build_mode).await,
            }
        }
    }
}

async fn build_fn(r#_fn: &str, _mode: BuildMode) -> LLLCommandRunResult {
    todo!();
}

async fn build_all_fns(mode: BuildMode) -> LLLCommandRunResult {
    let project_dir = Arc::new(env::current_dir().unwrap());
    let entrypoints = collect_handlers(&project_dir).await;
    if entrypoints.is_empty() {
        return Err(LambdasNotFound);
    }
    let node_config = Arc::new(NodeConfig::read_node_config(&project_dir).unwrap());
    let mut build_json: Vec<Value> = Vec::new();
    let build_root = project_dir.join(".l3/build").join(match mode {
        BuildMode::Debug => "debug",
        BuildMode::Release => "release",
    });
    _ = fs::remove_dir_all(&build_root);
    for entrypoint in entrypoints {
        for handler in &entrypoint.handlers {
            l3_fn_build::build_fn(FnBuildSpec {
                project_dir: project_dir.clone(),
                runtime: Runtime::Node(node_config.clone()),
                entrypoint: entrypoint.path.clone(),
                mode: mode.clone(),
                handler_fn_name: handler.fn_name.clone(),
                output: FnOutputConfig {
                    build_root: build_root.clone(),
                    create_archive: true,
                    use_build_mode: false,
                },
            })
            .await
            .unwrap();
            build_json.push(build_json_value(&entrypoint, handler));
        }
    }
    fs::write(
        build_root.join("l3_build.json"),
        serde_json::to_string(&build_json).unwrap(),
    )
    .unwrap();
    let rel_build_dir = build_root.strip_prefix(project_dir.as_ref()).unwrap();
    println!(
        "\x1b[0;32;1m✔\x1b[0m Lambda builds are in {}",
        rel_build_dir.to_string_lossy()
    );
    Ok(())
}

fn build_json_value(entrypoint: &FnEntrypoint, handler: &FnHandler) -> Value {
    let mut fn_json = json!({
        "fn_identifier": handler.to_fn_identifier(),
        "entrypoint_path": entrypoint.path.clone(),
        "handler_fn_name": handler.fn_name.clone(),
        "http_route": null,
    });
    if let FnRouting::HttpRoute(http_route) = &handler.routing {
        fn_json.as_object_mut().unwrap().insert(
            String::from("http_route"),
            json!({
                "http_method": http_route.method,
                "http_path": http_route.path,
            }),
        );
    }
    fn_json
}