init.rs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use crate::{
  5. helpers::{
  6. framework::{infer_from_package_json as infer_framework, Framework},
  7. resolve_tauri_path, template, Logger,
  8. },
  9. VersionMetadata,
  10. };
  11. use std::{
  12. collections::BTreeMap,
  13. env::current_dir,
  14. fmt::Display,
  15. fs::{read_to_string, remove_dir_all},
  16. path::PathBuf,
  17. str::FromStr,
  18. };
  19. use crate::Result;
  20. use anyhow::Context;
  21. use clap::Parser;
  22. use dialoguer::Input;
  23. use handlebars::{to_json, Handlebars};
  24. use include_dir::{include_dir, Dir};
  25. use serde::Deserialize;
  26. const TEMPLATE_DIR: Dir<'_> = include_dir!("templates/app");
  27. #[derive(Debug, Parser)]
  28. #[clap(about = "Initializes a Tauri project")]
  29. pub struct Options {
  30. /// Skip prompting for values
  31. #[clap(long)]
  32. ci: bool,
  33. /// Force init to overwrite the src-tauri folder
  34. #[clap(short, long)]
  35. force: bool,
  36. /// Enables logging
  37. #[clap(short, long)]
  38. log: bool,
  39. /// Set target directory for init
  40. #[clap(short, long)]
  41. #[clap(default_value_t = current_dir().expect("failed to read cwd").display().to_string())]
  42. directory: String,
  43. /// Path of the Tauri project to use (relative to the cwd)
  44. #[clap(short, long)]
  45. tauri_path: Option<PathBuf>,
  46. /// Name of your Tauri application
  47. #[clap(short = 'A', long)]
  48. app_name: Option<String>,
  49. /// Window title of your Tauri application
  50. #[clap(short = 'W', long)]
  51. window_title: Option<String>,
  52. /// Web assets location, relative to <project-dir>/src-tauri
  53. #[clap(short = 'D', long)]
  54. dist_dir: Option<String>,
  55. /// Url of your dev server
  56. #[clap(short = 'P', long)]
  57. dev_path: Option<String>,
  58. }
  59. #[derive(Deserialize)]
  60. struct PackageJson {
  61. name: Option<String>,
  62. product_name: Option<String>,
  63. }
  64. #[derive(Default)]
  65. struct InitDefaults {
  66. app_name: Option<String>,
  67. framework: Option<Framework>,
  68. }
  69. impl Options {
  70. fn load(mut self) -> Result<Self> {
  71. self.ci = self.ci || std::env::var("CI").is_ok();
  72. let package_json_path = PathBuf::from(&self.directory).join("package.json");
  73. let init_defaults = if package_json_path.exists() {
  74. let package_json_text = read_to_string(package_json_path)?;
  75. let package_json: PackageJson = serde_json::from_str(&package_json_text)?;
  76. let (framework, _) = infer_framework(&package_json_text);
  77. InitDefaults {
  78. app_name: package_json.product_name.or(package_json.name),
  79. framework,
  80. }
  81. } else {
  82. Default::default()
  83. };
  84. self.app_name = self.app_name.or(request_input(
  85. "What is your app name?",
  86. init_defaults.app_name.clone(),
  87. self.ci,
  88. )?);
  89. self.window_title = self.window_title.or(request_input(
  90. "What should the window title be?",
  91. init_defaults.app_name.clone(),
  92. self.ci,
  93. )?);
  94. self.dist_dir = self.dist_dir
  95. .or(request_input(
  96. r#"Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created?"#,
  97. init_defaults.framework.as_ref().map(|f| f.dist_dir()),
  98. self.ci)?);
  99. self.dev_path = self.dev_path.or(request_input(
  100. "What is the url of your dev server?",
  101. init_defaults.framework.map(|f| f.dev_path()),
  102. self.ci,
  103. )?);
  104. Ok(self)
  105. }
  106. }
  107. pub fn command(mut options: Options) -> Result<()> {
  108. options = options.load()?;
  109. let logger = Logger::new("tauri:init");
  110. let template_target_path = PathBuf::from(&options.directory).join("src-tauri");
  111. let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata.json"))?;
  112. if template_target_path.exists() && !options.force {
  113. logger.warn(format!(
  114. "Tauri dir ({:?}) not empty. Run `init --force` to overwrite.",
  115. template_target_path
  116. ));
  117. } else {
  118. let (tauri_dep, tauri_build_dep) = if let Some(tauri_path) = options.tauri_path {
  119. (
  120. format!(
  121. r#"{{ path = {:?}, features = [ "api-all" ] }}"#,
  122. resolve_tauri_path(&tauri_path, "core/tauri")
  123. ),
  124. format!(
  125. "{{ path = {:?} }}",
  126. resolve_tauri_path(&tauri_path, "core/tauri-build")
  127. ),
  128. )
  129. } else {
  130. (
  131. format!(
  132. r#"{{ version = "{}", features = [ "api-all" ] }}"#,
  133. metadata.tauri
  134. ),
  135. format!(r#"{{ version = "{}" }}"#, metadata.tauri_build),
  136. )
  137. };
  138. let _ = remove_dir_all(&template_target_path);
  139. let handlebars = Handlebars::new();
  140. let mut data = BTreeMap::new();
  141. data.insert("tauri_dep", to_json(tauri_dep));
  142. data.insert("tauri_build_dep", to_json(tauri_build_dep));
  143. data.insert(
  144. "dist_dir",
  145. to_json(options.dist_dir.unwrap_or_else(|| "../dist".to_string())),
  146. );
  147. data.insert(
  148. "dev_path",
  149. to_json(
  150. options
  151. .dev_path
  152. .unwrap_or_else(|| "http://localhost:4000".to_string()),
  153. ),
  154. );
  155. data.insert(
  156. "app_name",
  157. to_json(options.app_name.unwrap_or_else(|| "Tauri App".to_string())),
  158. );
  159. data.insert(
  160. "window_title",
  161. to_json(options.window_title.unwrap_or_else(|| "Tauri".to_string())),
  162. );
  163. template::render(&handlebars, &data, &TEMPLATE_DIR, &options.directory)
  164. .with_context(|| "failed to render Tauri template")?;
  165. }
  166. Ok(())
  167. }
  168. fn request_input<T>(prompt: &str, default: Option<T>, skip: bool) -> Result<Option<T>>
  169. where
  170. T: Clone + FromStr + Display,
  171. T::Err: Display + std::fmt::Debug,
  172. {
  173. if skip {
  174. Ok(default)
  175. } else {
  176. let mut builder = Input::new();
  177. builder.with_prompt(prompt);
  178. if let Some(v) = default {
  179. builder.default(v);
  180. }
  181. builder.interact_text().map(Some).map_err(Into::into)
  182. }
  183. }