init.rs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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::{
  5. helpers::{
  6. framework::{infer_from_package_json as infer_framework, Framework},
  7. npm::PackageManager,
  8. prompts, resolve_tauri_path, template,
  9. },
  10. VersionMetadata,
  11. };
  12. use std::{
  13. collections::BTreeMap,
  14. env::current_dir,
  15. fs::{read_to_string, remove_dir_all},
  16. path::PathBuf,
  17. };
  18. use crate::Result;
  19. use anyhow::Context;
  20. use clap::Parser;
  21. use handlebars::{to_json, Handlebars};
  22. use include_dir::{include_dir, Dir};
  23. const TEMPLATE_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/templates/app");
  24. const TAURI_CONF_TEMPLATE: &str = include_str!("../templates/tauri.conf.json");
  25. #[derive(Debug, Parser)]
  26. #[clap(about = "Initialize a Tauri project in an existing directory")]
  27. pub struct Options {
  28. /// Skip prompting for values
  29. #[clap(long, env = "CI")]
  30. ci: bool,
  31. /// Force init to overwrite the src-tauri folder
  32. #[clap(short, long)]
  33. force: bool,
  34. /// Enables logging
  35. #[clap(short, long)]
  36. log: bool,
  37. /// Set target directory for init
  38. #[clap(short, long)]
  39. #[clap(default_value_t = current_dir().expect("failed to read cwd").display().to_string())]
  40. directory: String,
  41. /// Path of the Tauri project to use (relative to the cwd)
  42. #[clap(short, long)]
  43. tauri_path: Option<PathBuf>,
  44. /// Name of your Tauri application
  45. #[clap(short = 'A', long)]
  46. app_name: Option<String>,
  47. /// Window title of your Tauri application
  48. #[clap(short = 'W', long)]
  49. window_title: Option<String>,
  50. /// Web assets location, relative to <project-dir>/src-tauri
  51. #[clap(short = 'D', long)]
  52. frontend_dist: Option<String>,
  53. /// Url of your dev server
  54. #[clap(short = 'P', long)]
  55. dev_url: Option<String>,
  56. /// A shell command to run before `tauri dev` kicks in.
  57. #[clap(long)]
  58. before_dev_command: Option<String>,
  59. /// A shell command to run before `tauri build` kicks in.
  60. #[clap(long)]
  61. before_build_command: Option<String>,
  62. }
  63. #[derive(Default)]
  64. struct InitDefaults {
  65. app_name: Option<String>,
  66. framework: Option<Framework>,
  67. }
  68. impl Options {
  69. fn load(mut self) -> Result<Self> {
  70. let package_json_path = PathBuf::from(&self.directory).join("package.json");
  71. let init_defaults = if package_json_path.exists() {
  72. let package_json_text = read_to_string(package_json_path)?;
  73. let package_json: crate::PackageJson = serde_json::from_str(&package_json_text)?;
  74. let (framework, _) = infer_framework(&package_json_text);
  75. InitDefaults {
  76. app_name: package_json.product_name.or(package_json.name),
  77. framework,
  78. }
  79. } else {
  80. Default::default()
  81. };
  82. self.app_name = self.app_name.map(|s| Ok(Some(s))).unwrap_or_else(|| {
  83. prompts::input(
  84. "What is your app name?",
  85. Some(
  86. init_defaults
  87. .app_name
  88. .clone()
  89. .unwrap_or_else(|| "Tauri App".to_string()),
  90. ),
  91. self.ci,
  92. true,
  93. )
  94. })?;
  95. self.window_title = self.window_title.map(|s| Ok(Some(s))).unwrap_or_else(|| {
  96. prompts::input(
  97. "What should the window title be?",
  98. Some(
  99. init_defaults
  100. .app_name
  101. .clone()
  102. .unwrap_or_else(|| "Tauri".to_string()),
  103. ),
  104. self.ci,
  105. true,
  106. )
  107. })?;
  108. self.frontend_dist = self.frontend_dist.map(|s| Ok(Some(s))).unwrap_or_else(|| prompts::input(
  109. 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?"#,
  110. init_defaults.framework.as_ref().map(|f| f.frontend_dist()),
  111. self.ci,
  112. false,
  113. ))?;
  114. self.dev_url = self.dev_url.map(|s| Ok(Some(s))).unwrap_or_else(|| {
  115. prompts::input(
  116. "What is the url of your dev server?",
  117. init_defaults.framework.map(|f| f.dev_url()),
  118. self.ci,
  119. true,
  120. )
  121. })?;
  122. let detected_package_manager = match PackageManager::from_project(&self.directory).first() {
  123. Some(&package_manager) => package_manager,
  124. None => PackageManager::Npm,
  125. };
  126. self.before_dev_command = self
  127. .before_dev_command
  128. .map(|s| Ok(Some(s)))
  129. .unwrap_or_else(|| {
  130. prompts::input(
  131. "What is your frontend dev command?",
  132. Some(default_dev_command(detected_package_manager).into()),
  133. self.ci,
  134. true,
  135. )
  136. })?;
  137. self.before_build_command = self
  138. .before_build_command
  139. .map(|s| Ok(Some(s)))
  140. .unwrap_or_else(|| {
  141. prompts::input(
  142. "What is your frontend build command?",
  143. Some(default_build_command(detected_package_manager).into()),
  144. self.ci,
  145. true,
  146. )
  147. })?;
  148. Ok(self)
  149. }
  150. }
  151. fn default_dev_command(pm: PackageManager) -> &'static str {
  152. match pm {
  153. PackageManager::Yarn => "yarn dev",
  154. PackageManager::YarnBerry => "yarn dev",
  155. PackageManager::Npm => "npm run dev",
  156. PackageManager::Pnpm => "pnpm dev",
  157. PackageManager::Bun => "bun dev",
  158. PackageManager::Deno => "deno task dev",
  159. }
  160. }
  161. fn default_build_command(pm: PackageManager) -> &'static str {
  162. match pm {
  163. PackageManager::Yarn => "yarn build",
  164. PackageManager::YarnBerry => "yarn build",
  165. PackageManager::Npm => "npm run build",
  166. PackageManager::Pnpm => "pnpm build",
  167. PackageManager::Bun => "bun build",
  168. PackageManager::Deno => "deno task build",
  169. }
  170. }
  171. pub fn command(mut options: Options) -> Result<()> {
  172. options = options.load()?;
  173. let template_target_path = PathBuf::from(&options.directory).join("src-tauri");
  174. let metadata = serde_json::from_str::<VersionMetadata>(include_str!("../metadata-v2.json"))?;
  175. if template_target_path.exists() && !options.force {
  176. log::warn!(
  177. "Tauri dir ({:?}) not empty. Run `init --force` to overwrite.",
  178. template_target_path
  179. );
  180. } else {
  181. let (tauri_dep, tauri_build_dep) = if let Some(tauri_path) = &options.tauri_path {
  182. (
  183. format!(
  184. r#"{{ path = {:?} }}"#,
  185. resolve_tauri_path(tauri_path, "crates/tauri")
  186. ),
  187. format!(
  188. "{{ path = {:?} }}",
  189. resolve_tauri_path(tauri_path, "crates/tauri-build")
  190. ),
  191. )
  192. } else {
  193. (
  194. format!(r#"{{ version = "{}" }}"#, metadata.tauri),
  195. format!(r#"{{ version = "{}" }}"#, metadata.tauri_build),
  196. )
  197. };
  198. let _ = remove_dir_all(&template_target_path);
  199. let mut handlebars = Handlebars::new();
  200. handlebars.register_escape_fn(handlebars::no_escape);
  201. let mut data = BTreeMap::new();
  202. data.insert("tauri_dep", to_json(tauri_dep));
  203. if options.tauri_path.is_some() {
  204. data.insert("patch_tauri_dep", to_json(true));
  205. }
  206. data.insert("tauri_build_dep", to_json(tauri_build_dep));
  207. data.insert(
  208. "frontend_dist",
  209. to_json(options.frontend_dist.as_deref().unwrap_or("../dist")),
  210. );
  211. data.insert("dev_url", to_json(options.dev_url));
  212. data.insert(
  213. "app_name",
  214. to_json(options.app_name.as_deref().unwrap_or("Tauri App")),
  215. );
  216. data.insert(
  217. "window_title",
  218. to_json(options.window_title.as_deref().unwrap_or("Tauri")),
  219. );
  220. data.insert("before_dev_command", to_json(options.before_dev_command));
  221. data.insert(
  222. "before_build_command",
  223. to_json(options.before_build_command),
  224. );
  225. let mut config = serde_json::from_str(
  226. &handlebars
  227. .render_template(TAURI_CONF_TEMPLATE, &data)
  228. .expect("Failed to render tauri.conf.json template"),
  229. )
  230. .unwrap();
  231. if option_env!("TARGET") == Some("node") {
  232. let mut dir = current_dir().expect("failed to read cwd");
  233. let mut count = 0;
  234. let mut cli_node_module_path = None;
  235. let cli_path = "node_modules/@tauri-apps/cli";
  236. // only go up three folders max
  237. while count <= 2 {
  238. let test_path = dir.join(cli_path);
  239. if test_path.exists() {
  240. let mut node_module_path = PathBuf::from("..");
  241. for _ in 0..count {
  242. node_module_path.push("..");
  243. }
  244. node_module_path.push(cli_path);
  245. node_module_path.push("config.schema.json");
  246. cli_node_module_path.replace(node_module_path);
  247. break;
  248. }
  249. count += 1;
  250. match dir.parent() {
  251. Some(parent) => {
  252. dir = parent.to_path_buf();
  253. }
  254. None => break,
  255. }
  256. }
  257. if let Some(cli_node_module_path) = cli_node_module_path {
  258. let mut map = serde_json::Map::default();
  259. map.insert(
  260. "$schema".into(),
  261. serde_json::Value::String(
  262. cli_node_module_path
  263. .display()
  264. .to_string()
  265. .replace('\\', "/"),
  266. ),
  267. );
  268. let merge_config = serde_json::Value::Object(map);
  269. json_patch::merge(&mut config, &merge_config);
  270. }
  271. }
  272. data.insert(
  273. "tauri_config",
  274. to_json(serde_json::to_string_pretty(&config).unwrap()),
  275. );
  276. template::render(&handlebars, &data, &TEMPLATE_DIR, &options.directory)
  277. .with_context(|| "failed to render Tauri template")?;
  278. }
  279. Ok(())
  280. }