forklaunch 1.15.0

Launch faster with forklaunch
use std::{collections::HashSet, fs::read_to_string, path::Path};

use anyhow::{Context, Result, bail};
use serde_json::{from_str, to_string_pretty};

use super::{
    manifest::ManifestData,
    package_json::{
        package_json_constants::{
            BETTER_SQLITE_POSTINSTALL_SCRIPT, MIKRO_ORM_DATABASE_VERSION, SQLITE_POSTINSTALL_SCRIPT,
        },
        project_package_json::ProjectPackageJson,
    },
    rendered_template::{RenderedTemplate, TEMPLATES_DIR},
};
use crate::constants::{
    Database, ERROR_FAILED_TO_CREATE_DATABASE_EXPORT_INDEX_TS, ERROR_UNSUPPORTED_DATABASE, Runtime,
};

pub(crate) fn get_db_driver(database: &Database) -> String {
    match database {
        Database::MongoDB => "MongoDriver".to_string(),
        Database::PostgreSQL => "PostgreSqlDriver".to_string(),
        Database::SQLite => "SqliteDriver".to_string(),
        Database::MySQL => "MySqlDriver".to_string(),
        Database::MariaDB => "MariaDbDriver".to_string(),
        Database::BetterSQLite => "BetterSqliteDriver".to_string(),
        Database::LibSQL => "LibSqlDriver".to_string(),
        Database::MsSQL => "MsSqlDriver".to_string(),
    }
}

pub(crate) fn get_database_port(database: &Database) -> Option<String> {
    match database {
        Database::MongoDB => Some("27017".to_string()),
        Database::PostgreSQL => Some("5432".to_string()),
        Database::SQLite => None,
        Database::MySQL => Some("3306".to_string()),
        Database::MariaDB => Some("3306".to_string()),
        Database::BetterSQLite => None,
        Database::LibSQL => None,
        Database::MsSQL => Some("1433".to_string()),
    }
}

pub(crate) fn generate_index_ts_database_export(
    base_path: &Path,
    databases: Option<Vec<String>>,
    manifest_data: Option<&ManifestData>,
) -> Result<RenderedTemplate> {
    let mut export_set = HashSet::new();
    let mut database_set = HashSet::new();

    let projects = match manifest_data {
        Some(ManifestData::Service(service)) => service.projects.clone(),
        Some(ManifestData::Worker(worker)) => worker.projects.clone(),
        _ => vec![],
    };

    projects.iter().for_each(|project| {
        if let Some(resources) = &project.resources {
            if let Some(database) = &resources.database {
                database_set.insert(database.to_string());
            }
        }
    });

    database_set.extend(databases.unwrap_or_default());

    database_set
        .iter()
        .map(|database| {
            let export_string = match database.parse::<Database>()? {
                Database::MongoDB => Some("nosql.base.properties"),
                Database::PostgreSQL => Some("sql.base.properties"),
                Database::SQLite => Some("sql.base.properties"),
                Database::MySQL => Some("sql.base.properties"),
                Database::MariaDB => Some("sql.base.properties"),
                Database::MsSQL => Some("sql.base.properties"),
                Database::BetterSQLite => Some("sql.base.properties"),
                Database::LibSQL => Some("sql.base.properties"),
            };

            if let Some(export_string) = export_string {
                export_set.insert(format!("export * from './{}'", export_string));
            }

            Ok(())
        })
        .collect::<Result<Vec<()>>>()?;

    Ok(RenderedTemplate {
        path: Path::new(&base_path)
            .join("core")
            .join("persistence")
            .join("index.ts"),
        content: export_set.into_iter().collect::<Vec<String>>().join("\n"),
        context: None,
    })
}

pub(crate) fn update_core_package_json(
    manifest_data: &ManifestData,
    base_path: &Path,
) -> Result<RenderedTemplate> {
    let package_json_path = base_path.join("core").join("package.json");
    let package_json_content = read_to_string(&package_json_path)?;
    let mut full_package_json: ProjectPackageJson = from_str(&package_json_content)?;

    let database = match manifest_data {
        ManifestData::Service(service) => Some(service.database.clone()),
        ManifestData::Worker(worker) => worker.database.clone(),
        _ => bail!(ERROR_UNSUPPORTED_DATABASE),
    };

    if let Some(database) = database {
        full_package_json
            .dependencies
            .as_mut()
            .unwrap()
            .databases
            .insert(database.parse::<Database>()?);

        full_package_json
            .dependencies
            .as_mut()
            .unwrap()
            .mikro_orm_database = Some(MIKRO_ORM_DATABASE_VERSION.to_string());
    }

    Ok(RenderedTemplate {
        path: package_json_path,
        content: to_string_pretty(&full_package_json)?,
        context: None,
    })
}

pub(crate) fn get_base_entity_filename(database: &Database) -> Result<&str> {
    match database {
        Database::MongoDB => Ok("nosql.base.properties.ts"),
        Database::PostgreSQL => Ok("sql.base.properties.ts"),
        Database::SQLite => Ok("sql.base.properties.ts"),
        Database::MySQL => Ok("sql.base.properties.ts"),
        Database::MariaDB => Ok("sql.base.properties.ts"),
        Database::BetterSQLite => Ok("sql.base.properties.ts"),
        Database::LibSQL => Ok("sql.base.properties.ts"),
        Database::MsSQL => Ok("sql.base.properties.ts"),
    }
}

pub(crate) fn add_base_entity_to_core(
    manifest_data: &ManifestData,
    base_path: &Path,
) -> Result<Vec<RenderedTemplate>> {
    let database = match manifest_data {
        ManifestData::Service(service) => service.database.clone(),
        ManifestData::Worker(worker) => worker.database.clone().unwrap(),
        _ => bail!(ERROR_UNSUPPORTED_DATABASE),
    };

    let database = database.parse::<Database>()?;
    let filename = get_base_entity_filename(&database)?;

    let entity_path = base_path.join("core").join("persistence").join(filename);

    let template = TEMPLATES_DIR.get_file(
        Path::new("project")
            .join("core")
            .join("persistence")
            .join(filename),
    );

    Ok(vec![
        RenderedTemplate {
            path: entity_path,
            content: template.unwrap().contents_utf8().unwrap().to_string(),
            context: None,
        },
        generate_index_ts_database_export(
            base_path,
            Some(vec![database.to_string()]),
            Some(manifest_data),
        )
        .with_context(|| ERROR_FAILED_TO_CREATE_DATABASE_EXPORT_INDEX_TS)?,
        update_core_package_json(manifest_data, base_path)?,
    ])
}

pub(crate) fn is_in_memory_database(database: &Database) -> bool {
    match database {
        Database::SQLite => true,
        Database::BetterSQLite => true,
        Database::LibSQL => true,
        _ => false,
    }
}

pub(crate) fn get_postinstall_script(database: &Database) -> Option<String> {
    match database {
        Database::SQLite => Some(SQLITE_POSTINSTALL_SCRIPT.to_string()),
        Database::BetterSQLite => Some(BETTER_SQLITE_POSTINSTALL_SCRIPT.to_string()),
        _ => None,
    }
}

pub(crate) fn get_database_variants(runtime: &Runtime) -> &[&str] {
    match runtime {
        Runtime::Bun => &Database::VARIANTS[..Database::VARIANTS.len() - 1],
        Runtime::Node => &Database::VARIANTS[..],
    }
}