mod authoring;
mod module;
use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Debug, Parser)]
#[command(
name = "lenso",
version,
about = "Build, diagnose, and verify Lenso Modules and App Definitions",
propagate_version = true
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
New(ModuleCreateArgs),
Dev(ModuleDevArgs),
Check(ModuleCheckArgs),
Verify(ModuleVerifyArgs),
App {
#[command(subcommand)]
command: authoring::AppCommand,
},
}
#[derive(Debug, Args, Clone)]
struct ModuleCreateArgs {
module_id: String,
#[arg(long)]
repo_root: Option<std::path::PathBuf>,
#[arg(long, value_enum, default_value_t = ModuleRuntimeArg::Rust)]
runtime: ModuleRuntimeArg,
#[arg(long, value_enum, default_value_t = ModuleRecipeArg::Stateless)]
recipe: ModuleRecipeArg,
#[arg(long)]
dir: Option<std::path::PathBuf>,
#[arg(long)]
no_install: bool,
#[arg(long)]
capability: Option<String>,
#[arg(long)]
dry_run: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ModuleRuntimeArg {
Rust,
Bun,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum)]
enum ModuleRecipeArg {
Stateless,
Stateful,
WebConsole,
ManagedWork,
}
#[derive(Debug, Args, Clone)]
struct ModuleDevArgs {
#[arg(long)]
repo_root: Option<std::path::PathBuf>,
#[arg(long, default_value = "lenso.json")]
project: std::path::PathBuf,
#[arg(long = "bun-bin", default_value = "bun")]
bun_bin: String,
}
#[derive(Debug, Args, Clone)]
struct ModuleCheckArgs {
#[arg(long)]
repo_root: Option<std::path::PathBuf>,
#[arg(long, default_value = "lenso.json")]
project: std::path::PathBuf,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args, Clone)]
struct ModuleVerifyArgs {
#[arg(long)]
repo_root: Option<std::path::PathBuf>,
#[arg(long, default_value = "lenso.json")]
project: std::path::PathBuf,
#[arg(long = "module")]
module_key: Option<String>,
#[arg(long, default_value = "lenso.module.verify.json")]
manifest: std::path::PathBuf,
#[arg(long, default_value = ".lenso/module-verification.json")]
output: std::path::PathBuf,
#[arg(long)]
json: bool,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
match cli.command {
Command::New(args) => create_module(args)?,
Command::Dev(args) => dev_module(args).await?,
Command::Check(args) => check_module(args)?,
Command::Verify(args) => verify_module(args)?,
Command::App { command } => authoring::app(command)?,
}
Ok(())
}
fn create_module(args: ModuleCreateArgs) -> anyhow::Result<()> {
module::create_module(&module::ModuleCreateOptions {
capability: args.capability,
dir: args.dir,
dry_run: args.dry_run,
module_id: args.module_id,
no_install: args.no_install,
repo_root: args.repo_root,
recipe: match args.recipe {
ModuleRecipeArg::Stateless => module::ModuleRecipe::Stateless,
ModuleRecipeArg::Stateful => module::ModuleRecipe::Stateful,
ModuleRecipeArg::WebConsole => module::ModuleRecipe::WebConsole,
ModuleRecipeArg::ManagedWork => module::ModuleRecipe::ManagedWork,
},
runtime: match args.runtime {
ModuleRuntimeArg::Rust => module::ModuleRuntime::Rust,
ModuleRuntimeArg::Bun => module::ModuleRuntime::Bun,
},
})
}
async fn dev_module(args: ModuleDevArgs) -> anyhow::Result<()> {
authoring::dev_module(args.repo_root.as_deref(), &args.project, &args.bun_bin).await
}
fn check_module(args: ModuleCheckArgs) -> anyhow::Result<()> {
authoring::check_module(&authoring::ModuleCheckOptions {
json: args.json,
project: args.project,
repo_root: args.repo_root,
})
}
fn verify_module(args: ModuleVerifyArgs) -> anyhow::Result<()> {
authoring::verify_module(authoring::ModuleVerifyOptions {
json: args.json,
manifest: args.manifest,
module_key: args.module_key,
output: args.output,
project: args.project,
repo_root: args.repo_root,
})
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn command_tree_is_valid() {
Cli::command().debug_assert();
}
#[test]
fn only_intent_level_commands_are_public() {
let command = Cli::command();
let names = command
.get_subcommands()
.map(clap::Command::get_name)
.collect::<Vec<_>>();
assert_eq!(names, ["new", "dev", "check", "verify", "app"]);
let app = command
.get_subcommands()
.find(|subcommand| subcommand.get_name() == "app")
.expect("app command");
let app_names = app
.get_subcommands()
.map(clap::Command::get_name)
.collect::<Vec<_>>();
assert_eq!(app_names, ["add", "remove", "check", "resolve"]);
}
}