Skip to main content

storm_config/
workspace_config.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::path::Path;
4use std::str::FromStr;
5use std::{fmt, fs};
6
7use serde::de::DeserializeOwned;
8use serde::{Deserialize, Serialize};
9use storm_workspace::utils::get_workspace_root;
10
11use crate::types::PackageJson;
12use crate::{Config, ConfigError, Environment, File, Value};
13
14#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
15#[serde(rename_all = "lowercase")]
16pub enum WorkspaceMode {
17  Development,
18  Test,
19  Production,
20}
21
22impl Default for WorkspaceMode {
23  fn default() -> Self {
24    WorkspaceMode::Production
25  }
26}
27
28impl FromStr for WorkspaceMode {
29  type Err = ();
30
31  fn from_str(input: &str) -> Result<WorkspaceMode, Self::Err> {
32    match input {
33      "development" => Ok(WorkspaceMode::Development),
34      "test" => Ok(WorkspaceMode::Test),
35      "production" => Ok(WorkspaceMode::Production),
36      _ => Err(()),
37    }
38  }
39}
40
41impl fmt::Display for WorkspaceMode {
42  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43    match *self {
44      WorkspaceMode::Development => write!(f, "development"),
45      WorkspaceMode::Test => write!(f, "test"),
46      WorkspaceMode::Production => write!(f, "production"),
47    }
48  }
49}
50
51#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
52#[serde(rename_all = "lowercase")]
53pub enum WorkspaceVariant {
54  Minimal,
55  Monorepo,
56}
57
58impl Default for WorkspaceVariant {
59  fn default() -> Self {
60    WorkspaceVariant::Monorepo
61  }
62}
63
64impl FromStr for WorkspaceVariant {
65  type Err = ();
66
67  fn from_str(input: &str) -> Result<WorkspaceVariant, Self::Err> {
68    match input {
69      "minimal" => Ok(WorkspaceVariant::Minimal),
70      "monorepo" => Ok(WorkspaceVariant::Monorepo),
71      _ => Err(()),
72    }
73  }
74}
75
76impl fmt::Display for WorkspaceVariant {
77  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78    match *self {
79      WorkspaceVariant::Minimal => write!(f, "minimal"),
80      WorkspaceVariant::Monorepo => write!(f, "monorepo"),
81    }
82  }
83}
84
85#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
86#[serde(rename_all = "lowercase")]
87pub enum PackageManagerType {
88  Npm,
89  Yarn,
90  Pnpm,
91  Bun,
92}
93
94impl FromStr for PackageManagerType {
95  type Err = ();
96
97  fn from_str(input: &str) -> Result<PackageManagerType, Self::Err> {
98    match input {
99      "npm" => Ok(PackageManagerType::Npm),
100      "yarn" => Ok(PackageManagerType::Yarn),
101      "pnpm" => Ok(PackageManagerType::Pnpm),
102      "bun" => Ok(PackageManagerType::Bun),
103      _ => Err(()),
104    }
105  }
106}
107
108impl fmt::Display for PackageManagerType {
109  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110    match *self {
111      PackageManagerType::Npm => write!(f, "npm"),
112      PackageManagerType::Yarn => write!(f, "yarn"),
113      PackageManagerType::Pnpm => write!(f, "pnpm"),
114      PackageManagerType::Bun => write!(f, "bun"),
115    }
116  }
117}
118
119#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
120#[serde(rename_all = "lowercase")]
121pub enum LogLevel {
122  Silent,
123  Fatal,
124  Error,
125  Warn,
126  Success,
127  Info,
128  Debug,
129  Trace,
130  All,
131}
132
133impl FromStr for LogLevel {
134  type Err = ();
135
136  fn from_str(input: &str) -> Result<LogLevel, Self::Err> {
137    match input {
138      "silent" => Ok(LogLevel::Silent),
139      "fatal" => Ok(LogLevel::Fatal),
140      "error" => Ok(LogLevel::Error),
141      "warn" => Ok(LogLevel::Warn),
142      "success" => Ok(LogLevel::Success),
143      "info" => Ok(LogLevel::Info),
144      "debug" => Ok(LogLevel::Debug),
145      "trace" => Ok(LogLevel::Trace),
146      "all" => Ok(LogLevel::All),
147      _ => Err(()),
148    }
149  }
150}
151
152impl fmt::Display for LogLevel {
153  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
154    match *self {
155      LogLevel::Silent => write!(f, "silent"),
156      LogLevel::Fatal => write!(f, "fatal"),
157      LogLevel::Error => write!(f, "error"),
158      LogLevel::Warn => write!(f, "warn"),
159      LogLevel::Success => write!(f, "success"),
160      LogLevel::Info => write!(f, "info"),
161      LogLevel::Debug => write!(f, "debug"),
162      LogLevel::Trace => write!(f, "trace"),
163      LogLevel::All => write!(f, "all"),
164    }
165  }
166}
167
168#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
169#[serde(untagged)]
170pub enum ExtendsConfig {
171  Single(String),
172  Multiple(Vec<String>),
173}
174
175#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
176#[serde(rename_all = "camelCase")]
177pub struct OrganizationDetails {
178  /// The name of the organization
179  pub name: Option<String>,
180  /// A description of the organization
181  pub description: Option<String>,
182  /// A URL to the organization's logo image
183  pub logo: Option<String>,
184  /// A URL to the organization's icon image
185  pub icon: Option<String>,
186  /// A URL to a page that provides more information about the organization
187  pub url: Option<String>,
188}
189
190#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
191#[serde(untagged)]
192pub enum WorkspaceOrganizationConfig {
193  Details(OrganizationDetails),
194  Name(String),
195}
196
197impl Default for WorkspaceOrganizationConfig {
198  fn default() -> Self {
199    WorkspaceOrganizationConfig::Name("storm-software".to_string())
200  }
201}
202
203#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
204#[serde(rename_all = "camelCase")]
205pub struct ColorPaletteConfig {
206  /// The dark background color of the workspace
207  pub dark: String,
208  /// The light background color of the workspace
209  pub light: String,
210  /// The primary brand specific color of the workspace
211  pub brand: String,
212  /// The alternate brand specific color of the workspace
213  pub alternate: Option<String>,
214  /// The secondary brand specific color of the workspace
215  pub accent: Option<String>,
216  /// The color used to display hyperlink text
217  pub link: String,
218  /// The help color of the workspace
219  pub help: String,
220  /// The success color of the workspace
221  pub success: String,
222  /// The info color of the workspace
223  pub info: String,
224  /// The warning color of the workspace
225  pub warning: String,
226  /// The danger color of the workspace
227  pub danger: String,
228  /// The fatal color of the workspace
229  pub fatal: Option<String>,
230  /// The positive color of the workspace
231  pub positive: String,
232  /// The negative color of the workspace
233  pub negative: String,
234  /// The gradient color stops of the workspace
235  pub gradient: Option<Vec<String>>,
236}
237
238impl Default for ColorPaletteConfig {
239  fn default() -> Self {
240    Self {
241      dark: "#151718".to_string(),
242      light: "#cbd5e1".to_string(),
243      brand: "#1fb2a6".to_string(),
244      alternate: None,
245      accent: None,
246      link: "#3fa6ff".to_string(),
247      help: "#818cf8".to_string(),
248      success: "#45b27e".to_string(),
249      info: "#38bdf8".to_string(),
250      warning: "#f3d371".to_string(),
251      danger: "#d8314a".to_string(),
252      fatal: None,
253      positive: "#4ade80".to_string(),
254      negative: "#ef4444".to_string(),
255      gradient: None,
256    }
257  }
258}
259
260#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
261#[serde(rename_all = "camelCase")]
262pub struct ColorSchemaConfig {
263  /// The foreground color of the workspace
264  pub foreground: String,
265  /// The background color of the workspace
266  pub background: String,
267  /// The primary brand specific color of the workspace
268  pub brand: String,
269  /// The alternate brand specific color of the workspace
270  pub alternate: Option<String>,
271  /// The secondary brand specific color of the workspace
272  pub accent: Option<String>,
273  /// The color used to display hyperlink text
274  pub link: String,
275  /// The help color of the workspace
276  pub help: String,
277  /// The success color of the workspace
278  pub success: String,
279  /// The info color of the workspace
280  pub info: String,
281  /// The warning color of the workspace
282  pub warning: String,
283  /// The danger color of the workspace
284  pub danger: String,
285  /// The fatal color of the workspace
286  pub fatal: Option<String>,
287  /// The positive color of the workspace
288  pub positive: String,
289  /// The negative color of the workspace
290  pub negative: String,
291  /// The gradient color stops of the workspace
292  pub gradient: Option<Vec<String>>,
293}
294
295impl ColorSchemaConfig {
296  fn default_dark() -> Self {
297    Self {
298      foreground: "#cbd5e1".to_string(),
299      background: "#151718".to_string(),
300      brand: "#1fb2a6".to_string(),
301      alternate: None,
302      accent: None,
303      link: "#3fa6ff".to_string(),
304      help: "#818cf8".to_string(),
305      success: "#45b27e".to_string(),
306      info: "#38bdf8".to_string(),
307      warning: "#f3d371".to_string(),
308      danger: "#d8314a".to_string(),
309      fatal: None,
310      positive: "#4ade80".to_string(),
311      negative: "#ef4444".to_string(),
312      gradient: None,
313    }
314  }
315
316  fn default_light() -> Self {
317    Self {
318      foreground: "#151718".to_string(),
319      background: "#cbd5e1".to_string(),
320      brand: "#1fb2a6".to_string(),
321      alternate: None,
322      accent: None,
323      link: "#3fa6ff".to_string(),
324      help: "#818cf8".to_string(),
325      success: "#45b27e".to_string(),
326      info: "#38bdf8".to_string(),
327      warning: "#f3d371".to_string(),
328      danger: "#d8314a".to_string(),
329      fatal: None,
330      positive: "#4ade80".to_string(),
331      negative: "#ef4444".to_string(),
332      gradient: None,
333    }
334  }
335}
336
337impl Default for ColorSchemaConfig {
338  fn default() -> Self {
339    ColorSchemaConfig::default_dark()
340  }
341}
342
343#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
344#[serde(rename_all = "camelCase")]
345pub struct ColorThemeConfig {
346  /// The light color schema of the workspace
347  pub light: ColorSchemaConfig,
348  /// The dark color schema of the workspace
349  pub dark: ColorSchemaConfig,
350}
351
352impl Default for ColorThemeConfig {
353  fn default() -> Self {
354    Self { light: ColorSchemaConfig::default_light(), dark: ColorSchemaConfig::default_dark() }
355  }
356}
357
358#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
359#[serde(untagged)]
360pub enum ColorThemeEntry {
361  Palette(ColorPaletteConfig),
362  Theme(ColorThemeConfig),
363}
364
365#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
366#[serde(untagged)]
367pub enum WorkspaceColorsConfig {
368  Palette(ColorPaletteConfig),
369  Theme(ColorThemeConfig),
370  Collection(HashMap<String, ColorThemeEntry>),
371}
372
373impl Default for WorkspaceColorsConfig {
374  fn default() -> Self {
375    WorkspaceColorsConfig::Palette(ColorPaletteConfig::default())
376  }
377}
378
379#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
380#[serde(rename_all = "camelCase")]
381pub struct WorkspaceRegistryUrlConfig {
382  /// A remote registry URL used to publish distributable packages to GitHub
383  pub github: Option<String>,
384  /// A remote registry URL used to publish distributable packages to npm
385  pub npm: Option<String>,
386  /// A remote registry URL used to publish distributable packages to crates.io
387  pub cargo: Option<String>,
388  /// A remote registry URL used to publish distributable packages to Cyclone
389  pub cyclone: Option<String>,
390  /// A remote registry URL used to publish container images
391  pub container: Option<String>,
392}
393
394#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
395#[serde(rename_all = "camelCase")]
396pub struct WorkspaceBotConfig {
397  /// The workspace bot user's name (this is the bot that will be used to perform various tasks)
398  pub name: String,
399  /// The email of the workspace bot
400  pub email: String,
401}
402
403impl Default for WorkspaceBotConfig {
404  fn default() -> Self {
405    Self { name: "stormie-bot".to_string(), email: "stormie-bot@stormsoftware.com".to_string() }
406  }
407}
408
409#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
410#[serde(rename_all = "camelCase")]
411pub struct WorkspaceReleaseBannerConfig {
412  /// The URL for the workspace's release banner image
413  pub url: Option<String>,
414  /// The alt text for the workspace's release banner image
415  pub alt: String,
416}
417
418impl Default for WorkspaceReleaseBannerConfig {
419  fn default() -> Self {
420    Self { url: None, alt: "The workspace's banner image".to_string() }
421  }
422}
423
424#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
425#[serde(untagged)]
426pub enum ReleaseBannerConfig {
427  Url(String),
428  Details(WorkspaceReleaseBannerConfig),
429}
430
431impl Default for ReleaseBannerConfig {
432  fn default() -> Self {
433    ReleaseBannerConfig::Details(WorkspaceReleaseBannerConfig::default())
434  }
435}
436
437#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
438#[serde(rename_all = "camelCase")]
439pub struct WorkspaceReleaseConfig {
440  /// The workspace's release banner details or URL
441  pub banner: ReleaseBannerConfig,
442  /// A header message appended to the start of the release notes
443  pub header: Option<String>,
444  /// A footer message appended to the end of the release notes
445  pub footer: Option<String>,
446}
447
448impl Default for WorkspaceReleaseConfig {
449  fn default() -> Self {
450    Self { banner: ReleaseBannerConfig::default(), header: None, footer: None }
451  }
452}
453
454#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
455#[serde(rename_all = "camelCase")]
456pub struct WorkspaceSocialsConfig {
457  /// A Twitter/X account associated with the organization/project
458  pub twitter: Option<String>,
459  /// A Discord account associated with the organization/project
460  pub discord: Option<String>,
461  /// A Telegram account associated with the organization/project
462  pub telegram: Option<String>,
463  /// A Slack account associated with the organization/project
464  pub slack: Option<String>,
465  /// A Medium account associated with the organization/project
466  pub medium: Option<String>,
467  /// A GitHub account associated with the organization/project
468  pub github: Option<String>,
469}
470
471#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
472#[serde(rename_all = "camelCase")]
473pub struct WorkspaceErrorConfig {
474  /// The path to the workspace's error codes JSON file
475  pub codes_file: String,
476  /// A URL to a page that looks up the workspace's error messages given a specific error code
477  pub url: Option<String>,
478}
479
480impl Default for WorkspaceErrorConfig {
481  fn default() -> Self {
482    Self { codes_file: "tools/errors/codes.json".to_string(), url: None }
483  }
484}
485
486#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
487#[serde(rename_all = "camelCase")]
488pub struct WorkspaceDirectoriesConfig {
489  /// The directory used to store the environment's cached file data
490  pub cache: Option<String>,
491  /// The directory used to store the environment's data files
492  pub data: Option<String>,
493  /// The directory used to store the environment's configuration files
494  pub config: Option<String>,
495  /// The directory used to store the environment's temp files
496  pub temp: Option<String>,
497  /// The directory used to store the environment's log files
498  pub log: Option<String>,
499  /// The directory used to store the workspace's distributable files after a build
500  pub build: String,
501}
502
503impl Default for WorkspaceDirectoriesConfig {
504  fn default() -> Self {
505    Self { cache: None, data: None, config: None, temp: None, log: None, build: "dist".to_string() }
506  }
507}
508
509/// Storm Workspace config values used during various development processes.
510/// It represents the shared config of the entire monorepo.
511#[derive(Debug, Clone)]
512pub struct WorkspaceConfig {
513  /// The JSON schema reference describing the workspace configuration
514  pub schema: String,
515  /// The configuration values parsed from the file
516  pub config: Option<Config>,
517  /// The filepath of the Storm config. When this field is null, no config file was found in the current workspace.
518  pub config_file: Option<String>,
519  /// Optional configuration presets to extend from
520  pub extends: Option<ExtendsConfig>,
521  /// The root directory of the package
522  pub workspace_root: String,
523  /// The name of the package
524  pub name: String,
525  /// The namespace of the package
526  pub namespace: String,
527  /// The configured workspace variant
528  pub variant: WorkspaceVariant,
529  /// The organization configuration backing the workspace
530  pub organization: WorkspaceOrganizationConfig,
531  /// The repo URL of the workspace (i.e. GitHub)
532  pub repository: String,
533  /// The license used by the package
534  pub license: String,
535  /// The homepage of the workspace
536  pub homepage: String,
537  /// The documentation site for the workspace
538  pub docs: Option<String>,
539  /// The development portal site for the workspace
540  pub portal: Option<String>,
541  /// The licensing site for the workspace
542  pub licensing: Option<String>,
543  /// The contact site for the workspace
544  pub contact: Option<String>,
545  /// The support site for the workspace
546  pub support: Option<String>,
547  /// The branch of the workspace
548  pub branch: String,
549  /// A tag specifying the version pre-release identifier
550  pub preid: Option<String>,
551  /// The owner of the package
552  pub owner: String,
553  /// The workspace bot configuration used to automate tasks
554  pub bot: WorkspaceBotConfig,
555  /// The current workspace runtime mode (development/test/production)
556  pub mode: WorkspaceMode,
557  /// Configured color settings for the workspace
558  pub colors: WorkspaceColorsConfig,
559  /// The workspace release configuration
560  pub release: WorkspaceReleaseConfig,
561  /// The workspace socials configuration
562  pub socials: WorkspaceSocialsConfig,
563  /// The workspace error configuration
564  pub error: WorkspaceErrorConfig,
565  /// Should all known types of workspace caching be skipped?
566  pub skip_cache: bool,
567  /// The registry configuration for the workspace
568  pub registry: WorkspaceRegistryUrlConfig,
569  /// The directories configuration for the workspace
570  pub directories: WorkspaceDirectoriesConfig,
571  /// The package manager used by the repository
572  pub package_manager: PackageManagerType,
573  /// The default timezone of the workspace
574  pub timezone: String,
575  /// The default locale of the workspace
576  pub locale: String,
577  /// The log level used to filter out lower priority log messages. If not provided, this is defaulted using the `environment` config value (if `environment` is set to `production` then `level` is `error`, else `level` is `debug`).
578  pub log_level: LogLevel,
579  /// Should the logging of the current Storm Workspace configuration be skipped?
580  pub skip_config_logging: bool,
581  /// Configuration of each used extension
582  pub extensions: RefCell<HashMap<String, HashMap<String, Value>>>,
583}
584
585impl WorkspaceConfig {
586  pub fn new() -> Result<Self, ConfigError> {
587    let workspace_root = get_workspace_root().expect("No workspace root could be found");
588    let workspace_config = Self::from_workspace_root(&workspace_root)?;
589
590    Ok(workspace_config)
591  }
592
593  pub fn from_workspace_root(workspace_root: &Path) -> Result<Self, ConfigError> {
594    let mut workspace_config = Self::default();
595    workspace_config.workspace_root = workspace_root.to_str().unwrap().to_string();
596
597    match fs::metadata(format!("{}/package.json", workspace_root.to_string_lossy())) {
598      Ok(_) => {
599        let package_json_path = format!("{}/package.json", workspace_root.to_string_lossy());
600
601        let package_json: PackageJson = serde_json::from_reader(
602          std::fs::File::open(Path::new(&package_json_path))
603            .expect("Unable to read package.json file"),
604        )
605        .expect("error while reading or parsing");
606
607        workspace_config.config = Some(
608          Config::builder()
609            .set_default("name", package_json.name)?
610            .set_default("namespace", package_json.namespace)?
611            .set_default("repository", package_json.repository.get("url").unwrap().to_string())?
612            .set_default("license", package_json.license)?
613            .set_default("homepage", package_json.homepage)?
614            .set_default("workspace_root", workspace_root.to_str().unwrap().to_string())?
615            .add_source(
616              File::with_name(&format!(
617                "{}/storm-workspace.json",
618                workspace_root.to_string_lossy()
619              ))
620              .required(false),
621            )
622            .add_source(
623              File::with_name(&format!(
624                "{}/storm-workspace.jsonc",
625                workspace_root.to_string_lossy()
626              ))
627              .required(false),
628            )
629            .add_source(
630              File::with_name(&format!("{}/.storm/config.json", workspace_root.to_string_lossy()))
631                .required(false),
632            )
633            .add_source(
634              File::with_name(&format!(
635                "{}/.storm-workspace/config.json",
636                workspace_root.to_string_lossy()
637              ))
638              .required(false),
639            )
640            .add_source(
641              File::with_name(&format!(
642                "{}/storm-workspace.toml",
643                workspace_root.to_string_lossy()
644              ))
645              .required(false),
646            )
647            .add_source(
648              File::with_name(&format!("{}/.storm/config.toml", workspace_root.to_string_lossy()))
649                .required(false),
650            )
651            .add_source(
652              File::with_name(&format!(
653                "{}/.storm-workspace/config.toml",
654                workspace_root.to_string_lossy()
655              ))
656              .required(false),
657            )
658            .add_source(
659              File::with_name(&format!(
660                "{}/storm-workspace.yaml",
661                workspace_root.to_string_lossy()
662              ))
663              .required(false),
664            )
665            .add_source(
666              File::with_name(&format!("{}/.storm/config.yaml", workspace_root.to_string_lossy()))
667                .required(false),
668            )
669            .add_source(
670              File::with_name(&format!(
671                "{}/.storm-workspace/config.yaml",
672                workspace_root.to_string_lossy()
673              ))
674              .required(false),
675            )
676            .add_source(
677              File::with_name(&format!("{}/storm-workspace.yml", workspace_root.to_string_lossy()))
678                .required(false),
679            )
680            .add_source(
681              File::with_name(&format!("{}/.storm/config.yml", workspace_root.to_string_lossy()))
682                .required(false),
683            )
684            .add_source(
685              File::with_name(&format!(
686                "{}/.storm-workspace/config.yml",
687                workspace_root.to_string_lossy()
688              ))
689              .required(false),
690            )
691            .add_source(Environment::with_prefix("storm"))
692            .build()?,
693        );
694
695        let config = workspace_config.config.as_ref().unwrap();
696        if let Ok(found) = config.get_string("config_file") {
697          workspace_config.config_file = Some(found);
698        }
699        if let Ok(found) = config.get_string("schema") {
700          workspace_config.schema = found;
701        }
702        if let Ok(found) = config.get::<ExtendsConfig>("extends") {
703          workspace_config.extends = Some(found);
704        }
705        if let Ok(found) = config.get_string("workspace_root") {
706          workspace_config.workspace_root = found;
707        }
708        if let Ok(found) = config.get_string("name") {
709          workspace_config.name = found;
710        }
711        if let Ok(found) = config.get_string("namespace") {
712          workspace_config.namespace = found;
713        }
714        if let Ok(found) = config.get::<WorkspaceVariant>("variant") {
715          workspace_config.variant = found;
716        }
717        if let Ok(found) = config.get::<WorkspaceOrganizationConfig>("organization") {
718          workspace_config.organization = found;
719        } else if let Ok(found) = config.get::<WorkspaceOrganizationConfig>("org") {
720          workspace_config.organization = found;
721        } else if let Ok(found) = config.get::<WorkspaceOrganizationConfig>("organization_config") {
722          workspace_config.organization = found;
723        }
724        if let Ok(found) = config.get_string("repository") {
725          workspace_config.repository = found;
726        }
727        if let Ok(found) = config.get_string("license") {
728          workspace_config.license = found;
729        }
730        if let Ok(found) = config.get_string("homepage") {
731          workspace_config.homepage = found;
732        }
733        if let Ok(found) = config.get_string("docs") {
734          workspace_config.docs = Some(found);
735        }
736        if let Ok(found) = config.get_string("portal") {
737          workspace_config.portal = Some(found);
738        }
739        if let Ok(found) = config.get_string("licensing") {
740          workspace_config.licensing = Some(found);
741        }
742        if let Ok(found) = config.get_string("contact") {
743          workspace_config.contact = Some(found);
744        }
745        if let Ok(found) = config.get_string("support") {
746          workspace_config.support = Some(found);
747        }
748        if let Ok(found) = config.get_string("branch") {
749          workspace_config.branch = found;
750        }
751        if let Ok(found) = config.get_string("preid") {
752          workspace_config.preid = Some(found);
753        }
754        if let Ok(found) = config.get_string("owner") {
755          workspace_config.owner = found;
756        }
757        if let Ok(found) = config.get_string("mode") {
758          workspace_config.mode = WorkspaceMode::from_str(&found).unwrap();
759        }
760        if let Ok(found) = config.get_bool("skip_cache") {
761          workspace_config.skip_cache = found;
762        }
763        if let Ok(found) = config.get_string("package_manager") {
764          workspace_config.package_manager = PackageManagerType::from_str(&found).unwrap();
765        }
766        if let Ok(found) = config.get_string("timezone") {
767          workspace_config.timezone = found;
768        }
769        if let Ok(found) = config.get_string("locale") {
770          workspace_config.locale = found;
771        }
772        if let Ok(found) = config.get_string("log_level") {
773          workspace_config.log_level = LogLevel::from_str(&found).unwrap();
774        }
775        if let Ok(found) = config.get_bool("skip_config_logging") {
776          workspace_config.skip_config_logging = found;
777        }
778        if let Ok(found) = config.get::<WorkspaceReleaseConfig>("release") {
779          workspace_config.release = found;
780        }
781        if let Ok(found) = config.get::<WorkspaceColorsConfig>("colors") {
782          workspace_config.colors = found;
783        }
784        if let Ok(found) = config.get::<WorkspaceBotConfig>("bot") {
785          workspace_config.bot = found;
786        } else if let Ok(found) = config.get::<WorkspaceBotConfig>("bot_config") {
787          workspace_config.bot = found;
788        } else if let Ok(found) = config.get::<WorkspaceBotConfig>("workspaceBot") {
789          workspace_config.bot = found;
790        }
791        if let Ok(found) = config.get::<WorkspaceSocialsConfig>("socials") {
792          workspace_config.socials = found;
793        }
794        if let Ok(found) = config.get::<WorkspaceErrorConfig>("error") {
795          workspace_config.error = found;
796        }
797        if let Ok(found) = config.get::<WorkspaceRegistryUrlConfig>("registry") {
798          workspace_config.registry = found;
799        } else if let Ok(found) = config.get::<WorkspaceRegistryUrlConfig>("registry_urls") {
800          workspace_config.registry = found;
801        } else if let Ok(found) = config.get::<WorkspaceRegistryUrlConfig>("registryUrls") {
802          workspace_config.registry = found;
803        }
804        if let Ok(found) = config.get::<WorkspaceDirectoriesConfig>("directories") {
805          workspace_config.directories = found;
806        }
807        if let Ok(found) = config.get_string("package_manager") {
808          workspace_config.package_manager = PackageManagerType::from_str(&found).unwrap();
809        }
810
811        Ok(workspace_config)
812      }
813      Err(_) => {
814        return Err(ConfigError::NotFound(format!(
815          "{}/package.json",
816          workspace_root.to_string_lossy()
817        )));
818      }
819    }
820  }
821
822  pub fn get_extension(&self, name: &str) -> Option<HashMap<String, Value>> {
823    if let Some(existing) = self.extensions.borrow().get(name) {
824      return Some(existing.clone());
825    }
826
827    let extension = self.config.as_ref().expect("Config value must be determined").get_table(name);
828    match extension.is_ok() {
829      true => {
830        self.extensions.borrow_mut().insert(name.to_string(), extension.ok().unwrap());
831        return self.extensions.borrow().get(name).cloned();
832      }
833      false => None,
834    }
835  }
836}
837
838impl Default for WorkspaceConfig {
839  fn default() -> Self {
840    WorkspaceConfig {
841      schema: "https://stormsoftware.com/schemas/storm-workspace.json".to_string(),
842      config: None,
843      config_file: None,
844      extends: None,
845      workspace_root: get_workspace_root().unwrap().to_str().unwrap().to_string(),
846      mode: WorkspaceMode::Production,
847      variant: WorkspaceVariant::Monorepo,
848      name: "storm-monorepo".to_string(),
849      namespace: "storm-software".to_string(),
850      organization: WorkspaceOrganizationConfig::default(),
851      repository: "https://github.com/storm-software/storm-monorepo".to_string(),
852      license: "Apache-2.0".to_string(),
853      homepage: "https://stormsoftware.com".to_string(),
854      branch: "main".to_string(),
855      preid: None,
856      docs: None,
857      portal: None,
858      licensing: None,
859      contact: None,
860      support: None,
861      release: WorkspaceReleaseConfig::default(),
862      socials: WorkspaceSocialsConfig::default(),
863      error: WorkspaceErrorConfig::default(),
864      directories: WorkspaceDirectoriesConfig::default(),
865      owner: "@storm-software/admin".to_string(),
866      bot: WorkspaceBotConfig::default(),
867      log_level: LogLevel::Info,
868      skip_config_logging: true,
869      skip_cache: false,
870      package_manager: PackageManagerType::Npm,
871      registry: WorkspaceRegistryUrlConfig::default(),
872      timezone: "America/New_York".to_string(),
873      locale: "en-US".to_string(),
874      colors: WorkspaceColorsConfig::default(),
875      extensions: HashMap::new().into(),
876    }
877  }
878}
879
880impl FromStr for WorkspaceConfig {
881  type Err = ();
882
883  fn from_str(input: &str) -> Result<WorkspaceConfig, Self::Err> {
884    let workspace_root = Path::new(input);
885    WorkspaceConfig::from_workspace_root(workspace_root).map_err(|_| ())
886  }
887}