use anyhow::Result;
use std::fs;
use std::path::Path;
pub fn add_page(path: &str) -> Result<()> {
let project_dir = Path::new(".");
if !project_dir.join("Forte.toml").exists() {
anyhow::bail!("Not a Forte project (Forte.toml not found)");
}
let page_path = normalize_page_path(path);
let rs_page_dir = project_dir.join(format!("rs/src/pages/{}", page_path));
let fe_page_dir = project_dir.join(format!("fe/src/pages/{}", page_path));
if rs_page_dir.exists() || fe_page_dir.exists() {
anyhow::bail!("Page '{}' already exists", path);
}
fs::create_dir_all(&rs_page_dir)?;
fs::create_dir_all(&fe_page_dir)?;
let component_name = path_to_component_name(&page_path);
fs::write(
rs_page_dir.join("mod.rs"),
generate_backend_page(&page_path),
)?;
fs::write(
fe_page_dir.join("page.tsx"),
generate_frontend_page(&component_name),
)?;
println!("Created page '{}'", path);
println!(" - rs/src/pages/{}/mod.rs", page_path);
println!(" - fe/src/pages/{}/page.tsx", page_path);
Ok(())
}
fn normalize_page_path(path: &str) -> String {
path.trim_matches('/').to_string()
}
fn path_to_component_name(path: &str) -> String {
let parts: Vec<&str> = path.split('/').collect();
let last_part = parts.last().unwrap_or(&"page");
let name = if last_part.starts_with('[') && last_part.ends_with(']') {
&last_part[1..last_part.len() - 1]
} else {
last_part
};
let mut result = String::new();
let mut capitalize_next = true;
for c in name.chars() {
if c == '_' || c == '-' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
format!("{}Page", result)
}
fn generate_backend_page(page_path: &str) -> String {
let params = extract_params(page_path);
if params.is_empty() {
r#"use anyhow::Result;
use forte_sdk::ForteRequest;
use serde::Serialize;
#[derive(Serialize)]
pub enum Props {
Ok { message: String },
}
pub async fn handler(_req: ForteRequest<'_>) -> Result<Props> {
Ok(Props::Ok {
message: "Hello from Forte!".to_string(),
})
}
"#
.to_string()
} else {
let param_fields = params
.iter()
.map(|p| format!(" pub {}: String,", p))
.collect::<Vec<_>>()
.join("\n");
format!(
r#"use anyhow::Result;
use forte_sdk::ForteRequest;
use serde::{{Deserialize, Serialize}};
#[derive(Deserialize)]
pub struct PathParams {{
{param_fields}
}}
#[derive(Serialize)]
pub enum Props {{
Ok {{ message: String }},
}}
pub async fn handler(_req: ForteRequest<'_>, _path_params: PathParams) -> Result<Props> {{
Ok(Props::Ok {{
message: "Hello from Forte!".to_string(),
}})
}}
"#
)
}
}
fn generate_frontend_page(component_name: &str) -> String {
format!(
r#"import type {{ Props }} from "./.props";
export default function {component_name}(props: Props) {{
if (props.t !== "Ok") {{
return <div>Error loading page</div>;
}}
return (
<div>
<h1>{component_name}</h1>
<p>{{props.message}}</p>
</div>
);
}}
"#
)
}
fn extract_params(path: &str) -> Vec<String> {
path.split('/')
.filter(|s| s.starts_with('[') && s.ends_with(']'))
.map(|s| s[1..s.len() - 1].to_string())
.collect()
}
pub fn add_action(path: &str) -> Result<()> {
let project_dir = Path::new(".");
if !project_dir.join("Forte.toml").exists() {
anyhow::bail!("Not a Forte project (Forte.toml not found)");
}
let action_path = normalize_action_path(path);
let rs_action_file = project_dir.join(format!("rs/src/actions/{}.rs", action_path));
if rs_action_file.exists() {
anyhow::bail!("Action '{}' already exists", path);
}
if let Some(parent) = rs_action_file.parent() {
fs::create_dir_all(parent)?;
}
let action_name = path_to_action_name(&action_path);
fs::write(&rs_action_file, generate_backend_action(&action_name))?;
println!("Created action '{}'", path);
println!(" - rs/src/actions/{}.rs", action_path);
println!(
" - import from fe/src/actions/.generated/{}.ts (auto-generated on next forte build/dev)",
action_path
);
Ok(())
}
fn normalize_action_path(path: &str) -> String {
path.trim_matches('/').replace('-', "_")
}
fn path_to_action_name(path: &str) -> String {
let parts: Vec<&str> = path.split('/').collect();
let last_part = parts.last().unwrap_or(&"action");
let mut result = String::new();
let mut capitalize_next = true;
for c in last_part.chars() {
if c == '_' || c == '-' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_ascii_uppercase());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
fn generate_backend_action(_action_name: &str) -> String {
r#"use forte_sdk::ForteRequest;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct Input {
pub message: String,
}
#[derive(Serialize)]
pub enum Output {
Ok { result: String },
Error { message: String },
}
pub async fn handler(req: ForteRequest<'_, Input>) -> Output {
Output::Ok {
result: format!("Received: {}", req.body.message),
}
}
"#
.to_string()
}