mod.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::Result;
  5. use clap::Parser;
  6. use colored::{ColoredString, Colorize};
  7. use dialoguer::{theme::ColorfulTheme, Confirm};
  8. use serde::Deserialize;
  9. use std::fmt::{self, Display, Formatter};
  10. mod app;
  11. mod env_nodejs;
  12. mod env_rust;
  13. mod env_system;
  14. #[cfg(target_os = "macos")]
  15. mod ios;
  16. mod packages_nodejs;
  17. mod packages_rust;
  18. mod plugins;
  19. #[derive(Deserialize)]
  20. struct JsCliVersionMetadata {
  21. version: String,
  22. node: String,
  23. }
  24. #[derive(Deserialize)]
  25. #[serde(rename_all = "camelCase")]
  26. pub struct VersionMetadata {
  27. #[serde(rename = "cli.js")]
  28. js_cli: JsCliVersionMetadata,
  29. }
  30. fn version_metadata() -> Result<VersionMetadata> {
  31. serde_json::from_str::<VersionMetadata>(include_str!("../../metadata-v2.json"))
  32. .map_err(Into::into)
  33. }
  34. #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
  35. pub enum Status {
  36. Neutral = 0,
  37. #[default]
  38. Success,
  39. Warning,
  40. Error,
  41. }
  42. impl Status {
  43. fn color<S: AsRef<str>>(&self, s: S) -> ColoredString {
  44. let s = s.as_ref();
  45. match self {
  46. Status::Neutral => s.normal(),
  47. Status::Success => s.green(),
  48. Status::Warning => s.yellow(),
  49. Status::Error => s.red(),
  50. }
  51. }
  52. }
  53. impl Display for Status {
  54. fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
  55. write!(
  56. f,
  57. "{}",
  58. match self {
  59. Status::Neutral => "-".cyan(),
  60. Status::Success => "✔".green(),
  61. Status::Warning => "⚠".yellow(),
  62. Status::Error => "✘".red(),
  63. }
  64. )
  65. }
  66. }
  67. #[derive(Default)]
  68. pub enum ActionResult {
  69. Full {
  70. description: String,
  71. status: Status,
  72. },
  73. Description(String),
  74. #[default]
  75. None,
  76. }
  77. impl From<String> for ActionResult {
  78. fn from(value: String) -> Self {
  79. ActionResult::Description(value)
  80. }
  81. }
  82. impl From<(String, Status)> for ActionResult {
  83. fn from(value: (String, Status)) -> Self {
  84. ActionResult::Full {
  85. description: value.0,
  86. status: value.1,
  87. }
  88. }
  89. }
  90. impl From<Option<String>> for ActionResult {
  91. fn from(value: Option<String>) -> Self {
  92. value.map(ActionResult::Description).unwrap_or_default()
  93. }
  94. }
  95. impl From<Option<(String, Status)>> for ActionResult {
  96. fn from(value: Option<(String, Status)>) -> Self {
  97. value
  98. .map(|v| ActionResult::Full {
  99. description: v.0,
  100. status: v.1,
  101. })
  102. .unwrap_or_default()
  103. }
  104. }
  105. pub struct SectionItem {
  106. /// If description is none, the item is skipped
  107. description: Option<String>,
  108. status: Status,
  109. action: Option<Box<dyn FnMut() -> ActionResult>>,
  110. action_if_err: Option<Box<dyn FnMut() -> ActionResult>>,
  111. }
  112. impl Display for SectionItem {
  113. fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
  114. let desc = self
  115. .description
  116. .as_ref()
  117. .map(|s| s.replace('\n', "\n "))
  118. .unwrap_or_default();
  119. let (first, second) = desc.split_once(':').unwrap();
  120. write!(f, "{} {}:{}", self.status, first.bold(), second)
  121. }
  122. }
  123. impl SectionItem {
  124. fn new() -> Self {
  125. Self {
  126. action: None,
  127. action_if_err: None,
  128. description: None,
  129. status: Status::Neutral,
  130. }
  131. }
  132. fn action<F: FnMut() -> ActionResult + 'static>(mut self, action: F) -> Self {
  133. self.action = Some(Box::new(action));
  134. self
  135. }
  136. // fn action_if_err<F: FnMut() -> ActionResult + 'static>(mut self, action: F) -> Self {
  137. // self.action_if_err = Some(Box::new(action));
  138. // self
  139. // }
  140. fn description<S: AsRef<str>>(mut self, description: S) -> Self {
  141. self.description = Some(description.as_ref().to_string());
  142. self
  143. }
  144. fn run_action(&mut self) {
  145. let mut res = ActionResult::None;
  146. if let Some(action) = &mut self.action {
  147. res = action();
  148. }
  149. self.apply_action_result(res);
  150. }
  151. fn run_action_if_err(&mut self) {
  152. let mut res = ActionResult::None;
  153. if let Some(action) = &mut self.action_if_err {
  154. res = action();
  155. }
  156. self.apply_action_result(res);
  157. }
  158. fn apply_action_result(&mut self, result: ActionResult) {
  159. match result {
  160. ActionResult::Full {
  161. description,
  162. status,
  163. } => {
  164. self.description = Some(description);
  165. self.status = status;
  166. }
  167. ActionResult::Description(description) => {
  168. self.description = Some(description);
  169. }
  170. ActionResult::None => {}
  171. }
  172. }
  173. fn run(&mut self, interactive: bool) -> Status {
  174. self.run_action();
  175. if self.status == Status::Error && interactive && self.action_if_err.is_some() {
  176. if let Some(description) = &self.description {
  177. let confirmed = Confirm::with_theme(&ColorfulTheme::default())
  178. .with_prompt(format!(
  179. "{}\n Run the automatic fix?",
  180. description.replace('\n', "\n ")
  181. ))
  182. .interact()
  183. .unwrap_or(false);
  184. if confirmed {
  185. self.run_action_if_err()
  186. }
  187. }
  188. }
  189. self.status
  190. }
  191. }
  192. struct Section<'a> {
  193. label: &'a str,
  194. interactive: bool,
  195. items: Vec<SectionItem>,
  196. }
  197. impl Section<'_> {
  198. fn display(&mut self) {
  199. let mut status = Status::Neutral;
  200. for item in &mut self.items {
  201. let s = item.run(self.interactive);
  202. if s > status {
  203. status = s;
  204. }
  205. }
  206. let status_str = format!("[{status}]");
  207. let status = status.color(status_str);
  208. println!();
  209. println!("{} {}", status, self.label.bold().yellow());
  210. for item in &self.items {
  211. if item.description.is_some() {
  212. println!(" {item}");
  213. }
  214. }
  215. }
  216. }
  217. #[derive(Debug, Parser)]
  218. #[clap(
  219. about = "Show a concise list of information about the environment, Rust, Node.js and their versions as well as a few relevant project configurations"
  220. )]
  221. pub struct Options {
  222. /// Interactive mode to apply automatic fixes.
  223. #[clap(long)]
  224. pub interactive: bool,
  225. }
  226. pub fn command(options: Options) -> Result<()> {
  227. let Options { interactive } = options;
  228. let app_dir = crate::helpers::app_paths::resolve_app_dir();
  229. let tauri_dir = crate::helpers::app_paths::resolve_tauri_dir();
  230. if tauri_dir.is_some() {
  231. // safe to initialize
  232. crate::helpers::app_paths::resolve();
  233. }
  234. let package_manager = app_dir
  235. .as_ref()
  236. .map(packages_nodejs::package_manager)
  237. .unwrap_or(crate::helpers::npm::PackageManager::Npm);
  238. let metadata = version_metadata()?;
  239. let mut environment = Section {
  240. label: "Environment",
  241. interactive,
  242. items: Vec::new(),
  243. };
  244. environment.items.extend(env_system::items());
  245. environment.items.extend(env_rust::items());
  246. let items = env_nodejs::items(&metadata);
  247. environment.items.extend(items);
  248. let mut packages = Section {
  249. label: "Packages",
  250. interactive,
  251. items: Vec::new(),
  252. };
  253. packages
  254. .items
  255. .extend(packages_rust::items(app_dir.as_ref(), tauri_dir.as_deref()));
  256. packages.items.extend(packages_nodejs::items(
  257. app_dir.as_ref(),
  258. package_manager,
  259. &metadata,
  260. ));
  261. let mut plugins = Section {
  262. label: "Plugins",
  263. interactive,
  264. items: plugins::items(app_dir.as_ref(), tauri_dir.as_deref(), package_manager),
  265. };
  266. let mut app = Section {
  267. label: "App",
  268. interactive,
  269. items: Vec::new(),
  270. };
  271. app
  272. .items
  273. .extend(app::items(app_dir.as_ref(), tauri_dir.as_deref()));
  274. environment.display();
  275. packages.display();
  276. plugins.display();
  277. app.display();
  278. // iOS
  279. #[cfg(target_os = "macos")]
  280. {
  281. if let Some(p) = &tauri_dir {
  282. if p.join("gen/apple").exists() {
  283. let mut ios = Section {
  284. label: "iOS",
  285. interactive,
  286. items: Vec::new(),
  287. };
  288. ios.items.extend(ios::items());
  289. ios.display();
  290. }
  291. }
  292. }
  293. Ok(())
  294. }