1use std::{collections::HashMap, path::Path};
25
26use serde::Deserialize;
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).join(".vscode").join("land-config.json");
330
331 load_config(&config_path)
332}
333
334pub fn load_config(config_path:&Path) -> Result<LandConfig, String> {
352 if !config_path.exists() {
353 return Err(format!("Configuration file not found: {}", config_path.display()));
354 }
355
356 let content = std::fs::read_to_string(config_path).map_err(|e| format!("Failed to read config file: {}", e))?;
357
358 let config:LandConfig = json5::from_str(&content).map_err(|e| format!("Failed to parse config JSON: {}", e))?;
360
361 Ok(config)
362}
363
364pub fn get_profile<'a>(config:&'a LandConfig, profile_name:&str) -> Option<&'a Profile> {
375 config.profiles.get(profile_name)
376}
377
378pub fn get_workbench_type(config:&LandConfig, profile_name:&str) -> String {
389 if let Some(profile) = config.profiles.get(profile_name) {
390 if let Some(workbench) = &profile.workbench {
391 return workbench.clone();
392 }
393 }
394
395 if let Some(workbench_config) = &config.workbench {
397 if let Some(default) = &workbench_config.default {
398 return default.clone();
399 }
400 }
401
402 "Browser".to_string()
404}
405
406pub fn get_workbench_features<'a>(config:&'a LandConfig, workbench_type:&str) -> Option<&'a WorkbenchFeatures> {
417 if let Some(workbench_config) = &config.workbench {
418 if let Some(features) = &workbench_config.features {
419 return features.get(workbench_type);
420 }
421 }
422 None
423}
424
425pub fn resolve_profile_env(config:&LandConfig, profile_name:&str) -> HashMap<String, String> {
439 let mut env_vars = HashMap::new();
440
441 if let Some(templates) = &config.templates {
443 for (key, value) in &templates.env {
444 env_vars.insert(key.clone(), value.clone());
445 }
446 }
447
448 if let Some(profile) = config.profiles.get(profile_name) {
450 if let Some(profile_env) = &profile.env {
451 for (key, value) in profile_env {
452 env_vars.insert(key.clone(), value.clone());
453 }
454 }
455 }
456
457 let workbench_type = get_workbench_type(config, profile_name);
459 env_vars.insert(workbench_type.clone(), "true".to_string());
460
461 env_vars
462}
463
464pub fn resolve_profile_features(config:&LandConfig, profile_name:&str) -> HashMap<String, bool> {
477 let mut features = HashMap::new();
478
479 if let Some(feature_config) = &config.features {
481 for (name, config) in feature_config {
482 features.insert(name.clone(), config.default.unwrap_or(false));
483 }
484 }
485
486 if let Some(profile) = config.profiles.get(profile_name) {
488 if let Some(profile_features) = &profile.features {
489 for (key, value) in profile_features {
490 features.insert(key.clone(), *value);
491 }
492 }
493 }
494
495 features
496}
497
498pub fn features_to_env(features:&HashMap<String, bool>) -> HashMap<String, String> {
510 let mut env_vars = HashMap::new();
511
512 for (name, value) in features {
513 let env_key = format!("FEATURE_{}", name.to_uppercase().replace("-", "_"));
514 env_vars.insert(env_key, value.to_string());
515 }
516
517 env_vars
518}
519
520pub fn get_build_command(config:&LandConfig, profile_name:&str) -> Option<String> {
531 config.build_commands.as_ref()?.get(profile_name).cloned()
532}
533
534#[cfg(test)]
539mod tests {
540 use super::*;
541
542 #[test]
543 fn test_load_config() {
544 }
547
548 #[test]
549 fn test_features_to_env() {
550 let mut features = HashMap::new();
551 features.insert("tauri_ipc".to_string(), true);
552 features.insert("wind_services".to_string(), false);
553
554 let env = features_to_env(&features);
555
556 assert_eq!(env.get("FEATURE_TAURI_IPC"), Some(&"true".to_string()));
557 assert_eq!(env.get("FEATURE_WIND_SERVICES"), Some(&"false".to_string()));
558 }
559}