openapi_nexus_config/
typescript_config.rs

1//! TypeScript-specific configuration
2
3use std::fmt;
4use std::str::FromStr;
5
6use clap::Args;
7use serde::{Deserialize, Serialize};
8
9use openapi_nexus_core::NamingConvention;
10
11/// TypeScript module systems
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub enum TypeScriptModule {
14    CommonJS,
15    ESNext,
16    ES2020,
17    ES2022,
18}
19
20impl FromStr for TypeScriptModule {
21    type Err = String;
22
23    fn from_str(s: &str) -> Result<Self, Self::Err> {
24        match s.to_lowercase().as_str() {
25            "commonjs" | "cjs" => Ok(Self::CommonJS),
26            "esnext" => Ok(Self::ESNext),
27            "es2020" => Ok(Self::ES2020),
28            "es2022" => Ok(Self::ES2022),
29            _ => Err(format!(
30                "Invalid TypeScript module: '{}'. Expected one of: commonjs, esnext, es2020, es2022",
31                s
32            )),
33        }
34    }
35}
36
37impl fmt::Display for TypeScriptModule {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            TypeScriptModule::CommonJS => write!(f, "commonjs"),
41            TypeScriptModule::ESNext => write!(f, "esnext"),
42            TypeScriptModule::ES2020 => write!(f, "es2020"),
43            TypeScriptModule::ES2022 => write!(f, "es2022"),
44        }
45    }
46}
47
48/// TypeScript-specific configuration (supports CLI args, env vars, and config files)
49#[derive(Debug, Clone, Args, Serialize, Deserialize)]
50#[command(next_help_heading = "TypeScript Options")]
51pub struct TypeScriptConfig {
52    /// File naming convention (camelCase, kebab-case, snake_case, PascalCase)
53    #[arg(long = "ts-file-naming-convention", env = "OPENAPI_NEXUS_TS_FILE_NAMING_CONVENTION", default_value_t = default_file_naming_convention())]
54    #[serde(default = "default_file_naming_convention")]
55    pub file_naming_convention: NamingConvention,
56
57    /// NPM package scope/prefix (e.g., "@myorg")
58    #[arg(long = "ts-package-scope", env = "OPENAPI_NEXUS_TS_PACKAGE_SCOPE")]
59    #[serde(default)]
60    pub package_scope: Option<String>,
61
62    /// Override package name (defaults to OpenAPI title in kebab-case)
63    #[arg(long = "ts-package-name", env = "OPENAPI_NEXUS_TS_PACKAGE_NAME")]
64    #[serde(default)]
65    pub package_name: Option<String>,
66
67    /// Whether to generate npm package files
68    #[arg(long = "ts-generate-package", env = "OPENAPI_NEXUS_TS_GENERATE_PACKAGE", default_value_t = default_generate_package())]
69    #[serde(default = "default_generate_package")]
70    pub generate_package: bool,
71
72    /// TypeScript compiler target
73    #[arg(long = "ts-target", env = "OPENAPI_NEXUS_TS_TARGET", default_value_t = default_typescript_target())]
74    #[serde(default = "default_typescript_target")]
75    pub ts_target: String,
76
77    /// TypeScript module system (commonjs, esnext, es2020, es2022)
78    #[arg(long = "ts-module", env = "OPENAPI_NEXUS_TS_MODULE", value_parser = TypeScriptModule::from_str, default_value_t = default_typescript_module())]
79    #[serde(default = "default_typescript_module")]
80    pub ts_module: TypeScriptModule,
81
82    /// TypeScript compiler lib array (e.g., "ES2020,DOM" or ["ES2020", "DOM"] in TOML)
83    #[arg(long = "ts-lib", env = "OPENAPI_NEXUS_TS_LIB", value_delimiter = ',')]
84    #[serde(default, deserialize_with = "deserialize_string_vec")]
85    pub ts_lib: Option<Vec<String>>,
86
87    /// Whether to generate ESM configuration
88    #[arg(long = "ts-generate-esm-config", env = "OPENAPI_NEXUS_TS_GENERATE_ESM_CONFIG", default_value_t = default_generate_esm_config())]
89    #[serde(default = "default_generate_esm_config")]
90    pub generate_esm_config: bool,
91
92    /// Whether to include build scripts in package.json
93    #[arg(long = "ts-include-build-scripts", env = "OPENAPI_NEXUS_TS_INCLUDE_BUILD_SCRIPTS", default_value_t = default_include_build_scripts())]
94    #[serde(default = "default_include_build_scripts")]
95    pub include_build_scripts: bool,
96}
97
98fn default_file_naming_convention() -> NamingConvention {
99    NamingConvention::PascalCase
100}
101
102fn default_generate_package() -> bool {
103    true
104}
105
106fn default_typescript_target() -> String {
107    "es6".to_string()
108}
109
110fn default_typescript_module() -> TypeScriptModule {
111    TypeScriptModule::CommonJS
112}
113
114fn default_generate_esm_config() -> bool {
115    true
116}
117
118fn default_include_build_scripts() -> bool {
119    false
120}
121
122/// Helper to deserialize string vec from TOML array or comma-separated string
123fn deserialize_string_vec<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
124where
125    D: serde::Deserializer<'de>,
126{
127    use serde::Deserialize;
128
129    #[derive(Deserialize)]
130    #[serde(untagged)]
131    enum StringVec {
132        Array(Vec<String>),
133        String(String),
134    }
135
136    match StringVec::deserialize(deserializer)? {
137        StringVec::Array(vec) => Ok(Some(vec)),
138        StringVec::String(s) => {
139            if s.is_empty() {
140                Ok(None)
141            } else {
142                Ok(Some(s.split(',').map(|s| s.trim().to_string()).collect()))
143            }
144        }
145    }
146}
147
148impl Default for TypeScriptConfig {
149    fn default() -> Self {
150        Self {
151            file_naming_convention: default_file_naming_convention(),
152            package_scope: None,
153            package_name: None,
154            generate_package: default_generate_package(),
155            ts_target: default_typescript_target(),
156            ts_module: default_typescript_module(),
157            ts_lib: None,
158            generate_esm_config: default_generate_esm_config(),
159            include_build_scripts: default_include_build_scripts(),
160        }
161    }
162}