fn0-cli 0.1.2

CLI for fn0 cloud
use crate::{
    config::{Framework, ProjectConfig},
    generators::templates::{hono, package_json, rolldown_config, wit_component, wit_deps},
    utils::command::CommandExecutor,
};
use color_eyre::Result;
use std::path::Path;
use tokio::fs;

pub struct TypeScriptGenerator;

impl TypeScriptGenerator {
    pub async fn generate(project_path: &Path, config: &ProjectConfig) -> Result<()> {
        println!("📦 Creating project directory...");
        fs::create_dir_all(project_path).await?;

        println!("📄 Creating package.json...");
        let package_json_content = package_json::generate(config);
        let package_json_path = project_path.join("package.json");
        fs::write(package_json_path, package_json_content).await?;

        println!("📄 Creating tsconfig.json...");
        let tsconfig_content = Self::generate_tsconfig();
        let tsconfig_path = project_path.join("tsconfig.json");
        fs::write(tsconfig_path, tsconfig_content).await?;

        println!("📄 Creating rolldown.config.mjs...");
        let rolldown_content = rolldown_config::generate();
        let rolldown_path = project_path.join("rolldown.config.mjs");
        fs::write(rolldown_path, rolldown_content).await?;

        println!("📄 Creating .gitignore...");
        let gitignore_content = Self::generate_gitignore();
        let gitignore_path = project_path.join(".gitignore");
        fs::write(gitignore_path, gitignore_content).await?;

        println!("📄 Creating WIT interface files...");
        let wit_path = project_path.join("wit");
        fs::create_dir_all(&wit_path).await?;

        let wit_component_content = wit_component::generate();
        let wit_component_path = wit_path.join("component.wit");
        fs::write(wit_component_path, wit_component_content).await?;

        let wit_deps_path = wit_path.join("deps");
        fs::create_dir_all(&wit_deps_path).await?;

        let deps = wit_deps::WitDeps::get_all_deps();
        for (rel_path, content) in deps {
            let parts: Vec<&str> = rel_path.split('/').collect();
            let dir_name = parts[0];
            let file_name = parts[1];

            let dep_dir = wit_deps_path.join(dir_name);
            fs::create_dir_all(&dep_dir).await?;

            let dep_file = dep_dir.join(file_name);
            fs::write(dep_file, content).await?;
        }

        let src_path = project_path.join("src");
        fs::create_dir_all(&src_path).await?;

        println!("📄 Creating server files...");
        match config.framework {
            Framework::Hono => {
                let index_content = hono::generate_index();
                let index_path = src_path.join("index.ts");
                fs::write(index_path, index_content).await?;

                let component_content = hono::generate_component();
                let component_path = src_path.join("component.ts");
                fs::write(component_path, component_content).await?;
            }
        }

        println!("📥 Installing dependencies...");

        let pm_cmd = config.package_manager.command();

        println!("  Installing {}...", config.framework);
        let mut install_args = config.package_manager.install_args();
        install_args.push(config.framework.package_name());

        CommandExecutor::run(pm_cmd, &install_args, project_path).await?;

        println!("  Installing development dependencies...");
        let mut install_dev_args = config.package_manager.install_dev_args();
        install_dev_args.push("typescript");
        install_dev_args.push("@types/node");

        for pkg in config.framework.additional_packages() {
            install_dev_args.push(pkg);
        }

        CommandExecutor::run(pm_cmd, &install_dev_args, project_path).await?;

        Ok(())
    }

    fn generate_tsconfig() -> String {
        r#"{
  "compilerOptions": {
    "target": "ES2022",
    "module": "nodenext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
"#
        .to_string()
    }

    fn generate_gitignore() -> String {
        r#"node_modules/
dist/
.env
.DS_Store
*.log
*.wasm
"#
        .to_string()
    }
}