1use serde::Deserialize;
25use std::collections::HashMap;
26use std::path::Path;
27
28#[derive(Debug, Deserialize, Clone)]
34pub struct LandConfig {
35 pub version: String,
37 pub workbench: Option<WorkbenchConfig>,
39 pub features: Option<HashMap<String, FeatureConfig>>,
41 pub binary: Option<BinaryConfig>,
43 pub profiles: HashMap<String, Profile>,
45 pub templates: Option<Templates>,
47 #[serde(rename = "env_prefixes")]
49 pub env_prefixes: Option<HashMap<String, String>>,
50 #[serde(rename = "build_commands")]
52 pub build_commands: Option<HashMap<String, String>>,
53 #[serde(rename = "environment_variables")]
55 pub environment_variables: Option<EnvironmentVariableInventory>,
56 pub cli: Option<CliConfig>,
58}
59
60#[derive(Debug, Deserialize, Clone)]
62pub struct CliConfig {
63 #[serde(rename = "default_profile")]
65 pub default_profile: Option<String>,
66 #[serde(rename = "config_file")]
68 pub config_file: Option<String>,
69 #[serde(rename = "log_format")]
71 pub log_format: Option<String>,
72 pub colors: Option<bool>,
74 pub progress: Option<bool>,
76 #[serde(rename = "dry_run_default")]
78 pub dry_run_default: Option<bool>,
79 #[serde(rename = "profile_aliases")]
81 pub profile_aliases: HashMap<String, String>,
82}
83
84#[derive(Debug, Deserialize, Clone)]
86pub struct EnvironmentVariableInventory {
87 #[serde(rename = "build_flags")]
89 pub build_flags: Option<HashMap<String, EnvironmentVariableInfo>>,
90 #[serde(rename = "build_config")]
92 pub build_config: Option<HashMap<String, EnvironmentVariableInfo>>,
93 pub node: Option<HashMap<String, EnvironmentVariableInfo>>,
95 pub rust: Option<HashMap<String, EnvironmentVariableInfo>>,
97 pub mountain: Option<HashMap<String, EnvironmentVariableInfo>>,
99 pub tauri: Option<HashMap<String, EnvironmentVariableInfo>>,
101 pub apple: Option<HashMap<String, EnvironmentVariableInfo>>,
103 pub android: Option<HashMap<String, EnvironmentVariableInfo>>,
105 pub ci: Option<HashMap<String, EnvironmentVariableInfo>>,
107 pub api: Option<HashMap<String, EnvironmentVariableInfo>>,
109 pub other: Option<HashMap<String, EnvironmentVariableInfo>>,
111}
112
113#[derive(Debug, Deserialize, Clone)]
115pub struct EnvironmentVariableInfo {
116 #[serde(rename = "type")]
118 pub var_type: Option<String>,
119 pub description: Option<String>,
121 pub values: Option<Vec<String>>,
123 pub default: Option<String>,
125 #[serde(rename = "config_path")]
127 pub config_path: Option<String>,
128 pub sensitive: Option<bool>,
130}
131
132#[derive(Debug, Deserialize, Clone)]
134pub struct WorkbenchConfig {
135 pub default: Option<String>,
137 pub available: Option<Vec<String>>,
139 pub features: Option<HashMap<String, WorkbenchFeatures>>,
141}
142
143#[derive(Debug, Deserialize, Clone)]
145pub struct WorkbenchFeatures {
146 pub description: Option<String>,
148 pub coverage: Option<String>,
150 pub complexity: Option<String>,
152 pub polyfills: Option<bool>,
154 #[serde(rename = "mountain_providers")]
156 pub mountain_providers: Option<bool>,
157 #[serde(rename = "wind_services")]
159 pub wind_services: Option<bool>,
160 #[serde(rename = "electron_apis")]
162 pub electron_apis: Option<bool>,
163 pub recommended: Option<bool>,
165 #[serde(rename = "recommended_for")]
167 pub recommended_for: Option<Vec<String>>,
168}
169
170#[derive(Debug, Deserialize, Clone)]
172pub struct FeatureConfig {
173 pub description: Option<String>,
175 pub default: Option<bool>,
177 #[serde(rename = "depends_on")]
179 pub depends_on: Option<Vec<String>>,
180}
181
182#[derive(Debug, Deserialize, Clone)]
184pub struct BinaryConfig {
185 #[serde(rename = "name_template")]
187 pub name_template: Option<String>,
188 #[serde(rename = "identifier_template")]
190 pub identifier_template: Option<String>,
191 #[serde(rename = "version_format")]
193 pub version_format: Option<String>,
194 pub sign: Option<SignConfig>,
196 pub notarize: Option<NotarizeConfig>,
198 pub updater: Option<UpdaterConfig>,
200}
201
202#[derive(Debug, Deserialize, Clone)]
204pub struct SignConfig {
205 pub macos: Option<MacOSSignConfig>,
207 pub windows: Option<WindowsSignConfig>,
209 pub linux: Option<LinuxSignConfig>,
211}
212
213#[derive(Debug, Deserialize, Clone)]
215pub struct MacOSSignConfig {
216 pub identity: Option<String>,
218 pub entitlements: Option<String>,
220 #[serde(rename = "hardenedRuntime")]
222 pub hardened_runtime: Option<bool>,
223 #[serde(rename = "gatekeeper_assess")]
225 pub gatekeeper_assess: Option<bool>,
226}
227
228#[derive(Debug, Deserialize, Clone)]
230pub struct WindowsSignConfig {
231 pub certificate: Option<String>,
233 #[serde(rename = "timestamp_server")]
235 pub timestamp_server: Option<String>,
236 #[serde(rename = "tsa_can_only_access_urls")]
238 pub tsa_can_only_access_urls: Option<Vec<String>>,
239}
240
241#[derive(Debug, Deserialize, Clone)]
243pub struct LinuxSignConfig {
244 #[serde(rename = "gpg_key")]
246 pub gpg_key: Option<String>,
247 #[serde(rename = "gpg_passphrase_env")]
249 pub gpg_passphrase_env: Option<String>,
250}
251
252#[derive(Debug, Deserialize, Clone)]
254pub struct NotarizeConfig {
255 pub macos: Option<MacOSNotarizeConfig>,
257}
258
259#[derive(Debug, Deserialize, Clone)]
261pub struct MacOSNotarizeConfig {
262 #[serde(rename = "apple_id")]
264 pub apple_id: Option<String>,
265 #[serde(rename = "password_env")]
267 pub password_env: Option<String>,
268 #[serde(rename = "team_id")]
270 pub team_id: Option<String>,
271}
272
273#[derive(Debug, Deserialize, Clone)]
275pub struct UpdaterConfig {
276 pub enabled: Option<bool>,
278 pub endpoints: Option<Vec<String>>,
280 pub pubkey: Option<String>,
282}
283
284#[derive(Debug, Deserialize, Clone)]
286pub struct Profile {
287 pub description: Option<String>,
289 pub workbench: Option<String>,
291 pub env: Option<HashMap<String, String>>,
293 pub features: Option<HashMap<String, bool>>,
295 #[serde(rename = "rhai_script")]
297 pub rhai_script: Option<String>,
298}
299
300#[derive(Debug, Deserialize, Clone)]
302pub struct Templates {
303 pub env: HashMap<String, String>,
305}
306
307pub fn load(workspace_root: &str) -> Result<LandConfig, String> {
329 let config_path = Path::new(workspace_root)
330 .join(".vscode")
331 .join("land-config.json");
332
333 load_config(&config_path)
334}
335
336pub fn load_config(config_path: &Path) -> Result<LandConfig, String> {
354 if !config_path.exists() {
355 return Err(format!(
356 "Configuration file not found: {}",
357 config_path.display()
358 ));
359 }
360
361 let content = std::fs::read_to_string(config_path)
362 .map_err(|e| format!("Failed to read config file: {}", e))?;
363
364 let config: LandConfig = json5::from_str(&content)
366 .map_err(|e| format!("Failed to parse config JSON: {}", e))?;
367
368 Ok(config)
369}
370
371pub fn get_profile<'a>(config: &'a LandConfig, profile_name: &str) -> Option<&'a Profile> {
382 config.profiles.get(profile_name)
383}
384
385pub fn get_workbench_type(config: &LandConfig, profile_name: &str) -> String {
396 if let Some(profile) = config.profiles.get(profile_name) {
397 if let Some(workbench) = &profile.workbench {
398 return workbench.clone();
399 }
400 }
401
402 if let Some(workbench_config) = &config.workbench {
404 if let Some(default) = &workbench_config.default {
405 return default.clone();
406 }
407 }
408
409 "Browser".to_string()
411}
412
413pub fn get_workbench_features<'a>(
424 config: &'a LandConfig,
425 workbench_type: &str,
426) -> Option<&'a WorkbenchFeatures> {
427 if let Some(workbench_config) = &config.workbench {
428 if let Some(features) = &workbench_config.features {
429 return features.get(workbench_type);
430 }
431 }
432 None
433}
434
435pub fn resolve_profile_env(config: &LandConfig, profile_name: &str) -> HashMap<String, String> {
449 let mut env_vars = HashMap::new();
450
451 if let Some(templates) = &config.templates {
453 for (key, value) in &templates.env {
454 env_vars.insert(key.clone(), value.clone());
455 }
456 }
457
458 if let Some(profile) = config.profiles.get(profile_name) {
460 if let Some(profile_env) = &profile.env {
461 for (key, value) in profile_env {
462 env_vars.insert(key.clone(), value.clone());
463 }
464 }
465 }
466
467 let workbench_type = get_workbench_type(config, profile_name);
469 env_vars.insert(workbench_type.clone(), "true".to_string());
470
471 env_vars
472}
473
474pub fn resolve_profile_features(config: &LandConfig, profile_name: &str) -> HashMap<String, bool> {
487 let mut features = HashMap::new();
488
489 if let Some(feature_config) = &config.features {
491 for (name, config) in feature_config {
492 features.insert(name.clone(), config.default.unwrap_or(false));
493 }
494 }
495
496 if let Some(profile) = config.profiles.get(profile_name) {
498 if let Some(profile_features) = &profile.features {
499 for (key, value) in profile_features {
500 features.insert(key.clone(), *value);
501 }
502 }
503 }
504
505 features
506}
507
508pub fn features_to_env(features: &HashMap<String, bool>) -> HashMap<String, String> {
520 let mut env_vars = HashMap::new();
521
522 for (name, value) in features {
523 let env_key = format!("FEATURE_{}", name.to_uppercase().replace("-", "_"));
524 env_vars.insert(env_key, value.to_string());
525 }
526
527 env_vars
528}
529
530pub fn get_build_command(config: &LandConfig, profile_name: &str) -> Option<String> {
541 config.build_commands.as_ref()?.get(profile_name).cloned()
542}
543
544#[cfg(test)]
549mod tests {
550 use super::*;
551
552 #[test]
553 fn test_load_config() {
554 }
557
558 #[test]
559 fn test_features_to_env() {
560 let mut features = HashMap::new();
561 features.insert("tauri_ipc".to_string(), true);
562 features.insert("wind_services".to_string(), false);
563
564 let env = features_to_env(&features);
565
566 assert_eq!(env.get("FEATURE_TAURI_IPC"), Some(&"true".to_string()));
567 assert_eq!(env.get("FEATURE_WIND_SERVICES"), Some(&"false".to_string()));
568 }
569}